5

Sorry I feel stupid asking this and am prepared to lose half of my points asking this but why does this algorithm not work? It works up to a point. After the number 13 the factorials are a little off. For instance the numbers do not entirely match in the hundreds thousands place and onward.

#include <stdio.h>

float factorial(unsigned int i) {

    if (i <= 1) {
        return 1;
    }
    return i * factorial(i - 1);
}

int  main() {
    int i = 13;
    printf("Factorial of %d is %f\n", i, factorial(i));
    return 0;
}

Here's the output:

Factorial of 13 is 6227020800.000000

Here is an example of inaccurate output:

Factorial of 14 is 87178289152.000000

The output for the number 14 should actually be this (from mathisfun.com)

14        87,178,291,200

I changed the return type to float to obtain more accurate output but I obtained this code for the most part from here: https://www.tutorialspoint.com/cprogramming/c_recursion.htm

EDIT: If I change to the return type to double the output is accurate up to 21.I am using the %Lf string formatter for the output in the printf function.

user3808269
  • 1,321
  • 3
  • 21
  • 40
  • 10
    "I changed the return type to float to obtain more accurate output"... Changing the return type to `float` to "obtain more accurate output" is one of the most misguided things one can do in this case. What made you believe it will lead to "more accurate output"? – AnT stands with Russia Jan 06 '17 at 17:03
  • 2
    You may want to implement your own `BigNumber` class or use a 3rd party library if you're going to start computing factorials. – AndyG Jan 06 '17 at 17:03
  • 2
    ... or even consider if a factorial is necessary. Some series have a factorial in the terms, but can be summed without computing a factorial when the term can be derived from the previous term (e.g. Taylor series). – Weather Vane Jan 06 '17 at 17:05
  • You could simplify your code a bit: `unsigned long long factorial(const unsigned i) { if (!i) return 1; return i * factorial(i - 1); }` – ForceBru Jan 06 '17 at 17:08
  • @ForceBru: How is this a "simplification"? Using `if (!i)` on a varable that has numerical (not boolean) semantics is ugly and unreadable. No, the OP's `if (i <= 1)` is how it should be done. – AnT stands with Russia Jan 06 '17 at 17:37
  • @AnT Well, changing the return type to float made the output more accurate up to a point. – user3808269 Jan 06 '17 at 17:38
  • @WeatherVane I am just studying this to learn. This has no practical purpose other than programming is a well paying in demand occupation. – user3808269 Jan 06 '17 at 17:39
  • @ForceBru I tried this... Thank-you for the feedback. It ran even though it looked strange to have two return types but the output was not accurate. :/ I tested with the number 15 and the output according to the program was: 2004310016 which is not accurate. – user3808269 Jan 06 '17 at 17:41
  • @user3870315, no, the output is `1307674368000`, which _is_ accurate. – ForceBru Jan 06 '17 at 17:43
  • @ForceBru could you please provide all your code? I used your algorithm but still received the same inaccurate result. :/ – user3808269 Jan 06 '17 at 17:48
  • @user3870315, I just copied the function from my comment and called it: `factorial(15)`, which resulted in correct output. Also, integral types cannot be accurate or inaccurate. For example, 31415926 is _exactly_ equal to 31415926, but 31415927 is a _different_ exact number. This term can only be applied to floating point numbers, as far as I'm concerned. – ForceBru Jan 06 '17 at 17:53
  • @user3870315: "More accurate"? By "more accurate" you apparently mean "still inaccurate, but better looking". If that's what you want, then by all means use `float`. But don't complain then that the result is not spot-on. – AnT stands with Russia Jan 06 '17 at 18:37
  • Note that 32-bit integers can only store up to 12! (479001600); 13! (6227020800) is too big. Similarly, 64-bit integers can store up to 20! (2432902008176640000); 21! (51090942171709440000) is too big. Using `float` typically gives you 6-7 significant decimal digits; using `double` typically gives you 16-17 significant decimal digits; the range that can be stored is larger, though. You still rapidly run out of range, though: 170! ≈ 7.257E+306; 171! is too big to fit into an IEEE-754 64-bit `double`. Even `long double` only manages 299! ≈ 1.020E+612 and 300! is too big (testing on a Mac). – Jonathan Leffler Jan 06 '17 at 18:48

