我正在编写一个bcpow()的bug #10116特别恼人--它将
我开始寻找算法,以便能够计算一个数字的第n个根,我发现了这个答案,看起来非常可靠,我实际上扩展了这个公式使用WolframAlpha,我能够提高它的速度约5%,同时保持结果的准确性。
这是一个纯PHP实现,模仿了我的BCMath实现及其限制:
$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)
不产生整数。例如,如果$exp
是0.123456
,它的倒数将会是8.10005
,pow()
和_pow()
的结果会有些不同(演示):
pow(2, 0.123456)
=1.0893412745953
_pow(2, 0.123456)
=1.0905077326653
_pow(2, 1 / 8)
=_pow(2, 0.125)
=1.0905077326653
如何使用“手动”指数计算实现相同级别的精度?
_pow
将小数部分“舍入”到最近的1/n
。您可以递归地使其工作。因此,在计算_pow(2,0.125)
之后,您可以计算_pow(2,0.125-123456)
等等。 - Jeffrey Saxexp
和log
吗?还是有其他原因导致a^b = exp(b*log(a))
不可行?Jeffrey建议的递归当然可以工作,但如果需要许多1/k
来表示指数,则其速度可能不令人满意。将指数写成有理数n/d
并计算(a^n)^(1/d)
是否可行,或者预计n
和d
太大了?也许值得研究的是用小分母(连分数展开)的有理数近似指数,并通过递归完成其余部分。 - Daniel Fischerbcmath
API相当糟糕,除了*/+-
之外,我们还有sqrt
和残缺不全的pow
:http://www.php.net/manual/en/ref.bc.php。我看到计算`(a^n)^(1/d)`的一个问题是`1/d`也可能是无理数。无论如何,我问这个问题主要是因为我很好奇——我怀疑我不需要在这么大的数字上使用无理指数。=) - Alix Axel