我为bcmath
扩展程序编写了一个包装器,而关于bcpow()
的 bug #10116 特别令人讨厌 - 它会强制转换{{1} ($right_operand
)到(本机PHP,而不是任意长度)整数,所以当你尝试计算一个数字的平方根(或任何其他高于$exp
的根)时,你总是会结束使用1
代替正确的结果。
我开始搜索算法,这些算法可以让我计算一个数字的第n个根,而found this answer看起来很稳固,我实际上expanded the formula使用WolframAlpha并且我能够改进它&#39 ; s速度提高约5%,同时保持结果的准确性。
这是一个纯粹的PHP实现,模仿我的BCMath实现及其局限性:
1
上述seems to work great 除非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)
为$exp
,则其倒数为0.123456
,而8.10005
和pow()
的结果将略有不同({{3} }):
_pow()
= pow(2, 0.123456)
1.0893412745953
= _pow(2, 0.123456)
1.0905077326653
= _pow(2, 1 / 8)
= _pow(2, 0.125)
如何使用"手册"达到相同的准确度?指数计算?
答案 0 :(得分:5)
找到(正)数a
的n th 根的所用算法是用于求零的牛顿算法
f(x) = x^n - a.
只涉及以自然数作为指数的权力,因此很容易实现。
使用指数0 < y < 1
计算幂y
,其中1/n
不是具有整数n
的{{1}}形式,这更复杂。做模拟,解决
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 th 根,如果最终结果的期望精度很高,这也是缓慢且占用大量内存的。< / p>
总而言之,实施exp
和log
并使用
x^y = exp(y*log(x))