5 Answers5

12

Simple. float cannot accurately store integers above 16777216 without loss of precision.

int is better than float. But try long long so you can properly store 19 digits.

Paul Stelian
  • 1,381
  • 9
  • 27
  • 2
    `unsigned long long` can store even greater numbers. – ForceBru Jan 06 '17 at 17:06
  • 1
    @ForceBru But only a tiny bit greater. – melpomene Jan 06 '17 at 17:06
  • 3
    I don't think that's quite right... assuming an IEEE-754 float, integers up to 16777216 (2^24) have exact representations. – Sneftel Jan 06 '17 at 17:11
  • @Sneftel Though the precision can just get lost in each multiplication, so even if integer is expected, it won't be one.. – Eugene Sh. Jan 06 '17 at 17:15
  • 2
    @EugeneSh. No, as long as the output of a multiplication can be exactly represented, no precision will be lost. – Sneftel Jan 06 '17 at 17:16
  • @Sneftel I think the floating point multiplication is implementation defined. – Eugene Sh. Jan 06 '17 at 17:17
  • 2
    @EugeneSh. Although it is possible in theory for a C implementation not to implement IEEE 754, floating point multiplication without correct rounding would be unheard-of. – Sneftel Jan 06 '17 at 17:20
  • @Sneftel I don't think the arithmetic operations algorithms are part of IEEE 754 standard. As I can see there is only the high-level description of the operations are provided. Moreover it can rely on hardware-implemented FPU, which can do weird things. But of course, I agree that for small integers it is not the case most of the time and better so. – Eugene Sh. Jan 06 '17 at 17:28
  • 1
    @EugeneSh. Of course arithmetic operations are part of IEEE 754, and hardware implementations are required to follow the standard. The basic wording is, results are exact, then correctly rounded by the supported/selected rounding mode. Which, for all rounding modes specified in 754, means that exactly representable results do not entail a loss of precision. – Sneftel Jan 06 '17 at 17:34
  • @Sneftel Do you have a reference (I believe you, but would like to have the reference for the future :) )? – Eugene Sh. Jan 06 '17 at 17:36
  • @EugeneSh. Well... IEEE-754. :) (Copyrighted and fee-required, but cough cough google.) In my copy, chapter 5 covers operations, and refers to rounding modes. My go-to reference for floating point precision, though, is Higham's "Accuracy and Stability in Numerical Algorithms". – Sneftel Jan 06 '17 at 17:42
  • I tried changing the return type to `long long` and `unsigned long long` but still this did not produce the correct answer. – user3808269 Jan 06 '17 at 17:54
  • 1
    "Simple. float cannot accurately store integers above 2097152 without loss of precision." is factual incorrect. `float` can store many numbers exactly above 2097152. Typical `float` can not store **all** integers larger than a certain value, such as `16777216.0f`. – chux - Reinstate Monica Jan 06 '17 at 17:58
  • I changed the return type to double and now the answer is accurate up to 15. – user3808269 Jan 06 '17 at 17:59
  • Changing to the double data type for the return value works up to 22. – user3808269 Jan 06 '17 at 18:03
  • I have corrected the maximum value for float. But the point is I actually had to do at school an implementation of big numbers and I had to calculate 50!. In C. – Paul Stelian Jan 06 '17 at 18:12
3

float can represent a wider range of numbers than int, but it cannot represent all the values within that range - as you approach the edge of the range (i.e., as the magnitudes of the values increase), the gap between representable values gets wider.

For example, if you cannot represent values between 0.123 and 0.124, then you also cannot represent values between 123.0 and 124.0, or 1230.0 and 1240.0, or 12300.0 and 12400.0, etc. (of course, IEEE-754 single-precision float gives you a bit more precision than that).

