计算C中的大数因子

时间:2009-09-05 20:14:37

标签: c algorithm

在我的C代码中,我想计算1到100范围内数字的阶乘。对于小数字,该函数可以工作,但对于更大的数字,例如100!它返回不正确的结果。有什么办法在C中处理大数的阶乘?我正在使用的编译器是gcc v4.3.3。 我的代码如下:

#include <stdio.h>
#include <math.h>

double print_solution(int);

int main(void)
{
        int no_of_inputs,n ;

        int ctr = 1;

        scanf("%d",&no_of_inputs); //Read no of inputs

        do
        {
                scanf("%d",&n); //Read the input

                printf("%.0f\n",print_solution(n));

                ctr++;  

        }while(ctr <= no_of_inputs);


        return 0;       
}

double print_solution(int n)
{
        if(n == 0 || n == 1)
                return 1;
        else
                return n*print_solution(n-1);


}

16 个答案:

答案 0 :(得分:37)

没有标准C数据类型可以准确处理大到100的数字!如果您通过图书馆或自己完成arbitrary precision integer arithmetic,则唯一的选择。

如果这只是一些爱好项目,我建议你自己尝试一下。这是一种有趣的运动。如果这与工作相关,请使用预先存在的库。

您通常会获得的最大C数据类型是64位整数。 100!大约是10 157 ,它将500位的大部分精确存储为整数。

答案 1 :(得分:17)

100阶乘是巨大的,准确地说是93326215443944152681699238856266700490715968264381621468592963895217 59999322991560894146397615651828625369792082722375825118521091686400 00000000000000000000。

也许你应该使用像GMP这样的bignum库。它有很好的文档,非常一致的界面,速度,如果你在Linux上你的发行版可能有一个包(我想我的默认安装它)

答案 2 :(得分:14)

要大致计算大数的阶乘,你可以这样:

n! =  n * (n-1)!
so log(n!) = log(n) + log(n-1!)

现在您可以使用动态编程来计算log(n!)并计算
N! as(base)^(log-value)

答案 3 :(得分:9)

如果您不想使用bigint库,那么使用stdlib可以做的最好就是使用long double中的tgammal()math.h

long double fact(unsigned n)
{
    return tgammal(n + 1);
}

这将使你100!在x86上具有18位小数的精度(即80位long double)。

确切的实现也不是那么复杂:

#include <math.h>
#include <stdio.h>
#include <string.h>

void multd(char * s, size_t len, unsigned n)
{
    unsigned values[len];
    memset(values, 0, sizeof(unsigned) * len);
    for(size_t i = len; i--; )
    {
        unsigned x = values[i] + (s[i] - '0') * n;
        s[i] = '0' + x % 10;
        if(i) values[i - 1] += x / 10;
    }
}

void factd(char * s, size_t len, unsigned n)
{
    memset(s, '0', len - 1);
    s[len - 1] = '1';
    for(; n > 1; --n) multd(s, len, n);
}

int main(void)
{
    unsigned n = 100;
    size_t len = ceill(log10l(tgammal(n + 1)));
    char dstr[len + 1];
    dstr[len] = 0;
    factd(dstr, len, n);
    puts(dstr);
}

答案 4 :(得分:5)

每个人都在告诉你正确的答案,但还有几点。

  1. 您最初使用double来获得更宽范围的想法是行不通的,因为double不能精确地存储这些数据。它可以进行计算,但需要进行大量舍入。这就是bigint库存在的原因。

  2. 我知道这可能是教程或示例网站的一个例子,但是做无限递归会在某些时候咬你。您有一个基本上是迭代过程的递归解决方案。您将理解为什么当您尝试使用较大的值运行程序时,此站点的名称是什么(尝试10000)。

  3. 一种简单的迭代方法如下

      int answer, idx;
    
      for (answer = 1, idx = 1; idx <= no_of_inputs; idx++ ) {
        answer = answer * idx;
      }
      printf("Factorial of %3d =  %d\n", no_of_inputs, answer);
    

答案 5 :(得分:2)

这是我几年前解决谷歌谜语所做的,它使用GMP库http://gmplib.org/

#include <stdio.h>
#include "gmp.h"

