3

I need to compute the factorial of a number without using the multiplication operator. Because of this restriction, I directly tried to use repeated addition. It kind of works. However, my program is struggling to get the factorial of larger numbers. Is there a better way to solve the problem?

Here is my code:

void main(){
     unsigned long num = 0, ans = 1, temp = 1;

    printf("Enter a number: ");
    scanf("%lu", &num);

    while (temp <= num){
        int temp2 = 0, ctr = 0;
        while (ctr != temp){
            temp2 += ans;
            ctr ++;
        }
        ans = temp2;
        temp ++;
    }

    printf("%lu! is %lu\n", num, ans);
}
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
  • What do you call a large number? which value of `lu`? a `unsigned long int` is in general limited to `2^32-1` – Damien Jan 11 '21 at 10:12
  • You may want to use [The GNU MP Bignum Library](https://gmplib.org/). As a side effort, it will free you from using multiplication operator because you will have to use functions for multiplication with the library. – MikeCAT Jan 11 '21 at 10:13
  • 2
    @MikeCAT It is cheating a little bit maybe? :) – Damien Jan 11 '21 at 10:16
  • @Damien Even 20! is already inaccurate. – Kyle Angelo Gonzales Jan 11 '21 at 10:20
  • You may try using `unsigned long long int` but here, you will still be limited to `2^64-1`. What is the requirement? – Damien Jan 11 '21 at 10:22
  • Not only your program works fine but, according to my test, it doesn't show performance limits (as, I suspect, the code is probably optimized by the compiler using... multiplications). The only limit is the size of the output variable: with `unsigned long` the output is correct until 12!. Starting from 13!! it overflows. Using `unsigned long long`, as someone suggested, you increase the range (but first or later you'll find its limit too). – Roberto Caboni Jan 11 '21 at 10:51
  • 3
    `unsigned long long f[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000, 355687428096000, 6402373705728000, 121645100408832000, 2432902008176640000};` – pmg Jan 11 '21 at 11:45
  • @RobertoCaboni Perhaps it doesn't show performance limits because one of the multiplicands (the one being used to count the additions) is relatively small. If the multiplicands were swapped over in the addition loop, it would take a _lot_ longer to execute, unless the compiler does in fact optimize it to a multiplication. – Ian Abbott Jan 11 '21 at 17:06
  • @IanAbbott Yeah, you are right. I had in mind the generic "multiplication with addition" use case (with arbitrary big multiplicands) that would be very slow. The limit in the size of the output in this particular application doesn't give it the time to show its slowness. – Roberto Caboni Jan 11 '21 at 17:16
  • Here's a method that can get a factorial without using a single multiplication during the process. the method uses pre-computed squares and stored for future use by those who deals with factorials on a daily basis. https://math.stackexchange.com/questions/4711223/does-using-pre-computed-squares-speed-up-significantly-the-calculation-of-factor – user25406 Jun 02 '23 at 18:27

2 Answers2

5

You can implement a faster (than repeated addition) multiply function using bit shifts and addition to perform "long multiplication" in binary.

unsigned long long mul_ull(unsigned long long a, unsigned long long b)
{
    unsigned long long product = 0;
    unsigned int shift = 0;

    while (b)
    {
        if (b & 1)
        {
            product += a << shift;
        }
        shift++;
        b >>= 1;
    }
    return product;
}

EDIT: Alternative implementation of the above using single bit shifts and addition:

unsigned long long mul_ull(unsigned long long a, unsigned long long b)
{
    unsigned long long product = 0;

    while (b)
    {
        if (b & 1)
        {
            product += a;
        }
        a <<= 1;
        b >>= 1;
    }
    return product;
}

In practice, whether or not this is faster than repeated addition depends on any optimizations done by the compiler. An optimizing compiler could analyze the repeated addition and replace it with a multiplication. An optimizing compiler could also analyze the code of the mul_ull function above and replace it with a multiplication, but that may be harder for the optimizer to spot. So in practice, it is perfectly reasonable for the repeated addition algorithm to end up faster than the bit-shift and addition algorithm after optimization!