Having said that, float should be able to represent all integer values up to 224 exactly, so I'm going to bet the issue is in the printf call - float parameters are "promoted" to double, so there's a representation change involved, and that may account for the lost precision.

Try changing the return type of factorial to double and see if that doesn't help.

<gratuitous rant>

Every time I see a recursive factorial function I want to scream. Recursion in this particular case offers no improvement in either code clarity or performance over an iterative solution:

double fac( int x )
{
  double result = 1.0;
  while ( x )
  {
    result *= x--;
  }
  return result;
}

and can in fact result in worse performance due to the overhead of so many function calls.

Yes, the definition of a factorial is recursive, but the implementation of a factorial function doesn't have to be. Same for Fibonacci sequences. There's even a closed form solution for Fibonacci numbers

Fn = ((1 + √5)n - (1 - √5)n) / (2n * √5)

that doesn't require any looping in the first place.

Recursion's great for algorithms that partition their data into relatively few, equal-sized subsets (Quicksort, tree traversals, etc.). For something like this, where the partitioning is N-1 subsets of 1 element? Not so much.

</gratuitous rant>

John Bode
  • 119,563
  • 19
  • 122
  • 198
3

OP is encountering the precision limits of float. For typical float, whole number values above 16777216.0f are not all exactly representable. Some factorial results above this point are exactly representable.

Let us try this with different types.
At 11!, the float results exceeds 16777216.0f and is exactly correct.
At 14!, the float result is imprecise because of limited precision.
At 23!, the double result is imprecise because of limited precision.

At 22!, the answer exceeds my uintmax_t range. (64-bit)
At 35!, the answer exceeds my float range.
At 171!, the answer exceeds my double range.

A string representation is accurate endlessly until it reaches buffer limitations.

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

uintmax_t factorial_uintmax(unsigned int i) {
  if (i <= 1) {
    return 1;
  }
  return i * factorial_uintmax(i - 1);
}

float factorial_float(unsigned int i) {
  if (i <= 1) {
    return 1;
  }
  return i * factorial_float(i - 1);
}

double factorial_double(unsigned int i) {
  if (i <= 1) {
    return 1;
  }
  return i * factorial_double(i - 1);
}

char * string_mult(char *y, unsigned base, unsigned x) {
  size_t len = strlen(y);
  unsigned acc = 0;
  size_t i = len;
  while (i > 0) {
    i--;
    acc += (y[i] - '0') * x;
    y[i] = acc % base + '0';
    acc /= base;
  }
  while (acc) {
    memmove(&y[1], &y[0], ++len);
    y[0] = acc % base + '0';
    acc /= base;
  }
  return y;
}

char *factorial_string(char *dest, unsigned int i) {
  strcpy(dest, "1");
  for (unsigned m = 2; m <= i; m++) {
    string_mult(dest, 10, m);
  }
  return dest;
}

void factorial_test(unsigned int i) {
  uintmax_t u = factorial_uintmax(i);
  float f = factorial_float(i);
  double d = factorial_double(i);
  char s[2000];
  factorial_string(s, i);
  printf("factorial of %3d is uintmax_t: %ju\n", i, u);
  printf("                    float:     %.0f %s\n", f, "*" + (1.0 * f == u));
  printf("                    double:    %.0f %s\n", d, "*" + (d == u));
  printf("                    string:    %s\n", s);
}

int main(void) {
  for (unsigned i = 11; i < 172; i++)
    factorial_test(i);
  return 0;
}

Output

factorial of  11 is uintmax_t: 39916800
                    float:     39916800 
                    double:    39916800 
                    string:    39916800
factorial of  12 is uintmax_t: 479001600
                    float:     479001600 
                    double:    479001600 
                    string:    479001600
factorial of  13 is uintmax_t: 6227020800
                    float:     6227020800 
                    double:    6227020800 
                    string:    6227020800
