【问题标题】:Cannot calculate factorials bigger than 20! ! How to do so?不能计算大于 20 的阶乘! !怎么做?
【发布时间】:2019-03-26 23:14:07
【问题描述】:

我使用无符号长整数格式来计算大阶乘。但是我的代码在某些时候失败了你能看看吗?实际上,它是指数函数泰勒展开的较大代码的一部分,但此时该部分无关紧要。我将不胜感激任何建议。

谢谢

#include <stdio.h>
#include <math.h>
//We need to write a factorial function beforehand, since we
//have factorial in the denominators.
//Remembering that factorials are defined for integers; it is
//possible to define factorials of non-integer numbers using
//Gamma Function but we will omit that.
//We first declare the factorial function as follows:
unsigned long long factorial (int);
//Long long integer format only allows numbers in the order of 10^18 so 
//we shall use the sign bit in order to increase our range.
//Now we define it,
unsigned long long
factorial(int n)
{
//Here s is the free parameter which is increased by one in each step and
//pro is the initial product and by setting pro to be 0 we also cover the
//case of zero factorial.
    int s = 1;
    unsigned long long pro = 1;
    if (n < 0)
        printf("Factorial is not defined for a negative number \n");
    else {
    while (n >= s) { 
    printf("%d \n", s);
    pro *= s;
    s++;
    printf("%llu \n", pro);
    }
    return pro;
    }
}

int main ()
{
    int x[12] = { 1, 5, 10, 15, 20, 100, -1, -5, -10, -20, -50, -100};
//Here an array named "calc" is defined to store 
//the values of x.
unsigned long long  k = factorial(25);
printf("%llu \n", k);

//int k;
////The upper index controls the accuracy of the Taylor Series, so
////it is suitable to make it an adjustable parameter. 
//int p = 500;
//for ( k = 0; k < p; k++);

}

【问题讨论】:

标签: c gcc factorial


【解决方案1】:

我写的一个简单的算法。但它是在Java .. 您可以在大约 15 分钟内计算出 1000 的阶乘。

这个算法适用于我们在小学学到的基本公式。

/* FOR BEST RESULT DON'T CHANGE THE CODE UNTIL YOU KNOW WHAT YOU'RE DOING */
    public String factorial(int number){
        if(number == 0) return "1";
        String result = "1";
        for(int i = 0; i < number; i++){
            result = *longNumberMultiplyingAlgorithm*(result, "" + (i + 1));
        }
        return result;  
    }

    public String longNumberMultiplyingAlgorithm(String number1, String number2){
            int maxLength = Math.max(number1.length(), number2.length());
            int a = 0;
            String[] numbers = new String[maxLength];

            if(number2.length() > number1.length()){
                String t = number1;
                number1 = number2;
                number2 = t;
            }       

            for(int i = 0; i < number1.length(); i++){
                numbers[i] = "";
                a = 0;
                for(int j = 0; j < number2.length(); j++){
                    int result = Integer.parseInt(String.valueOf(number1.charAt(number1.length() - i - 1))) * Integer.parseInt(String.valueOf(number2.charAt(number2.length() - j - 1)));
                    if(result + a < 10){
                        numbers[i] = (result + a) + "" + numbers[i];
                        a = 0;
                    }else{
                        result += a;
                        a = (int)((result + 0.0) / 10);
                        result -= a * 10;
                        numbers[i] = result + "" + numbers[i];
                    }
                }
                if(a != 0){
                    numbers[i] = a + "" + numbers[i];
                }
                for(int k = 0; k < i; k++){
                    numbers[i] += "0";
                }
            }
            return longNumberAdditionAlgorithm(numbers);
        }

    private String longNumberAdditionAlgorithm(String[] numbers) {
            String final_number = "0";
            for(int l = 0; l < numbers.length; l++){
                int maxLength = Math.max(final_number.length(), numbers[l].length());
                String number = "";
                int[] n = new int[maxLength];
                int a = 0;
                for(int i = 0; i < n.length; i++){
                    int result = 0;
                    if(i >= final_number.length()){
                        result = Integer.parseInt(String.valueOf(numbers[l].charAt(numbers[l].length() - i - 1)));
                    }else
                    if(i >= numbers[l].length()){
                        result = Integer.parseInt(String.valueOf(final_number.charAt(final_number.length() - i - 1)));
                    }else{
                        result = Integer.parseInt(String.valueOf(final_number.charAt(final_number.length() - i - 1))) + Integer.parseInt(String.valueOf(numbers[l].charAt(numbers[l].length() - i - 1)));
                    }
                    if(result + a < 10){
                        number = (result + a) + "" + number;
                        a = 0;
                    }else{
                        result -= 10;
                        number = (result + a) + "" + number;
                        a = 1;
                    }
                }
                if(a == 1){
                    number = a + "" + number;
                }
                final_number = number;
            }
            return final_number;
        }

