Thursday, October 29, 2009

Finding 100 factorial

Problem Statement:
Write a program that takes input between 1 and 100 and finds its factorial.

Solution
Often students find this problem as assignment or algorithm exercise. Some of them even get fed up and skip this problem because you have to make calculations on large numbers. We all know that C++ 32 bit unsigned integer can hold data up to 4294967295 and long long i.e. 64 bit integer can handle at max 264 - 1 = 18446744073709551615. What do you think what is the maximum value whose factorial can be saved in 64 bit int. You can't even store 25's factorial in it as 25! = 15511210043330985984000000 :)

In this post, I will explain how easy it is to actually implement this algorithm. In the second part of this post, we will optimize our algorithm and will reduce some calculations.

Let us start!

First thing we have to keep in mind that we actually need a function that has the capability to multiply fairly large numbers.

We multiply two numbers usually by multiplying each digit of first number with each digit of 2nd number. For example if I have to multiply 123 to 1234:
  1. I will actually find 3 x 1234 = 3702, 2 x 1234 = 2468 and 1 x 1234 = 1234.
  2. I also have to add some zeros on the right side as I move from right to left.
  3. And then I will add all these values i.e. 3702 + 24680 + 123400 = 151782.

Voila!!! you noted something, we have to implement another function that can add two large numbers. Don't worry, it is good that we have found it initially. Let us assume that we already have a function that can add two numbers 'string add(string str1, string str2)' Now the function mul is quite simple to write that will multiply two very large numbers.

string mul(string str1, string str2)
{
 string ans;
 for(int i = str1.size()-1; i>=0; i--)
 {
  string sum;
  int carry = 0; 
  //Step 1. above
  for(int j = str2.size()-1; j >=0; j--)
  {
   //multiply two digits and add carry
   int k = ((str1[i]-'0')*(str2[j]-'0') + carry); 
   sum = (char)((k % 10) + '0') + sum;
   carry = (k/10);
  }
  
  if(carry > 0)
  {
   sum = (char)(carry+'0') + sum;
  }
  //Step 2. above
  for(int j = 0; j < str1.size() - 1 - i; j++)
  {
   sum+="0";  
  }
  //Step 3. above
  ans = add(ans, sum);
 }
 return ans;
}

And here we go with the add function, I think it is pretty simple to understand
string add(string str1, string str2)
{
 int n1 = str1.size(), n2 = str2.size();
 for(int i = 0; i < n1 - n2; i++)  //Make 134 as 00134 if 2nd number is 12345
  str2 = "0" + str2;
 for(int i = 0; i < n2 - n1; i++)
  str1 = "0" + str1;
 string ans;
 int carry = 0;
 for(int i = str1.size() - 1; i >= 0; i--)
 {
  ans = (char)((str1[i] - '0' + str2[i] - '0' + carry) % 10 + '0') + ans;
  carry = (str1[i] - '0' + str2[i] - '0' + carry)/10;
 }
 if(carry > 0)
  ans = (char)(carry + '0') + ans;
 return ans;
}
Now what is more difficult for you. Here comes the Factorial function:
int N = 25;  //We need 25!
                string ans = 1;
  for(j = 1; j <= N; j++)
  {
   stringstream ss;
   ss << j;
   ans = mul(ss.str(),ans);
  }
Optimization Just check one important thing, even we have to multiply small numbers. we have to make large calculations. For example, if I want to multiply 10000 to 9999. I have to make 20 calculations but it is possible in just 1 calculation and the result can also be saved in an integer so we can optimize our algorithm in this way. We will not call mul( ) function, until and unless it exceeds the limit of an integer that is '2147483647' and then multiply it with the next number. 25! = 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 x 11 x 12 x 13 x 14 x 15 x 16 x 17 x 18 x 19 x 20 x 21 x 22 x 23 x 24 x 25 Now we can find product upto 12 i.e. '479001600' and from 13 to 19 i.e. '253955520' and then call mul( ) function on these two strings.

I am sure, it will save a lot of calculations.