factorial of  14 is uintmax_t: 87178291200
                    float:     87178289152 *
                    double:    87178291200 
                    string:    87178291200
factorial of  20 is uintmax_t: 2432902008176640000
                    float:     2432902023163674624 *
                    double:    2432902008176640000 
                    string:    2432902008176640000
factorial of  21 is uintmax_t: 14197454024290336768
                    float:     51090940837169725440 *
                    double:    51090942171709440000 *
                    string:    51090942171709440000
factorial of  22 is uintmax_t: 17196083355034583040
                    float:     1124000724806013026304 *
                    double:    1124000727777607680000 *
                    string:    1124000727777607680000
factorial of  23 is uintmax_t: 8128291617894825984
                    float:     25852017444594485559296 *
                    double:    25852016738884978212864 *
                    string:    25852016738884976640000
factorial of  34 is uintmax_t: 4926277576697053184
                    float:     295232822996533287161359432338880069632 *
                    double:    295232799039604119555149671006000381952 *
                    string:    295232799039604140847618609643520000000
factorial of  35 is uintmax_t: 6399018521010896896
                    float:     inf *
                    double:    10333147966386144222209170348167175077888 *
                    string:    10333147966386144929666651337523200000000
factorial of 170 is uintmax_t: 0
                    float:     inf *
                    double:    72574156153079940453996357155895914678961840000000... *
                    string:    72574156153079989673967282111292631147169916812964...
factorial of 171 is uintmax_t: 0
                    float:     inf *
                    double:    inf *
                    string:    12410180702176678234248405241031039926166055775016...
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
2

Why Is This Factorial Algorithm Not Accurate

There's nothing wrong in your algorithm as such. It is just that the data types you use have a limit for the highest number they can store. This will be a problem no matter which algorithm you choose. You can change the data types from float to something like long double to hold something bigger. But eventually it will still start failing once the factorial value exceeds the capacity of that data type. In my opinion, you should put an a condition in your factorial function to return without calculating anything if the passed in argument is greater than a value that your chosen datatype can support.

VHS
  • 9,534
  • 3
  • 19
  • 43
  • The code doesn't use `int` to store results, and the problem isn't the highest number of that type. – melpomene Jan 06 '17 at 17:12
  • I thought when I first looked at the question, the argument was `int`. But now I see that it is `unsigned int`. Let me update my answer. – VHS Jan 06 '17 at 17:13
  • 2
    The argument type doesn't matter here. – melpomene Jan 06 '17 at 17:14
  • `float` can hold far, far bigger numbers than `unsigned long long`. Your explanation doesn't make sense. – melpomene Jan 06 '17 at 17:21
  • Argument type doesn't matter. But the value should. That's what I am saying in my answer. The function should put a limit to the max value it should accept as the argument. – VHS Jan 06 '17 at 17:21
  • 1
    Well, for that matter, a `long double` can store even a higher value than `float`. My answer is not to provide an alternate data type but to put a hard limit in the function depending on the datatype you choose. – VHS Jan 06 '17 at 17:24
  • Aah, I see. I changed that verbiage for `float to ...`. However, the other explanation still applies here. The original failure point of '14' is just an example. Should not be taken literally. – VHS Jan 06 '17 at 17:34
  • Your explanation of "*the data types you use have a limit for the highest number they can store*" and "*it will still start failing once the factorial value exceeds the capacity of that data type*" doesn't apply here because OP's code doesn't exceed the capacity of float. I mean, you're not wrong in general; it just has nothing to do with the problem OP has asked for help with. – melpomene Jan 06 '17 at 17:36
  • 1
    For me this makes the most sense. Thank-you for the helpful answer. The algorithm seems to be accurate up to the number 21 when using the `double` return type. – user3808269 Jan 06 '17 at 19:43
2

As explained in the other answers it has to do with the range and precision of the different types of numbers available.

It is possible to use a character array, a string, to represent the numbers and obtain exact values. For example:

/* Fact50.c -- display a table of factorials from 0! to 50!

   50! is 65 digits in length
*/

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