【讨论】:

    【解决方案2】:

    确实:

    2^64 = 18446744073709551616
    21!  = 51090942171709440000
    20!  =  2432902008176640000
    

    顺便说一句,要计算系列的结果(例如 Taylor),您不应该单独计算每个项;这肯定会给您带来诸如此类的问题。相反,请尝试通过重用前一项来计算每个项。

    例如,cos 的泰勒级数需要以下项的总和:

    (-1)^i * (x^(2*i)) / (2i)!
    

    很容易看出,每一项都可以很容易地从前一项计算出来:

    newterm = - oldterm * x^2 / ((2i+1)*(2i+2))
    

    所以,我相信您不需要为您正在尝试做的事情计算大阶乘。另一方面,如果需要,则必须使用一个库来处理大数字,例如 gmp

    【讨论】:

    • 我认为这就是我要如何超越它的确切障碍?
    • 另一方面,在链接所有这些 FP 计算时,必须注意算法的数值稳定性。
    【解决方案3】:

    你的整数类型溢出了。 unsigned long long 对您来说可能是 64 位长。

    • 20!是 0x21c3_677c_82b4_0000,适合。
    • 21!是 0x2_c507_7d36_b8c4_0000,不适合。

    您可以查看GMP 之类的库,它们支持任意大的整数。


    扩展 GMP 注释。下面是一些使用 GMP 计算阶乘的代码:

    void factorial(unsigned long long n, mpz_t result) {
        mpz_set_ui(result, 1);
    
        while (n > 1) {
            mpz_mul_ui(result, result, n);
            n = n-1;
        }
    }
    
    int main() {
        mpz_t fact;
        mpz_init(fact);
    
        factorial(100, fact);
    
        char *as_str = mpz_get_str(NULL, 16, fact);
        printf("%s\n", as_str);
    
        mpz_clear(fact);
        free(as_str);
    }
    

    这将计算factorial(100),并导致:

    0x1b30964ec395dc24069528d54bbda40d16e966ef9a70eb21b5b2943a321cdf10391745570cca9420c6ecb3b72ed2ee8b02ea2735c61a000000000000000000000000
    

    只是为了好玩,这里是 C++ 版本。构造函数、析构函数和运算符重载往往使这些东西的 C++ 版本看起来更干净一些。结果和以前一样。

    #include <gmpxx.h>
    #include <iostream>
    
    int main() {
        mpz_class fact = 1;
    
        for (int i=2; i<=100; ++i)
            fact *= i;
    
        std::cout << "0x" << fact.get_str(16) << "\n";
    }
    

    【讨论】:

    • 具体来说,21!需要 66 位。
    • @Vesnog:是的。 mpz_get_str 的第二个参数是表示数字的基数。您可以在其中传递从 2 到 16 的任何内容。
    【解决方案4】:

    unsigned long long 的范围通常是 02^64 - 1 (18,446,744,073,709,551,615)。 21!超出这个范围。

    【讨论】:

    • 是的,我猜这就是问题所在,所以这是通用限制,有点?
    • 是的。它是 C99 标准定义的。
    • @hacks 请注意:0 到 2^64 - 1 是unsigned long long最小值范围。
    • @chux;那么最大范围是多少呢?
    • @hacks 对于 maximum 范围没有 C 规范。一个平台可以使用一个 16 字节的unsigned long long,范围为 0 到 2^128 - 1。如果确实需要一个 64 位数字,C 提供了 uint64_t 类型。
    【解决方案5】:

    long long 只是这么大,因此只能表示这么大的数字。如果您需要更大整数的精确表示,则需要使用其他东西(一些 3-rd 方库或您自己制作的一些数据类型);如果你不需要它是精确的,那么你可以使用双精度。

    【讨论】:

    • 我从您的评论中推断出,由于我们没有计算 21 的称为 long long long 整数的东西!不求助于外部库是不可能的,所以我不知道。我对吗?顺便说一句,您如何制作数据类型??
    • 我不知道有什么编译器支持long long long。至于图书馆,你可以试试这个:gmplib.org
    【解决方案6】:

    factorial(25) 应该给出结果 18,446,744,073,709,551,615,它大于 unsigned long long Data Type Ranges 的范围

    【讨论】:

      【解决方案7】:

      unsigned long long 的限制是 18446744073709551615,或大约 1.8e+19。 20!大约是 2.4e+18,所以在范围内,但是 21!大约是 5.1e+19,超过了 unsigned long long 的最大大小。

      您可能会觉得这很有帮助:Are there types bigger than long long int in C++?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-29
        • 1970-01-01
        • 2014-11-22
        • 2010-11-25
        • 2015-04-19
        • 2021-08-11
        相关资源
        最近更新 更多