void fact(mpz_t r,int n){
    unsigned int i;
    mpz_t temp;
    mpz_init(temp);
    mpz_set_ui(r,1);
    for(i=1;i<=n;i++){
        mpz_set_ui(temp,i);
        mpz_mul(r,r,temp);
    }
    mpz_clear(temp);
}
int main(void) {
    mpz_t r;
    mpz_init(r);
    fact(r,188315);
    /* fact(r,100); */
    gmp_printf("%Zd\n",r);
    mpz_clear(r);
    return(0);
}

gcc -lgmp -o fact fact.c

./事实

答案 6 :(得分:1)

您可以尝试使用“unsigned long long”类型,但这是内置类型可以获得的最大值。 我建议(正如cletus已经提到的那样)要么采用已知的大数字实现,要么自己编写一个。 “这是一个很好的运动”x 2。

答案 7 :(得分:1)

如果您只想使用标准数据类型而不需要确切的答案,那么计算n的对数!而不是n!本身。 n的对数!很容易适合double(除非n很大)。

答案 8 :(得分:1)

最多可容纳12个!适合32位整数。阶乘高达20!适合64位整数。在那之后,您在大多数计算机上用尽了所有比特。但是34!适合无符号的128位整数57!可以容纳256位整数,并且为98!适合无符号512位整数。要计算100!作为整数,至少需要525位。

bc脚本可计算阶乘(最多3​​5个!但是您可以轻松更改限制):

#!/usr/bin/bc -l

define f(n) {
   auto r, i
   r = 1
   for (i = 1; i <= n; i++)
   {
       r *= i;
       print "n = ", i, ", log2 = ", l(r)/l(2), ", n! = ", r, "\n"
   }
}

f(35)
quit

以及一些示例值:

# Key values
# n =  1, log2 =   0.00000000000000000000, n! = 1
# n =  2, log2 =   1.00000000000000000000, n! = 2
# n =  3, log2 =   2.58496250072115618147, n! = 6
# n =  4, log2 =   4.58496250072115618149, n! = 24
# n =  5, log2 =   6.90689059560851852938, n! = 120
# n =  6, log2 =   9.49185309632967471087, n! = 720
# n =  7, log2 =  12.29920801838727881834, n! = 5040
# n =  8, log2 =  15.29920801838727881836, n! = 40320
# n =  9, log2 =  18.46913301982959118130, n! = 362880
# n = 10, log2 =  21.79106111471695352921, n! = 3628800
# n = 11, log2 =  25.25049273335425078544, n! = 39916800
# n = 12, log2 =  28.83545523407540696694, n! = 479001600
# n = 13, log2 =  32.53589495221649912738, n! = 6227020800
# n = 14, log2 =  36.34324987427410323486, n! = 87178291200
# n = 15, log2 =  40.25014046988262176421, n! = 1307674368000
# n = 16, log2 =  44.25014046988262176426, n! = 20922789888000
# n = 17, log2 =  48.33760331113296117256, n! = 355687428096000
# n = 18, log2 =  52.50752831257527353551, n! = 6402373705728000
# n = 19, log2 =  56.75545582601885902935, n! = 121645100408832000
# n = 20, log2 =  61.07738392090622137726, n! = 2432902008176640000
# n = 21, log2 =  65.46970134368498166621, n! = 51090942171709440000
# ...
# n = 34, log2 = 127.79512061296909618950, n! = 295232799039604140847618609643520000000
# n = 35, log2 = 132.92440362991406264487, n! = 10333147966386144929666651337523200000000
# ...
# n = 57, log2 = 254.48541573017643505939
# n = 58, log2 = 260.34339672530400718017
# ...
# n = 98, log2 = 511.49178048020535201128
# n = 99, log2 = 518.12113710028496163045
# n = 100, log2 = 524.76499329005968632625

对于阶乘57!,58!,98!,99!,100!我已经省略了阶乘值,因为它分布在输出中的多行中,而且并不是那么重要。注意100!至少需要525位精度。

此代码在我的GitHub上的SOQ(堆栈溢出问题)存储库中,作为src/miscellany子目录中的文件factorial.bc提供。

您可以使用doublelong double来扩展值的范围,但会损失一些准确性。

答案 9 :(得分:0)

我猜那是因为你的int范围溢出,大约是。 20亿。如果使用unsigned int,最多可以获得40亿,但除此之外,您必须使用bigint library

