PHP:如何将数字提高到(微小)分数指数?

时间:2015-11-02 20:23:20

标签: php math bcmath

我正在使用bcmath在PHP中进行计算,并且需要通过小数指数来提升e。不幸的是,bcpow()只接受整数指数。指数通常比浮点数允许的精度更高,因此正常的算术函数不会削减它。

例如:

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

结果为"1",错误为“bc数学警告:指数中的非零刻度”。

我可以使用其他功能代替bcpow()吗?

2 个答案:

答案 0 :(得分: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的东西。

答案 1 :(得分:1)

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

所以凯文用泰勒多项式得到了正确的想法,但是当你从中直接推导出你的算法时,你可能遇到麻烦,主要是你的代码在使用大的截止值时为长输入字符串变慢岛

原因如下: 在每个步骤中,我指的是每个新的$ i,代码调用bcfac($ i)。每次调用bcfac时,它都会执行$ i-1计算。并且$ i一直到299 ...这几乎是45000次操作!不是你快速的简单浮点操作,而是缓慢的BC字符串操作 - 如果设置bcscale(100),你的bcmul必须处理多达10000对字符!

bcpow也随着$ i的增加而减速。不像bcfac那么多,因为它可以使用类似于square-and-multiply方法的东西,但它仍然增加了一些东西。

总的来说,所需时间随着计算的多项式项的数量而增加。

那么......该怎么办?

这是一个提示:

无论何时处理多项式,特别是泰勒多项式,都要使用Horner方法。

它转换为: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次操作。 起始值为$ i = 299(以与凯文的代码精度相同的方式计算exp),我们现在只需要897个bc操作,而超过45000个。 即使使用30作为截止而不是300,我们现在只需要87个bc操作,而其他代码仅需要822个因子。

霍纳的方法再次挽救了这一天!

其他一些想法:

1)Kevin的代码可能会因输入=&#34; 0&#34;而崩溃,具体取决于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(我们通过Horner方法使用),所以为了x ^(n + 1)/(n + 1)!为了得到小的x / n也必须变小,这只是在n> x。

的情况下

Inconclusio:只要迭代次数小于输入值,结果就会发散。只有当您添加步骤直到迭代次数大于输入时,算法才开始慢慢收敛。

为了达到可以满足愿意使用bcmath的人的结果,你的$ i需要比你的$ number大得多。当你试图计算像e ^ 346674567801这样的东西时,这是一个很大的问题

解决方案是将输入分为整数部分及其分数部分。 比在整数部分使用bcpow而在部分部分使用bc_exp,它现在从get-go收敛,因为小数部分小于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)为您提供了一个浮点数,它可以满足您作为bcmath用户的需求。根据您的bcscale设置,您可能希望使用更准确的e值。

3)谈论迭代次数:300在大多数情况下都是矫枉过正的,而在其他情况下甚至可能还不够。获取bcscale和$ number并计算所需迭代次数的算法会很好。 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。我们可以使用与上述相同的策略:

取自然对数函数的Taylor-polynomial。 (因为没有定义ln(0),所以取1作为开发点) 使用Horner的方法可以大幅提升性能。 将结果转换为bc操作循环。 当处理x> 1时,也使用ln(x)= -ln(1 / x)。 1,保证收敛。