5 comments:

  1. Nice blog this really can be helpful for a developer.
    online printing company

    ReplyDelete
  2. thank a lot sir.

    I am trying to find a faster way. May be by using 'long long' data type or some divide and conquer

    ReplyDelete
  3. Simple but interesting problem.
    Large Numbers are creating mess in factorial calculation.

    Another solution can be if we increase the base then it would decrease digits. Lets use base 65536!

    it is worthless if we wont use primitive multiplication tools such as * operator. So one has to devise a good algorithm to manipulate the increased base while using primitive tools.

    Following code demonstrates the above specified idea:
    #include
    #include
    #define ARRAY_SIZE 200
    #define DIGITS 10
    /* Global variables */
    char array1[ARRAY_SIZE]={0};
    char array2[ARRAY_SIZE]={0};
    char array_result[ARRAY_SIZE + 1]={0};
    bool overflow_occured = false;
    unsigned short int Digit[DIGITS]={0};
    char *Powers[DIGITS]={"22300745198530623141595718272648361505980416","340282366920938463463374607431768211456","5192296858534827628530496329220096","79228162514264337593543950336","1208925819614629174706176","18446744073709551616","281474976710656","4294967296","65536","1"};

    void copy_str(char *ptr, char *number,int size)
    {
    int i;
    int j= size-1;
    for(i=ARRAY_SIZE-1; i>=0;i--)
    {
    ptr[i] = number[j];
    j--;
    if(j<0)
    {
    i--;
    break;
    }
    }
    while(i>=0)
    {
    ptr[i]=48;
    i--;
    }
    }
    void display_result_str()
    {
    int i;
    bool leading = true;
    for(i = 0; i< ARRAY_SIZE+1;i++)
    {
    if((leading == true ) && (array_result[i] == 48 ))
    continue;
    else
    {
    leading = false;
    printf("%c",array_result[i]);
    }
    }
    }

    void add_str()
    {
    int i;
    int carry = 0;
    for(i=ARRAY_SIZE-1;i>=0;i--)
    {
    array_result[i+1]=(((array1[i]-48) + (array2[i]-48) + carry)%10) + 48;
    carry = (((array1[i]-48) + (array2[i]-48) + carry)/10);
    }

    if(carry == 1)
    array_result[0] = 49;
    else
    array_result[0] = 48;
    }
    void multiply_str(int number,char*p)
    {
    /* assumes the second number is in string p*/
    int i;
    copy_str(array1,"0",1);
    copy_str(array2,p,strlen(p));
    for(i=0;i=0;i--)
    {
    temp=Digit[i];
    Digit[i]=(temp*number + carry)%65536;
    carry = (temp*number)/65536;
    }
    if(carry == 1)
    overflow_occured = true;

    }
    void factorial(short int number)
    {
    int i;
    char result[ARRAY_SIZE]={0};
    char temp[ARRAY_SIZE]={0};
    if(sizeof(int) != 4 || sizeof(short)!= 2)
    {
    printf("Cannot calculate factorial. program assumption failed!");
    return;
    }
    /* intialize the number to 1*/
    /* start computing factorial*/
    Digit[DIGITS-1] = 1;
    for(i=2;i<=number;i++)
    {
    multiply(i);
    if(overflow_occured)
    {
    printf("value too large!");
    return;
    }
    }

    copy_str(result,"0",1);
    /* convert result to decimal form */
    for(i=DIGITS-1;i>=0;i--)
    {
    if(Digit[i]!=0)
    {
    multiply_str(Digit[i],Powers[i]);
    copy_str(array1,array_result,ARRAY_SIZE+1);
    copy_str(array2,result,ARRAY_SIZE);
    add_str();
    copy_str(result,array_result,ARRAY_SIZE+1);
    }
    }
    printf("The factorial is ");
    display_result_str();
    printf("\n");
    }
    void main()
    {
    factorial(50);
    }


    Thank you Zeeshan Bhai for indicating a good problem.

    Umar Waqas.
    Alumni F05
    PUCIT.

    ReplyDelete