【发布时间】:2012-05-18 09:11:24
【问题描述】:
我正在为bcmath 扩展编写一个包装器,而关于bcpow() 的bug #10116 特别烦人——它将$right_operand ($exp) 转换为(本机 PHP,不是任意长度)整数,因此当您尝试计算一个数字的平方根(或任何其他高于 1 的根)时,您总是以 1 而不是正确的结果结束。
我开始寻找可以让我计算数字的 n 次根的算法,我 found this answer 看起来很可靠,我实际上 expanded the formula 使用 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;
}
上述seems to work great 除非1 / fmod($exp, 1) 不产生整数。例如,如果$exp 是0.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
如何使用“手动”指数计算达到同样的准确度?
【问题讨论】:
-
它的工作方式与宣传的完全一样。
_pow将小数部分“四舍五入”到最接近的1/n。您可以递归地完成这项工作。所以在计算_pow(2, 0.125)之后,你计算_pow(2,0.125-123456)等等。 -
啊,现在我明白了。那么 bcmath 没有
exp和log还是有其他原因导致a^b = exp(b*log(a))不是一个选项? Jeffrey 建议的递归当然会起作用,但如果您需要许多1/k来表示指数,它的速度可能不会令人满意。将指数写为有理数n/d并计算(a^n)^(1/d)是一个选项,还是必须预计n和d太大?也许值得研究的是用一个分母较小的有理数(连续分数展开)来近似指数,然后用递归来完成其余的工作。 -
@JeffreySax:啊,我明白了......这很糟糕,但似乎仍然不起作用(codepad.org/eI4ykyQU)还是我错过了什么?
-
@DanielFischer:感谢您回复我! =) 好吧,
bcmathAPI 很差,除了*/+-我们还有sqrt和一个残废的pow:php.net/manual/en/ref.bc.php。我在计算(a^n)^(1/d)时看到的一个问题是1/d也可能是一个无理数。不管怎样,我问这个主要是因为我很好奇——我怀疑我需要对这么大的数字使用无理指数。 =) -
我认为我们可以放心地忽略无理数。我们可以用有理数任意地逼近它们。问题是这种近似的分子和分母可能很大。您能否指定要处理的输入类型以及结果的准确性?您需要的数字越少,您可以在近似值中使用的分子和分母就越小。
标签: php algorithm math pow bcmath