计算浮点幂(PHP/BCMath)


Calculating Floating Point Powers (PHP/BCMath)

我正在为 bcmath 扩展编写一个包装器,关于bcpow()的错误 #10116 特别烦人——它将$right_operand($exp(转换为(本机 PHP,而不是任意长度(整数,因此当您尝试计算数字的平方根(或任何其他高于 1 的根(时,您总是得到1而不是正确的结果。

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

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

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;
}

上述方法似乎效果很好,除非1 / fmod($exp, 1)不产生整数。例如,如果$exp0.123456,它的逆数将是8.10005的,pow()_pow()的结果会有点不同(demo(:

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

如何使用"手动"指数计算达到相同的精度水平?

用于查找(正(数a的第 n根的算法是用于查找零的牛顿算法

f(x) = x^n - a.

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

使用指数0 < y < 1计算幂,其中y不是整数1/n n的形式更为复杂。做模拟,解决

x^(1/y) - a == 0

将再次涉及计算具有非整数指数的幂,这正是我们试图解决的问题。

如果y = n/d是有理的,分母d小,则通过计算很容易解决问题

x^(n/d) = (x^n)^(1/d),

但是对于大多数有理0 < y < 1,分子和分母相当大,中间x^n会很大,因此计算会使用大量内存并且需要(相对(很长时间。(对于0.123456 = 1929/15625的例子指数,它不是太糟糕,但0.1234567会相当费力。

计算一般有理0 < y < 1的幂的一种方法是写

y = 1/a ± 1/b ± 1/c ± ... ± 1/q

用整数a < b < c < ... < q和乘/除各个x^(1/k).(每个有理0 < y < 1都有这样的表示,最短的这种表示通常不涉及很多项,例如

1929/15625 = 1/8 - 1/648 - 1/1265625;

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

1929/15625 = 1/9 + 1/82 + 1/6678 + 1/46501020 + 1/2210396922562500,

所以这将涉及更多的工作。

通过

混合这些方法可以进行一些改进,首先通过y的连续分数展开找到一个接近有理近的y小分母 - 例如指数1929/15625 = [0;8,9,1,192]并使用前四个偏商产生近似10/81 = 0.123456790123...[注意,10/81 = 1/8 - 1/648,最短分解为纯分数的部分和是收敛的] - 然后将余数分解为纯分数。

然而,一般来说,这种方法会导致计算大n的第n根,如果最终结果的预期精度很高,这也是缓慢和内存密集型的。

总而言之,实现explog和使用可能更简单、更快捷。

x^y = exp(y*log(x))