#define MAXNUM      50
#define LEN         65

int multiply(int n, char fact[], int limit)
{
    int carry = 0;
    for (int i = LEN - 1; i >= limit; --i) {
        int product = (fact[i] - '0') * n + carry;
        fact[i] = product % 10 + '0';
        carry = 0;
        if (product > 9) {
            carry = product / 10;
            if (i == limit) {
                --limit;
                fact[limit] = '0';
            }
        }
    }

    return limit;
}

int main(void)
{
    printf("\n"
           "                          Table of Factorials\n"
           "\n");

    char fact[LEN + 1] = { [LEN - 1] = '1', [LEN] = '\0' };
    memset(fact, '.', LEN - 1);

    printf("%2d! = %s\n", 0, fact);
    printf("%2d! = %s\n", 1, fact);

    int limit = LEN - 1;
    for (int i = 2; i <= MAXNUM; ++i) {
        limit = multiply(i, fact, limit);
        printf("%2d! = %s\n", i, fact);
    }

    return 0;
}

program output:

                           Table of Factorials

 0! = ................................................................1
 1! = ................................................................1
 2! = ................................................................2
 3! = ................................................................6
 4! = ...............................................................24
 5! = ..............................................................120
 6! = ..............................................................720
 7! = .............................................................5040
 8! = ............................................................40320
 9! = ...........................................................362880
10! = ..........................................................3628800
11! = .........................................................39916800
12! = ........................................................479001600
13! = .......................................................6227020800
14! = ......................................................87178291200
15! = ....................................................1307674368000
16! = ...................................................20922789888000
17! = ..................................................355687428096000
18! = .................................................6402373705728000
19! = ...............................................121645100408832000
20! = ..............................................2432902008176640000
21! = .............................................51090942171709440000
22! = ...........................................1124000727777607680000
23! = ..........................................25852016738884976640000
24! = .........................................620448401733239439360000
25! = .......................................15511210043330985984000000
26! = ......................................403291461126605635584000000
27! = ....................................10888869450418352160768000000
28! = ...................................304888344611713860501504000000
29! = ..................................8841761993739701954543616000000
30! = ................................265252859812191058636308480000000
31! = ...............................8222838654177922817725562880000000
32! = .............................263130836933693530167218012160000000
33! = ............................8683317618811886495518194401280000000
34! = ..........................295232799039604140847618609643520000000
35! = ........................10333147966386144929666651337523200000000
36! = .......................371993326789901217467999448150835200000000
37! = .....................13763753091226345046315979581580902400000000
38! = ....................523022617466601111760007224100074291200000000
39! = ..................20397882081197443358640281739902897356800000000
40! = .................815915283247897734345611269596115894272000000000
41! = ...............33452526613163807108170062053440751665152000000000
42! = .............1405006117752879898543142606244511569936384000000000
43! = ............60415263063373835637355132068513997507264512000000000
44! = ..........2658271574788448768043625811014615890319638528000000000
45! = ........119622220865480194561963161495657715064383733760000000000
46! = .......5502622159812088949850305428800254892961651752960000000000
47! = .....258623241511168180642964355153611979969197632389120000000000
48! = ...12413915592536072670862289047373375038521486354677760000000000
49! = ..608281864034267560872252163321295376887552831379210240000000000
50! = 30414093201713378043612608166064768844377641568960512000000000000
Verbatim
  • 245
  • 1
  • 2
  • 8
  • Amazing. :) I am going to study this code more. I checked this up to the first 25 numbers and from my analysis the factorials are accurate. I checked the output against the factorial table here: https://www.mathsisfun.com/numbers/factorial.html – user3808269 Jan 10 '17 at 00:43
  • Also can you explain some of the code @ringzero? I am baffled here. 0_o lol – user3808269 Jan 10 '17 at 00:56
  • The program doesn't use recursion. It uses iteration (looping) -- doing multiplication in the same way as you would do the calculation by hand. – Verbatim Mar 11 '23 at 21:32