老问题,但人们可能仍然感兴趣。
所以凯文用泰勒多项式得到了正确的想法,但是当你从它直接推导出你的算法时,你可能会陷入困境,主要是当你使用较大的截止值$我。
这是为什么: 在每一步,我的意思是每一个新的$ i,代码调用bcfac($ i)。每次bcfac被称为执行$ i-1计算。而且,我一路高达299 ...这几乎是45000操作!不是你的quick'n'easy浮点操作,但慢BC字符串操作 - 如果你设置bcscale(100)你的bcmul必须处理多达10000对字符!
此外,bcpow也随着$ i的增加而减速。没有bcfac那么多,因为它可以有效地使用类似于平方和乘法的方法,但它仍然增加了一些东西。
总体而言,所需时间随着计算的多项式项的数量二次增长。
那么......该怎么办?
这里有一个技巧:
每当你处理多项式,尤其是泰勒多项式,使用霍纳方法。
它转换为:exp(x)= x^0/0! + x^1/1! + x^2/2! + x^3/3! + ...
...成:EXP(X)=(((...)* X/3 + 1)* X/2 + 1)* X/1 + 1
突然之间,你根本不需要任何权力或因子。
function bc_exp($number) {
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $number), 1);
return $result;
}
对于每一步,无论$ i是多少,这只需要3个bc操作。 以$ i = 299的起始值(以与kevin代码相同的精度计算exp),我们现在只需要897个bc操作,而不是45000. 即使使用30作为截止值而不是300,我们现在只需要87个bc操作,而其他代码仍然需要822个单独的因子。
Horner's Method再次拯救了这一天!
一些其他的想法:
1)凯文的代码将propably与输入崩溃= “0”,这取决于bcmath时如何处理错误,因为代码在第一步改掉bcpow(0,0)($ I = 0)。 2)较大的指数需要较长的多项式,因此需要更多的迭代,例如, bc_exp(300)会给出错误的答案,即使在$ i = 299的情况下,像bc_exp(3)这样的东西也可以正常工作。 每学期增加x^n/n!到结果,所以这个项必须在多项式开始收敛之前变小。现在比较两个连续的术语:
(x^(n+1)/(n+1)!)/(x^n/n!) = x/n
每个被加数比一个较大的前由x的因子/ N(我们通过霍纳方法中使用),因此,为了对于x ^(N + 1)/ (N + 1)!得到小的x/n也必须变小,这只是当n> x时的情况。
Inconclusio:只要迭代次数小于输入值,结果就会发散。只有当您添加步骤直到迭代次数大于输入次数时,算法才会开始慢慢收敛。
为了达到可以满足愿意使用bcmath的人的结果,您的$ i需要显着大于您的$数量。当你试图计算像e^346674567801
这样的解决方案是将输入分成整数部分和小数部分。 比整数部分使用bcpow和小数部分使用bc_exp,现在从开始收敛,因为小数部分小于1.最后,乘以结果。
e^x = e^(intpart+fracpart) = e^intpart * e^fracpart = bcpow(e,intpart) * bc_exp(fracpart)
你甚至可以直接落实到上面的代码:
function bc_exp2($number) {
$parts = explode (".", $number);
$fracpart = "0.".$parts[1];
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $fracpart), 1);
$result = bcmul(bcpow(exp(1), $parts[0]), $result);
return $result;
}
注意,EXP(1)赋予您propably不会满足您的需求为bcmath时用户一个浮点数。根据您的bcscale设置,您可能希望使用更准确的e值。3)谈论迭代次数:在大多数情况下,300将会过度杀伤,而在其他一些情况下,它可能还不够。一个算法,需要你的bcscale和$数字,并计算所需的迭代次数会很好。 Alraedy得到了一些涉及log(n!)的想法,但没有具体的。
4)要使用此方法的任意基地,您可以使用^ x = e ^(x * ln(a))。 您可能希望在使用bc_exp(而不是在bc_exp2中执行此操作)之前将x分成它的intpart和fracpart,以避免不必要的函数调用。
function bc_pow2($base,$exponent) {
$parts = explode (".", $exponent);
if ($parts[1] == 0){
$result = bcpow($base,$parts[0]);
else $result = bcmul(bc_exp(bcmul(bc_ln($base), "0.".$parts[1]), bcpow($base,$parts[0]);
return result;
}
现在我们只需要编程bc_ln。我们可以使用与上面相同的策略:
取自然对数函数的泰勒多项式。 (因为ln(0)没有定义,所以取1作为开发点) 使用Horner的方法来大幅度提高性能。 将结果转换为bc操作的循环。 当处理x> 1时,还要使用ln(x)= -ln(1/x)来保证收敛。
请注意,$ pow = 1/9223372036854775808 –