2015-11-02 63 views
6

我正在做一个使用bcmath的PHP计算,需要用分数指数提升e。不幸的是,bcpow()只接受整数指数。指数的精度通常比浮点数要高,因此普通的算术函数不会削减它。PHP:如何将数字提高到(微小)分数指数?

例如:

$e = exp(1); 
$pow = "0.000000000000000000108420217248550443400745280086994171142578125"; 
$result = bcpow($e, $pow); 

结果是"1"与错误, “BC数学警告:非零规模指数”。

是否有另一个功能可以用来代替bcpow()

+0

请注意,$ pow = 1/9223372036854775808 –

回答

6

你最好的选择是使用泰勒级数展开。正如你所说,PHP的bcpow仅限于提高到整数指数。

所以你可以做的是滚动你自己的BC因子函数,并使用wiki页面实现指数函数的泰勒级数展开。

function bcfac($num) { 
    if ($num==0) return 1; 
    $result = '1'; 
    for (; $num > 0; $num--) 
     $result = bcmul($result,$num); 
    return $result; 
} 
$mysum = '0'; 
for ($i=0; $i<300; $i++) { 
    $mysum = bcadd($mysum, bcdiv(bcpow($pow,$i), bcfac($i))); 
} 
print $mysum; 

显然,$i<300是无穷大......你可以改变它,以满足您的性能需求的近似值。

随着$i=20,我

1.00000000000000000010842021724855044340662275184110560868263421994092888869270293594926619547803962155136242752708629105688492780863293090291376157887898519458498571566021915144483905034693109606778068801680332504212458366799913406541920812216634834265692913062346724688397654924947370526356787052264726969653983148004800229537555582281617497990286595977830803702329470381960270717424849203303593850108090101578510305396615293917807977774686848422213799049363135722460179809890014584148659937665374616

这是令人欣慰的,因为小的指数应该产生非常接近的东西1.0。

+0

另外,我尝试使用'bcscale(500)'并得到相同的结果。 – Kevin

+0

完美!感谢您指点我泰勒系列。好东西。 – jchamberlain

+0

wolfram alpha同意第一~395位数http://www.wolframalpha.com/input/?i=e^%280.000000000000000000108420217248550443400745280086994171142578125%29 – chiliNUT

1

老问题,但人们可能仍然感兴趣。

所以凯文用泰勒多项式得到了正确的想法,但是当你从它直接推导出你的算法时,你可能会陷入困境,主要是当你使用较大的截止值$我。

这是为什么: 在每一步,我的意思是每一个新的$ 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)来保证收敛。

+0

如何估计'bcmul','bcln'和' (bc_exp(bcmul(bc_ln($ base),“0。”。$ parts [1]),bcpow($ base,$ parts [0]);''这样我可以节省一些时间计算这些操作?现在,使用bcscale作为100是不理想的。 –

+0

您是否意识到如何完成3)步骤?我真的对此感兴趣:) –

相关问题