From e52a1b356c26a75fc2833279eab7836061f31997 Mon Sep 17 00:00:00 2001
From: Saurabh Kumar <developer.saurabh@outlook.com>
Date: Wed, 28 Feb 2024 00:12:34 +0530
Subject: [PATCH 1/3] Implement `math.factorial(n)` to calculate very large
 factorials with speed and accuracy

---
 src/runtime/math.py | 190 +++++++++++++++++++++++++++++++++++++-------
 1 file changed, 161 insertions(+), 29 deletions(-)

diff --git a/src/runtime/math.py b/src/runtime/math.py
index 8655201892..7f383ecfa2 100644
--- a/src/runtime/math.py
+++ b/src/runtime/math.py
@@ -15,36 +15,169 @@ def modf(x: f64) -> tuple[f64, f64]:
     """
     return (x - f64(int(x)), float(int(x)))
 
-@overload
-def factorial(x: i32) -> i32:
-    """
-    Computes the factorial of `x`.
-    """
 
-    result: i32
-    result = 0
-    if x < 0:
-        return result
-    result = 1
-    i: i32
-    for i in range(1, x+1):
-        result *= i
-    return result
+def factorial(n: i32) -> i64:
+    """Computes the factorial of `n`."""
+    MAX_LOOKUP_VALUE: i32 = 20
+    FACTORIAL_LOOKUP_TABLE: list[i64] = [
+        i64(1),
+        i64(1),
+        i64(2),
+        i64(6),
+        i64(24),
+        i64(120),
+        i64(720),
+        i64(5040),
+        i64(40320),
+        i64(362880),
+        i64(3628800),
+        i64(39916800),
+        i64(479001600),
+        i64(6227020800),
+        i64(87178291200),
+        i64(1307674368000),
+        i64(20922789888000),
+        i64(355687428096000),
+        i64(6402373705728000),
+        i64(121645100408832000),
+        i64(2432902008176640000),
+    ]
+    if n < 0:
+        # Exceptions are not implemented currently
+        # raise ValueError("factorial() not defined for negative values")
+        assert 1 == 0, "factorial() not defined for negative values."
+    elif n < MAX_LOOKUP_VALUE:
+        return FACTORIAL_LOOKUP_TABLE[n]
+    else:
+        f: list[i32] = [0] * 4300
+        f[0] = 0
+        f[1] = 0
+        f[2] = 0
+        f[3] = 0
+        f[4] = 4
+        f[5] = 6
+        f[6] = 6
+        f[7] = 7
+        f[8] = 1
+        f[9] = 8
+        f[10] = 0
+        f[11] = 0
+        f[12] = 2
+        f[13] = 0
+        f[14] = 9
+        f[15] = 2
+        f[16] = 3
+        f[17] = 4
+        f[18] = 2
+
+        f_size: i32 = 19
+
+        i: i32 = 21
+        while i <= n:
+            index: i32 = 0
+            carry: i32 = 0
+            while index < f_size:
+                product: i32 = f[index] * i + carry
+                f[index] = product % 10
+
+                carry = product // 10
+                index += 1
+
+            while carry > 0:
+                f[f_size] = carry % 10
+                carry = carry // 10
+                f_size += 1
+            i += 1
+
+    result: str = ""
+    idx: i32
+    for idx in range(f_size - 1, -1, -1):
+        result += str(f[idx])
+    print(result)
+    return i64(0)
+
+
+@overload
+def factorial(n: i64) -> i64:
+    """Computes the factorial of `n`."""
+    MAX_LOOKUP_VALUE: i64 = i64(20)
+    FACTORIAL_LOOKUP_TABLE: list[i64] = [
+        i64(1),
+        i64(1),
+        i64(2),
+        i64(6),
+        i64(24),
+        i64(120),
+        i64(720),
+        i64(5040),
+        i64(40320),
+        i64(362880),
+        i64(3628800),
+        i64(39916800),
+        i64(479001600),
+        i64(6227020800),
+        i64(87178291200),
+        i64(1307674368000),
+        i64(20922789888000),
+        i64(355687428096000),
+        i64(6402373705728000),
+        i64(121645100408832000),
+        i64(2432902008176640000),
+    ]
+    if n < i64(0):
+        # Exceptions are not implemented currently
+        # raise ValueError("factorial() not defined for negative values")
+        assert 1 == 0, "factorial() not defined for negative values."
+    elif n < MAX_LOOKUP_VALUE:
+        return FACTORIAL_LOOKUP_TABLE[n]
+    else:
+        f: list[i32] = [0] * 4300
+        f[0] = 0
+        f[1] = 0
+        f[2] = 0
+        f[3] = 0
+        f[4] = 4
+        f[5] = 6
+        f[6] = 6
+        f[7] = 7
+        f[8] = 1
+        f[9] = 8
+        f[10] = 0
+        f[11] = 0
+        f[12] = 2
+        f[13] = 0
+        f[14] = 9
+        f[15] = 2
+        f[16] = 3
+        f[17] = 4
+        f[18] = 2
+
+        f_size: i32 = 19
+
+        i: i32 = 21
+        while i64(i) <= n:
+            index: i32 = 0
+            carry: i32 = 0
+            while index < f_size:
+                product: i32 = f[index] * i + carry
+                f[index] = product % 10
+
+                carry = product // 10
+                index += 1
+
+            while carry > 0:
+                f[f_size] = carry % 10
+                carry = carry // 10
+                f_size += 1
+            i += 1
+
+    result: str = ""
+    idx: i32
+    for idx in range(f_size - 1, -1, -1):
+        result += str(f[idx])
+    print(result)
+    return i64(0)
 
-@overload
-def factorial(x: i64) -> i64:
-    """
-    Computes the factorial of `x`.
-    """
-    result: i64
-    result = i64(0)
-    if x < i64(0):
-        return result
-    result = i64(1)
-    i: i64
-    for i in range(i64(1), x + i64(1)):
-        result *= i64(i)
-    return result
 
 @overload
 def floor(x: i32) -> i32:
@@ -457,7 +590,6 @@ def ldexp(x: f64, i: i32) -> f64:
     return result
 
 
-
 def mod(a: i32, b: i32) -> i32:
     """
     Returns a%b

