【问题标题】:PHP: How to raise number to (tiny) fractional exponent?PHP:如何将数字提高到(微小的)小数指数?
【发布时间】:2016-02-02 20:11:15
【问题描述】:

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

例如:

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

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

我可以使用其他函数来代替bcpow()吗?

【问题讨论】:

  • 请注意 $pow = 1/9223372036854775808

标签: php math bcmath


【解决方案1】:

您最好的选择可能是使用泰勒级数展开式。如您所述,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&lt;300 是无穷大的近似值...您可以更改它以满足您的性能需求。

有了$i=20,我得到了

1.00000000000000000010842021724855044340662275184110560868263421994092888869270293594926619547803962155136242752708629105688492780863293090291376157887898519458498571566021915144483905034693109606778068801680332504212458366799913406541920812216634834265692913062346724688397654924947370526356787052264726969653983148004800229537555582281617497990286595977830803702329470381960270717424849203303593850108090101578510305396615293917807977774686848422213799049363135722460179809890014584148659937665374616 P>

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

【讨论】:

  • 另外,我使用bcscale(500) 进行了尝试,得到了相同的结果。
  • 太完美了!谢谢你指点我泰勒系列。好东西。
  • wolfram alpha 最多同意前 395 个数字 wolframalpha.com/input/?i=e^%280.000000000000000000108420217248550443400745280086994171142578125%29
【解决方案2】:

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

因此,Kevin 使用泰勒多项式得到了正确的想法,但是当您直接从中导出算法时,您可能会遇到麻烦,主要是当您使用较大的 $ 截止值时,对于长输入字符串,您的代码会变慢i.

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

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

总体而言,所需时间随计算的多项式项数呈二次方增长。

那么……怎么办?

这里有一个提示:

每当您处理多项式,尤其是泰勒多项式时,请使用霍纳方法。

它转换为: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 的方法再次拯救了这一天!

其他一些想法:

1) Kevin 的代码可能会在 input="0" 时崩溃,具体取决于 bcmath 如何处理错误,因为代码在第一步 ($i=0) 尝试使用 bcpow(0,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 时才会出现这种情况。

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

为了达到让愿意使用 bcmath 的人满意的结果,您的 $i 需要比您的 $number 大得多。当您尝试计算诸如 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) 为您提供的浮点数可能无法满足您作为 bcmath 用户的需求。根据您的 bcscale 设置,您可能希望使用更准确的 e 值。

3) 谈论迭代次数:在大多数情况下,300 次将是多余的,而在其他一些情况下甚至可能还不够。采用 bcscale 和 $number 并计算所需迭代次数的算法会很好。 Alraedy 得到了一些涉及 log(n!) 的想法,但还没有具体的想法。

4) 要将此方法与任意基数一起使用,您可以使用 a^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 作为发展点) 使用霍纳的方法来大幅提高性能。 将结果变成一个 bc 操作循环。 在处理 x > 1 时也要使用 ln(x) = -ln(1/x) 来保证收敛。

【讨论】:

  • 如何估计bcmul(bc_exp(bcmul(bc_ln($base), "0.".$parts[1]), bcpow($base,$parts[0]);bcmulbclnbcpow 所需的规模,以便节省一些计算这些操作的时间?现在,使用 bcscale 作为 100 是次优的。
  • 您知道如何完成 3) 步骤吗?我对此很感兴趣:)
猜你喜欢
  • 1970-01-01
  • 2015-02-17
  • 2014-09-12
  • 2013-07-12
  • 2019-02-16
  • 2018-12-14
  • 2017-11-01
  • 2020-01-26
  • 2012-06-14
相关资源
最近更新 更多