Also, the above implementations of the mul_ull functions will tend to perform better if the second parameter b is the smaller of the numbers being multiplied when the one of the numbers is much larger than the other (as is typical for a factorial calculation). The execution time is roughly proportional to the log of b (when b is non-zero) but also depends on the number of 1-bits in the binary value of b. So for the factorial calculation, put the old running product in the first parameter a and the new factor in the second parameter b.

A factorial function using the above multiplication function:

unsigned long long factorial(unsigned int n)
{
    unsigned long long fac = 1;
    unsigned int i;

    for (i = 2; i <= n; i++)
    {
        fac = mul_ull(fac, i);
    }
    return fac;
}

The above factorial function is likely to produce an incorrect result for n > 20 due to arithmetic overflow. 66 bits are required to represent 21! but unsigned long long is only required to be 64 bits wide (and that is typically the actual width for most implementations).

EDIT 2023-06-07

A slightly longer factorial() function with the same restrictions on the value of n can use less than half the number of multiplications. This method is based on that shown in https://scicomp.stackexchange.com/q/42510 after the edit of March 21, 2023.

unsigned long long factorial(unsigned int n)
{
    unsigned long long fac;
    unsigned int m;
    unsigned int i;

    if (n <= 1)
    {
        fac = 1;
    }
    else
    {
        if ((n & 1) == 0)
        {
            m = n;
            n -= 2;
        }
        else
        {
            m = n + n;
            n -= 3;
        }
        fac = m;
        while (n > 0)
        {
            m += n;
            fac = mul_ull(fac, m);
            n -= 2;
        }
    }
    return fac;
}

For example, it calculates 9! = 18·24·28·30 = 362880, 10! = 10·18·24·28·30 = 3628800.

Ian Abbott
  • 15,083
  • 19
  • 33
  • GCC also provides 128-bit values such as `__uint128_t`. See https://stackoverflow.com/questions/16088282/is-there-a-128-bit-integer-in-gcc – Andrew Henle Jan 11 '21 at 16:35
  • @AndrewHenle Well that would get you up to 34!. – Ian Abbott Jan 11 '21 at 16:46
  • Thank you so much! However, I don't really know how to use bit shifts and << operators. What references can I look towards to learn about it? @IanAbbott – Kyle Angelo Gonzales Jan 13 '21 at 08:44
  • Here's a method that can get a factorial without using a single multiplication during the process. the method uses pre-computed squares and stored for future use by those who deals with factorials on a daily basis. https://math.stackexchange.com/questions/4711223/does-using-pre-computed-squares-speed-up-significantly-the-calculation-of-factor – user25406 Jun 07 '23 at 14:52
  • 1
    @user25406 Neat, but pre-computing squares is possibly cheating. Still, the hint you provided allows the number of multiplications to be halved pretty easily. – Ian Abbott Jun 07 '23 at 15:52
  • @IanAbbott, by pre-computing the squares, I meant making a table for future use, not just for calculating a single factorial. That way, the work involved in getting the squares will be divided among the many factorials calculated by looking up the squares on a table. – user25406 Jun 07 '23 at 18:18
  • @user25406 If you are building a table, you might as well just build a table of factorials. It won't be very large unless you need to return the result as a bignum. – Ian Abbott Jun 08 '23 at 08:40
  • @IanAbbott, I don't know how tables of squares are built but I know that squares can be easily calculated by just adding odd numbers like $1+3=2^2$, $2^2+5=3^2$...so the table can be built with minimal effort whereas building a table of factorials requires much more work. – user25406 Jun 08 '23 at 12:28
  • 1
    @user25406 The numbers that need to be squared are quite sparse and get fairly large, so a linear array is no good. For example, when calculating 20! by that method manually, I gave up when I had to square 9 digit numbers on my 10 digit pocket calculator! – Ian Abbott Jun 08 '23 at 13:09
