计算浮点功率(PHP/BCMath)

Ali*_*xel 7 php algorithm math bcmath pow

我正在为bcmath扩展编写一个包装器,并且关于#10116的bugbcpow()特别烦人 - 它将$right_operand($exp)转换为(本机PHP,而不是任意长度)整数,所以当你尝试计算平方根(或任何其他)高于1你的数字的根,1而不是正确的结果.

我开始搜索算法,这些算法可以让我计算数字的第n个根,我发现这个答案看起来非常可靠,我实际上使用WolframAlpha 扩展了公式,并且我能够将其速度提高约5%,同时保持准确性结果.

这是一个模仿我的BCMath实现及其局限性的纯PHP实现:

function _pow($n, $exp)
{
    $result = pow($n, intval($exp)); // bcmath casts $exp to (int)

    if (fmod($exp, 1) > 0) // does $exp have a fracional part higher than 0?
    {
        $exp = 1 / fmod($exp, 1); // convert the modulo into a root (2.5 -> 1 / 0.5 = 2)

        $x = 1;
        $y = (($n * _pow($x, 1 - $exp)) / $exp) - ($x / $exp) + $x;

        do
        {
            $x = $y;
            $y = (($n * _pow($x, 1 - $exp)) / $exp) - ($x / $exp) + $x;
        } while ($x > $y);

        return $result * $x; // 4^2.5 = 4^2 * 4^0.5 = 16 * 2 = 32
    }

    return $result;
}
Run Code Online (Sandbox Code Playgroud)

除非不产生整数,否则上述似乎效果很好 .例如,如果是,它的反转将是和结果和将有点不同(演示):1 / fmod($exp, 1)$exp0.1234568.10005pow()_pow()

  • pow(2, 0.123456) = 1.0893412745953
  • _pow(2, 0.123456) = 1.0905077326653
  • _pow(2, 1 / 8)= _pow(2, 0.125)=1.0905077326653

如何使用"手动"指数计算达到相同的准确度?

Dan*_*her 5

用于找到(正)数的 n 根的所采用的算法a是用于求零的牛顿算法

f(x) = x^n - a.
Run Code Online (Sandbox Code Playgroud)

这只涉及以自然数作为指数的权力,因此很容易实现.

计算功率使用指数0 < y < 1,其中y不是形式的1/n一个整数n是更复杂的.做模拟,解决

x^(1/y) - a == 0
Run Code Online (Sandbox Code Playgroud)

将再次涉及用非积分指数计算功率,这是我们试图解决的问题.

如果y = n/d小分母是合理的d,那么通过计算可以很容易地解决问题

x^(n/d) = (x^n)^(1/d),
Run Code Online (Sandbox Code Playgroud)

但是对于大多数理性而言0 < y < 1,分子和分母相当大,而中间体x^n会很大,因此计算将使用大量内存并占用(相对)长时间.(对于示例的指数0.123456 = 1929/15625,它不是太糟糕,但0.1234567会相当费力.)

计算一般理性的力量的一种方法0 < y < 1是写

y = 1/a ± 1/b ± 1/c ± ... ± 1/q
Run Code Online (Sandbox Code Playgroud)

使用整数a < b < c < ... < q并对个体进行乘法/除法x^(1/k).(每个理性0 < y < 1都有这样的表示,而最短的表示通常不涉及很多术语,例如

1929/15625 = 1/8 - 1/648 - 1/1265625;
Run Code Online (Sandbox Code Playgroud)

仅使用分解中的加法导致具有较大分母的较长表示,例如

1929/15625 = 1/9 + 1/82 + 1/6678 + 1/46501020 + 1/2210396922562500,
Run Code Online (Sandbox Code Playgroud)

所以这将涉及更多的工作.)

通过混合方法可以得到一些改进,首先y通过连续分数展开找到与小分母的近似有理逼近y- 对于示例指数1929/15625 = [0;8,9,1,192]并且使用前四个部分商得到近似10/81 = 0.123456790123...[注意,10/81 = 1/8 - 1/648最短的部分和分解成纯馏分是收敛剂] - 然后将剩余物分解成纯馏分.

然而,通常该方法导致计算Ñ TH根用于大n,这也是缓慢和存储器密集型的,如果最后的结果的期望的精度高.

总而言之,它可能是更简单,更快地实现explog和使用

x^y = exp(y*log(x))
Run Code Online (Sandbox Code Playgroud)