0

What is the greatest factorial we can calculate using an int in Java 19?

I have found the calculation is correct up to Factorial(12) using an int to stock the result.

Here is the recursive code I use:

public static int Factorial(int n) {
    if (n >= 13) {
        System.out.println("Trop grand");
        return -1;
    }
    if (n == 0) {
        return 1;
    } else {
        return n * Factorial(n - 1);
    }
}

Do you find the same result?

Subsidiary question: how would you implement the function with a long variable?

I tried everything, expected nothing and nothing special happened. (I have to fill this blank.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • I assume you want the computed value to be precise. Are you allowed to use [BigInteger](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/math/BigInteger.html) ? – Old Dog Programmer Dec 01 '22 at 02:22
  • Yes i want it to be exact. I did not think about that but let's say we are not allowed to use it ;) – Big Moustache guy Dec 01 '22 at 02:25
  • 1
    Well, I would change `int` to `long` in the code, But, instead of `n * Factorial(n - 1)`, I would use [`Math.multiplyExact(long x, long y)`](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/Math.html#multiplyExact(long,long)). Note that it throws an exception if the result overflows a `long`. – Old Dog Programmer Dec 01 '22 at 02:28
  • This is another one of those questions where you could just try a few experiments and figure it out for yourself. – President James K. Polk Dec 01 '22 at 14:37
  • 1
    @PresidentJamesK.Polk When I read the question, it looks to me like the O/P did try it out. The O/P wants to compare results. – Old Dog Programmer Dec 01 '22 at 15:51
  • Indeed I tried out ! Maybe not thoroughly enough because I mainly use JAVA and C# as programming languages but I am not proficient with it yet ... – Big Moustache guy Dec 01 '22 at 15:55

3 Answers3

2

I am not coding in Java, but int in C/C++ based languages is signed integer, meaning it uses one bit for sign (yes, even if you use two's complement), so in case you have 32/64 bit ints, you can use only numbers up to (not inclusive):

2^31 = 2147483648
2^63 = 9223372036854775808

Now looking at first few factorials taken from Fast exact bigint factorial:

[ 0.001 ms ] 1! = 1
[ 0.000 ms ] 2! = 2
[ 0.000 ms ] 3! = 6
[ 0.000 ms ] 4! = 24
[ 0.006 ms ] 5! = 120
[ 0.006 ms ] 6! = 720
[ 0.007 ms ] 7! = 5040
[ 0.005 ms ] 8! = 40320
[ 0.006 ms ] 9! = 362880
[ 0.007 ms ] 10! = 3628800
[ 0.008 ms ] 11! = 39916800
[ 0.012 ms ] 12! = 479001600
            2^31 = 2147483648 <-------------------------
[ 0.013 ms ] 13! = 6227020800
[ 0.014 ms ] 14! = 87178291200
[ 0.016 ms ] 15! = 1307674368000
[ 0.014 ms ] 16! = 20922789888000
[ 0.015 ms ] 17! = 355687428096000
[ 0.017 ms ] 18! = 6402373705728000
[ 0.019 ms ] 19! = 121645100408832000
[ 0.016 ms ] 20! = 2432902008176640000
            2^63 = 9223372036854775808 <-------------------------
[ 0.017 ms ] 21! = 51090942171709440000
[ 0.019 ms ] 22! = 1124000727777607680000

So I expect 20! should still fit for 64-bit signed ints.

If you use unsigned int you can compute up to twice as much number, but in case of 64 bit the 21! is still too big... To compute more, you need bigger bitwidth or disect the result to trailing zeros (either decadic or binary) and have the result in form of two integers, for example, like this:

void fact(int &man,int &exp,int n)        // man * 10^exp = n!
    {
    man=1; exp=0;
    if (n<=1) return;
    int i,j;
    for (i=2;i<=n;i++)
        {
        j=i;
        while (j%10==0){j/=10; exp++; }
        man*=j;
        if (man<0){ man=0; exp=0; return; }        // overflow
        while (man%10==0){ man/=10; exp++; }
        }
    }

I used it in VCL and 32-bit signed ints like this:

int i,m,e;
AnsiString s;
for (i=0;i<40;i++)
    {
    fact(m,e,i);
    s=m; while (e){ s+="0"; e--; } // just print m to s and add e times "0" at the end
    mm_log->Lines->Add(AnsiString().sprintf("%2i! = %s",i,s)); // output to memo
    }

with this output:

 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! = 19184179200 <- this one is overflowed too
16! = 0
17! = 0
18! = 0
19! = 0
20! = 0
21! = 0
22! = 0
23! = 0
24! = 0
25! = 0
26! = 0
27! = 0
28! = 0
29! = 0
30! = 0
31! = 0
32! = 0
33! = 0
34! = 0
35! = 0
36! = 0
37! = 0
38! = 0
39! = 0

As you can see, I could compute up to 14! this way instead of 12!...

Also, you are computing factorial using recursion ... iteration is much better unless you implement fast factorials which has no meaning without big integers.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • 1
    Alright thank you for this very complete and interesting answer. Now I wonder what are the downsides of using unsigned int lol. But it seems it has been removed from JAVA language or it is not natively supported. – Big Moustache guy Dec 01 '22 at 07:30
  • 1
    @GuillaumeDonnadieu Java never had unsigned data types apart from from byte. The integer datatypes have always been byte, short, int, and long (and BigInteger of course). – jwenting Dec 01 '22 at 07:40
  • @GuillaumeDonnadieu The question should be what is the downside of using "higher" languages like C/C++ ... where you lost direct access to flags which means its hard to detect overflows on `uints` ... see [Can't make value propagate through carry](https://stackoverflow.com/a/26603589/2521214) also [How to deal with overflow and underflow?](https://stackoverflow.com/a/33006665/2521214) might interest you – Spektre Dec 01 '22 at 08:24
  • @jwenting: Java `byte` is (and always was) signed, although it is often used (particularly in an array or Buffer) for data that _should_ be unsigned like file and socket I/O and cryptography, and thus needs `&0xFF` sprinkled all over. `char` OTOH is also an integer type of unsigned 16bits based on an early version of Unicode -- so that when Unicode expanded to 21bits, many APIs grew a 'codepoint' variant that uses `int` for a character. (C and C++ also make `char` integer, but there it is usually 8bits, and has signed, unsigned _and_ 'plain' forms, and you _also_ have `wchar_t`.) – dave_thompson_085 Dec 01 '22 at 10:32
  • This is way too advanced for me with my current level in programming but thanks for the light. – Big Moustache guy Dec 01 '22 at 16:04
1

The largest factorial that can be computed in Java using int is 12! = 479001600. (12! = 479,001,600).

Here is some code to test that:

public static void factorialTest () {
    int i = 0, r = 1;
    
    while (r > 0) { 
        r = factorial (i);
        System.out.println ((i++) + "! = " + r);
    }
}  

public static int factorial(int n) {
    if (n < 0) {  return -1;  }
    if (n == 0 || n == 1) { return 1; }
    int r = 1;
    try {
        for (int i = 1; i <= n; ++i) {
            r = Math.multiplyExact (r, i);
        }
    } catch (ArithmeticException ex) {
        return -2;
    }
    return r;
}

The method public static int factorial(int n) returns -1 if a negative number is passed, and -2 if n has a value that would result in the calculation of a number that can overflow an int.

Integer calculations in Java can overflow without generating an exception. If the result of a calculation is too large, the result "wraps around". Sometimes, it is desirable to detect an overflow. To support that, the Java Math API has a number of ~Exact methods. These throw an ArithmeticException when an overflow occurs.

So, the idea here is to calculate integer factorials, beginning at one, and incrementing until an overflow is caught, using try ... catch.

It is conceivable that this could be done without using try ... catch by testing for a result in which the result of the multiplication is less than the previous result. Since the sequence of results is strictly increasing, finding a smaller value in the result sequence would indicate a wraparound occurred. However, this isn't reliable: A calculation could wraparound and still produce an incorrect result greater than the previous result.

The largest factorial that can be computed in Java using long is 20! = 2432902008176640000. That's 20! = 2,432,902,008,176,640,000. One can test that by changing all int to long in the above code.

Comment: Although Java doesn't allow unsigned integer types to be declared, unsigned integer calculations are possible. Support for unsigned calculations includes calculations in which the result wraps around, and unsigned methods, such as toUnsignedString. However, that seems to be irrelevant to this discussion, since the calculation of 13! would overflow an unsigned int and the calculation of 21! would overflow an unsigned long.

Comment: The O/P used recursion to calculate factorials. I used iteration. While I am a fan of recursion, I prefer to use iteration when an iterative solution is easy to find and implement. Recursion can use more overhead than iteration.

Comment: Optimizing compilers will sometimes change code using recursion to using iteration. That may have been the case in the O/P's code.

Output, using long:

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! = -2
Old Dog Programmer
  • 901
  • 1
  • 7
  • 9
0

Theoretically, Java would support factorials up to 2^MAXINT-1. That's a pretty insane number, obviously. Specific JVMs, depending on their implementation, may support even larger values, but this is not required by the language specification.

This is the highest you will be able to fit in a BigInteger. Whether you can actually calculate a number that high depends obviously on how you go about that calculation and how much time you have on hand to perform the calculation.

From the JDK API documentation:

All methods and constructors in this class throw NullPointerException when passed a null object reference for any input parameter. BigInteger must support values in the range -2Integer.MAX_VALUE (exclusive) to +2Integer.MAX_VALUE (exclusive) and may support values outside of that range. An ArithmeticException is thrown when a BigInteger constructor or method would generate a value outside of the supported range.

See The Java™ Language Specification: 4.2.2 Integer Operations

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jwenting
  • 5,505
  • 2
  • 25
  • 30
  • 1
    "Whether you can actually calculate a number that high depends obviously on how you go about that calculation and how much time you have on hand to perform the calculation." You would also run out of memory to hold the digits. – Old Dog Programmer Dec 01 '22 at 16:11