0

For large values of n, a big format is needed.
As you cannot use multiplications, it seems logical that you must implement it yourself.
In practice, as only additions are needed, it is not so difficult to implement, if we are not looking for a high efficiency.

A little difficulty anyway : you have to convert the input integer in an array of digits. As modulo is not allowed I guess, I implemented it with the help of snprintf function.

Result:

100! is 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Note: this result is provided about instantaneously.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NDIGITS     1000        // maximum number of digits

struct MyBig {
    int digits[NDIGITS + 2];        // "+2" to ease overflow control
    int degree;
};

void reset (struct MyBig *big) {
    big->degree = 0;
    for (int i = 0; i <= NDIGITS; ++i) big->digits[i] = 0;
}

void create_with_div (struct MyBig *big, int n) {  // not used here
    reset (big);
    while (n != 0) {
        big->digits[big->degree++] = n%10;
        n /= 10;
    }
    if (big->degree != 0) big->degree--;
}

void create (struct MyBig *big, int n) {
    const int ND = 21;
    char dig[ND];
    snprintf (dig, ND, "%d", n);
    int length = strlen (dig);
    
    reset (big);
    big->degree = length - 1;
    for (int i = 0; i < length; i++) {
        big->digits[i] = dig[length - 1 - i] - '0';
    }
}

void print (struct MyBig *big) {
    for (int i = big->degree; i >= 0; --i) {
        printf ("%d", big->digits[i]);
    }
}

void accumul (struct MyBig *a, struct MyBig *b) {
    int carry_out = 0;
    for (int i = 0; i <= b->degree; ++i) {
        int sum = carry_out + a->digits[i] + b->digits[i];
        if (sum >= 10) {
            carry_out = 1;
            a->digits[i] = sum - 10;
        } else {
            carry_out = 0;
            a->digits[i] = sum;
        }
    }
    int degree = b->degree;
    while (carry_out != 0) {
        degree++;
        int sum = carry_out + a->digits[degree];
        carry_out = sum/10;
        a->digits[degree] = sum % 10;
    }
    if (a->degree < degree) a->degree = degree;
    if (degree > NDIGITS) {
        printf ("OVERFLOW!!\n");
        exit (1);
    }
}

void copy (struct MyBig *a, struct MyBig *b) {
    reset (a);
    a->degree = b->degree;
    for (int i = 0; i <= a->degree; ++i) {
        a->digits[i] = b->digits[i];
    }
}

void fact_big (struct MyBig *ans, unsigned int num) {
    create (ans, 1);
    int temp = 1;
    while (temp <= num){
        int ctr = 0;
        struct MyBig temp2;
        reset (&temp2);
        while (ctr != temp){
            accumul (&temp2, ans);
            ctr ++;
        }
        copy (ans, &temp2);
        temp ++;
    }
    return;
}

unsigned long long int fact (unsigned int num) {
    unsigned long long int ans = 1;
    int temp = 1;
    while (temp <= num){
        int ctr = 0;
        unsigned long long int temp2 = 0;
        while (ctr != temp){
            temp2 += ans;
            ctr ++;
        }
        ans = temp2;
        temp ++;
    }
    return ans;
}

void main(){
    unsigned long long int ans;
    unsigned int num;
    
    printf("Enter a number: ");
    scanf("%u", &num);
    
    ans = fact (num);
    printf("%u! is %llu\n", num, ans);
    
    struct MyBig fact;
    fact_big (&fact, num);
    printf("%u! is ", num);
    print (&fact);
    printf ("\n");
}
Damien
  • 4,809
  • 4
  • 15
  • 20
  • Here's a method that can get a factorial without using a single multiplication during the process. the method uses pre-computed squares and stored for future use by those who deals with factorials on a daily basis https://math.stackexchange.com/questions/4711223/does-using-pre-computed-squares-speed-up-significantly-the-calculation-of-factor – user25406 Jun 07 '23 at 14:53