【问题标题】:Calculating the nth factorial计算第 n 个阶乘
【发布时间】:2017-06-21 14:09:15
【问题描述】:

我正在编写一个实现以下表达式的函数 (1/n!)*(1!+2!+3!+...+n!)

函数传递了参数n,我必须将上述语句作为双精度返回,截断到小数点后第6位。我遇到的问题是阶乘值变得如此之大以至于它变为无穷大(对于n 的大值)。

这是我的代码:

public static double going(int n) {
   double factorial = 1.00;
   double result = 0.00, sum = 0.00;

   for(int i=1; i<n+1; i++){
     factorial *= i;
     sum += factorial;
   }
   //Truncate decimals to 6 places
   result = (1/factorial)*(sum);
   long truncate = (long)Math.pow(10,6);
   result = result * truncate;
   long value = (long) result;

   return (double) value / truncate;
}

现在,上面的代码适用于 n=5 或 n=113,但任何高于 n = 170 和我的 factorialsum 表达式变成无穷大。由于数字的指数增长,我的方法是否行不通?以及计算不会过多影响性能的非常大的数字将是一种解决方法(我相信 BigInteger 从查看类似问题来看相当慢)。

【问题讨论】:

标签: java double factorial truncation


【解决方案1】:

您无需评估单个阶乘即可解决此问题。

从计算上讲,您的公式简化为相当简单

1!/n! + 2!/n! + 3!/n! + ... + 1

除了第一项和最后一项之外,实际上有很多因素会抵消,这将有助于最终结果的精度,例如对于3! / n!,您只需将1 / 4 乘以1 / n。你不能做的事情是对阶乘进行求值和除法。

如果 15 位小数的精度是可以接受的(这似乎来自您的问题),那么您可以用浮点计算它,首先添加小项。在开发算法时,您会注意到这些术语是相关的,但要非常小心如何利用它,因为您可能会引入材料不精确性。 (如果我是你,我会认为这是第二步。)


这是一个原型实现。请注意,我首先将所有单个项累积在一个数组中,然后我首先从较小的项开始对它们进行总结。我认为从最后一项(1.0)开始并向后工作在计算上更准确,但这对于收敛速度如此之快的系列可能不是必需的。让我们彻底完成这项工作并分析结果。

private static double evaluate(int n){                        
    double terms[] = new double[n];
    double term = 1.0;
    terms[n - 1] = term;
    while (n > 1){            
        terms[n - 2] = term /= n;
        --n;
    }        
    double sum = 0.0;
    for (double t : terms){
        sum += t;
    }        
    return sum;        
}

您可以看到第一个术语变得微不足道的速度有多快。我认为你只需要几个术语来计算浮点double 的容差的结果。让我们设计一个算法在达到该点时停止:


最终版本。看起来这个数列收敛得很快,你不必担心先添加小项。所以你最终会得到绝对美丽

 private static double evaluate_fast(int n){        
    double sum = 1.0;
    double term = 1.0;
    while (n > 1){
        double old_sum = sum;
        sum += term /= n--;
        if (sum == old_sum){
            // precision exhausted for the type
            break;
        }
    }                
    return sum;        
}

如您所见,不需要BigDecimal &c,当然也不需要评估任何阶乘。

【讨论】:

  • 我还有可能遇到同样的问题吗?假设 n = 200,那么如果我尝试评估 170!/200!,170!由于编译器设置的限制,变为无穷大。
  • 你不评价170! / 200!通过计算两个阶乘。你通过计算 1.0 / 171 * 1.0 / 172 * ... * 1.0 / 200 来做到这一点。如果总和中的一项变得非常小,那么鉴于其中一项是 1,它不会影响最终结果。我只建议先添加小项,让它们有机会影响结果。
  • 好吧,这不是它变得很小的问题,而是关于如何实际评估表达式以使值不会太大以至于它们变成无穷大。如果170!被编译器视为无穷大,我需要以某种方式评估 170!/200!立即没有编译器将分子或分母视为无穷大,但我不知道如何解决这个问题。
  • 你太淘气了。不要评估阶乘。按照我的方式去做。很好,上面加糖。
  • 非常干净的解释和解决方案!我从中学到了很多。我需要学会用我的代码更有创意地思考,而不是尝试实现镜像逻辑表达式。如果可以的话,我会投票两次。
【解决方案2】:

你可以像这样使用 BigDecimal:

public static double going(int n) {
    BigDecimal factorial = BigDecimal.ONE;
    BigDecimal sum = BigDecimal.ZERO;

    BigDecimal result;

    for(int i=1; i<n+1; i++){
        factorial = factorial.multiply(new BigDecimal(i));
        sum = sum.add(factorial);
    }
    //Truncate decimals to 6 places
    result = sum.divide(factorial, 6, RoundingMode.HALF_EVEN);

    return result.doubleValue();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 2023-03-22
    • 2012-06-23
    • 1970-01-01
    • 1970-01-01
    • 2015-04-19
    相关资源
    最近更新 更多