答案 10 :(得分:0)

100! = 933262154439441526816992388562667004907159682643816214685929 6389521759999322991560894146397156518286253697920827223758251185210 916864000000000000000000000000

你不能用int或long来表示这个大数字。

答案 11 :(得分:0)

这肯定是由于溢出造成的。你需要一种表示大数字的方法(unsigned long long甚至不会覆盖25个!)。

答案 12 :(得分:0)

除了其他人的建议外,我建议您熟悉基本类型(int,long,long long,...)的存储限制,无论您实际使用的是什么计算机/平台。 (“如果有疑问,请打印更多!”)

之前的一张海报提到了80位精度限制,但这对于x86 CPU来说是特别的。

另一个人多次引用ISO C90,尽管C99是最新标准;即使许多编译器还没有完全实现C99,你可能会发现它们很可能至少支持long long,这应该对应于&gt; = 64位精度。

答案 13 :(得分:0)

在没有任何外部库的情况下计算大因子

这真是一个老问题。我看到大多数答案提示外部库近似结果,显示内存限制。但是,请稍微考虑一下 - 您不必总是在编程中使用integerdoubleunsigned long long进行数学运算!

我使用int[]来计算 Big Factorials 。这个小的 Java 代码可以(理论上)找出 任何数字的 -

public class BigFactorial {
    public static int[] calculateFactorial(int inputNumber) {
        int[] factorial = initializeFactorial(inputNumber);

        for(int i=inputNumber-1, j, k; i>0; i--){
            for(j=factorial.length-1, k=0; factorial[j] >= 0; j--){
                k += i*factorial[j];
                factorial[j] = k%10;
                k /= 10;
            }

            factorial[j] = k%10;
            k /= 10;
            factorial[j-1] = k;

            for(j=0; factorial[j]<1; j++){
                factorial[j] = -1;
            }
        }

        return factorial;
    }

    private static int[] initializeFactorial(int inputNumber){

        int digits = (int) Math.ceil(inputNumber*Math.log10(inputNumber/2))+2;
        int[] factorial = new int[digits+1];

        for(int i=0; i<factorial.length; i++){
            factorial[i] = -1;
        }

        for(int j=factorial.length-1, i=inputNumber; i>0; j--){
            factorial[j] = i%10;
            i /= 10;
        }

        return factorial;
    }

    public static void showOutput(int[] factorial){
        int i=0;
        while(factorial[i]<1){
            i++;
        }

        for(; i<factorial.length; i++){
            System.out.print(factorial[i]);
        }
    }

    ///test
    public static void main(String[] args) {
        int inputNumber = 5000;
        showOutput(calculateFactorial(inputNumber));
    }
}

答案 14 :(得分:0)

以下是您的问题的解决方案:

#include <stdio.h>
void factorial(int b){
    int temp = 0, r, size = 0, x;
    int arr[200] = {0};
    int l_b = b-1;
    while(b>0){
        r = b%10;
        arr[size++] = r;
        b = b/10;
    }
    while(l_b >= 2){
        int i=0;
        while(size>0){
         x = arr[i]*l_b+temp ;
         arr[i++] = x%10;
         temp = x/10;
         size--;
        }
       while(temp>0){
          arr[i++] = temp%10;
          temp = temp/10;
       }
      size = i;  --l_b;
    }
    for(int k=size-1;k>=0;k--)
        printf("%d",arr[k]);//ok i'm taking space here
    printf("\n");
}
int main(void) {
    // your code goes here
    int fact;

    scanf("%d\n",&fact);
    factorial(fact); 

    return 0;
}

答案 15 :(得分:-4)

我认为不要使用递归算法,它超级慢,即使它被缓存也会很慢。这只是你应该考虑的事情。

原因是当你调用fact(100)时你实际上没有运行100次,你实际上运行该函数 5050 次。哪个是坏的,如果它被缓存然后它可能是100次,但是,使用if语句运行函数调用然后运行循环仍然较慢。

double print_solution(int n)
{
    double rval = 1;
    unsigned int i;

    for( i = 1; i <= n; i++ ) {
        rval *= i;
    }

    return rval;

}

使用仲裁精度算法可以使它变得非常高,但是,您需要使用库来执行此操作,或者您可以创建自己的库,但这需要花费很多时间。