From e7857d0a33be4e009562ed9f0bf4cd81bcd44fee Mon Sep 17 00:00:00 2001
From: Saurabh Kumar <developer.saurabh@outlook.com>
Date: Sat, 9 Mar 2024 20:55:32 +0530
Subject: [PATCH 2/3] Use `int` as return type

---
 src/runtime/math.py | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/runtime/math.py b/src/runtime/math.py
index 7f383ecfa2..a630c72a1b 100644
--- a/src/runtime/math.py
+++ b/src/runtime/math.py
@@ -16,7 +16,7 @@ def modf(x: f64) -> tuple[f64, f64]:
     return (x - f64(int(x)), float(int(x)))
 
 
-def factorial(n: i32) -> i64:
+def factorial(n: i32) -> int:
     """Computes the factorial of `n`."""
     MAX_LOOKUP_VALUE: i32 = 20
     FACTORIAL_LOOKUP_TABLE: list[i64] = [
@@ -93,12 +93,11 @@ def factorial(n: i32) -> i64:
     idx: i32
     for idx in range(f_size - 1, -1, -1):
         result += str(f[idx])
-    print(result)
-    return i64(0)
+    return int(result)
 
 
 @overload
-def factorial(n: i64) -> i64:
+def factorial(n: i64) -> int:
     """Computes the factorial of `n`."""
     MAX_LOOKUP_VALUE: i64 = i64(20)
     FACTORIAL_LOOKUP_TABLE: list[i64] = [
@@ -175,8 +174,7 @@ def factorial(n: i64) -> i64:
     idx: i32
     for idx in range(f_size - 1, -1, -1):
         result += str(f[idx])
-    print(result)
-    return i64(0)
+    return int(result)
 
 
 @overload

From 14783559b1995735bbc1becc5f66c86f7f9a77d6 Mon Sep 17 00:00:00 2001
From: Saurabh Kumar <151380951+kmr-srbh@users.noreply.github.com>
Date: Tue, 12 Mar 2024 21:56:45 +0530
Subject: [PATCH 3/3] Revert setting return type to `int`

---
 src/runtime/math.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/runtime/math.py b/src/runtime/math.py
index a630c72a1b..4a3c457392 100644
--- a/src/runtime/math.py
+++ b/src/runtime/math.py
@@ -16,7 +16,7 @@ def modf(x: f64) -> tuple[f64, f64]:
     return (x - f64(int(x)), float(int(x)))
 
 
-def factorial(n: i32) -> int:
+def factorial(n: i32) -> i64:
     """Computes the factorial of `n`."""
     MAX_LOOKUP_VALUE: i32 = 20
     FACTORIAL_LOOKUP_TABLE: list[i64] = [
@@ -93,11 +93,11 @@ def factorial(n: i32) -> int:
     idx: i32
     for idx in range(f_size - 1, -1, -1):
         result += str(f[idx])
-    return int(result)
+    return i64(0)
 
 
 @overload
-def factorial(n: i64) -> int:
+def factorial(n: i64) -> i64:
     """Computes the factorial of `n`."""
     MAX_LOOKUP_VALUE: i64 = i64(20)
     FACTORIAL_LOOKUP_TABLE: list[i64] = [
@@ -174,7 +174,7 @@ def factorial(n: i64) -> int:
     idx: i32
     for idx in range(f_size - 1, -1, -1):
         result += str(f[idx])
-    return int(result)
+    return i64(0)
 
 
 @overload