commit 0d2ce70dd16c2777e079bdf39fdcfdebf8c4819e
Author: h7x4 <h7x4@nani.wtf>
Date:   Tue Mar 18 01:05:49 2025 +0100

    Initial commit

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b2be92b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+result
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..8439a9f
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,27 @@
+{
+  "nodes": {
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1742206328,
+        "narHash": "sha256-q+AQ///oMnyyFzzF4H9ShSRENt3Zsx37jTiRkLkXXE0=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "096478927c360bc18ea80c8274f013709cf7bdcd",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..900fbf6
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,93 @@
+{
+  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+
+  outputs = { self, nixpkgs }: let
+    system = "x86_64-linux";
+    pkgs = nixpkgs.legacyPackages.${system};
+  in {
+    bigint-lib = pkgs.callPackage ./lib.nix { };
+    packages.${system}.default = with self.bigint-lib; pkgs.writeText "bigint-test" (let
+      toS' = x: if builtins.isBool x
+                  then if x
+                    then "true"
+                    else "false"
+                  else if isBigInt x then bigIntToString x
+                  else toString x;
+      compare = name: f: x: y: "${name} ${bigIntToString x} ${bigIntToString y} = ${toS' (f x y)}";
+    in ''
+      ${bigIntToString (toBigInt 123)}
+      ${bigIntToString (toBigInt (-123))}
+      ${bigIntToString (toBigInt (-0))}
+      ${bigIntToString (toBigInt 0)}
+
+      ${compare "bigIntEquals" bigIntEquals (toBigInt (0)) (toBigInt (1))}
+      ${compare "bigIntEquals" bigIntEquals (toBigInt (-1)) (toBigInt (1))}
+      ${compare "bigIntEquals" bigIntEquals (toBigInt (2)) (toBigInt (2))}
+      ${compare "bigIntEquals" bigIntEquals (toBigInt (0)) (toBigInt (-0))}
+
+      ${compare "bigIntGreaterThan" bigIntGreaterThan (toBigInt 0) (toBigInt 0)}
+      ${compare "bigIntGreaterThan" bigIntGreaterThan (toBigInt 12345) (toBigInt 9999)}
+      ${compare "bigIntGreaterThan" bigIntGreaterThan (toBigInt 9999) (toBigInt 12345)}
+      ${compare "bigIntGreaterThan" bigIntGreaterThan (toBigInt 99999) (toBigInt 12345)}
+      ${compare "bigIntGreaterThan" bigIntGreaterThan (toBigInt (-99999)) (toBigInt 12345)}
+      ${compare "bigIntGreaterThan" bigIntGreaterThan (toBigInt 99999) (toBigInt (-12345))}
+      ${compare "bigIntGreaterThan" bigIntGreaterThan (toBigInt (-99999)) (toBigInt (-12345))}
+
+      ${compare "bigIntLessThan" bigIntLessThan (toBigInt 0) (toBigInt 0)}
+      ${compare "bigIntLessThan" bigIntLessThan (toBigInt 12345) (toBigInt 9999)}
+      ${compare "bigIntLessThan" bigIntLessThan (toBigInt 9999) (toBigInt 12345)}
+      ${compare "bigIntLessThan" bigIntLessThan (toBigInt 99999) (toBigInt 12345)}
+      ${compare "bigIntLessThan" bigIntLessThan (toBigInt (-99999)) (toBigInt 12345)}
+      ${compare "bigIntLessThan" bigIntLessThan (toBigInt 99999) (toBigInt (-12345))}
+      ${compare "bigIntLessThan" bigIntLessThan (toBigInt (-99999)) (toBigInt (-12345))}
+
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 0) (toBigInt 0)}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 1) (toBigInt 2)}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 1) (toBigInt (-2))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 5) (toBigInt (-10))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-2)) (toBigInt 1)}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-10)) (toBigInt 5)}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 100000000) (toBigInt (-1))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-1)) (toBigInt 100000000)}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 200000000) (toBigInt (-2))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-2)) (toBigInt 200000000)}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 99) (toBigInt 99)}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-1)) (toBigInt (-2))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-99)) (toBigInt (-99))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (99)) (toBigInt (-99))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-99)) (toBigInt (99))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt (-99)) (toBigInt (99))}
+      ${compare "bigIntAdd" bigIntAdd (toBigInt 100000000) (toBigInt 1)}
+
+      ${compare "bigIntSub" bigIntSub (toBigInt 0) (toBigInt 0)}
+      ${compare "bigIntSub" bigIntSub (toBigInt 1) (toBigInt 2)}
+      ${compare "bigIntSub" bigIntSub (toBigInt 100000000) (toBigInt (-1))}
+      ${compare "bigIntSub" bigIntSub (toBigInt (-1)) (toBigInt 100000000)}
+      ${compare "bigIntSub" bigIntSub (toBigInt 200000000) (toBigInt (-2))}
+      ${compare "bigIntSub" bigIntSub (toBigInt (-2)) (toBigInt 200000000)}
+      ${compare "bigIntSub" bigIntSub (toBigInt 99) (toBigInt 99)}
+      ${compare "bigIntSub" bigIntSub (toBigInt (-1)) (toBigInt (-2))}
+      ${compare "bigIntSub" bigIntSub (toBigInt (-99)) (toBigInt (-99))}
+      ${compare "bigIntSub" bigIntSub (toBigInt (99)) (toBigInt (-99))}
+      ${compare "bigIntSub" bigIntSub (toBigInt (-99)) (toBigInt (99))}
+      ${compare "bigIntSub" bigIntSub (toBigInt (-99)) (toBigInt (99))}
+
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 0) (toBigInt 0)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 100) (toBigInt 0)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 100) (toBigInt 1)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 100) (toBigInt 2)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 100) (toBigInt 100)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 1) (toBigInt 2)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 100000000) (toBigInt (-1))}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt (-1)) (toBigInt 100000000)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 200000000) (toBigInt (-2))}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt (-2)) (toBigInt 200000000)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt 99) (toBigInt 99)}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt (-1)) (toBigInt (-2))}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt (-99)) (toBigInt (-99))}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt (99)) (toBigInt (-99))}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt (-99)) (toBigInt (99))}
+      ${compare "bigIntMultiply" bigIntMultiply (toBigInt (-99)) (toBigInt (99))}
+    '');
+  };
+}
diff --git a/lib.nix b/lib.nix
new file mode 100644
index 0000000..ebb45eb
--- /dev/null
+++ b/lib.nix
@@ -0,0 +1,172 @@
+{ lib, ... }: with lib;
+rec {
+  # TODO: Changing the base currently requires more modifications to the code.
+  #       The arithmetic works out, but `toBigInt` and `bigIntToString` does not.
+  base = 10;
+
+  # a -> Bool
+  isBigInt = x: isAttrs x
+             && x ? type
+             && x.type == "bigint"
+             && x ? isPositive
+             && x ? digits;
+
+  # Num -> BigInt
+  toBigInt = num: {
+    type = "bigint";
+    isPositive = num >= 0;
+    digits = pipe num [
+      toString
+      (removePrefix "-")
+      stringToCharacters
+      (map toInt)
+    ];
+  };
+
+  # BigInt -> String
+  bigIntToString = { isPositive, digits, ... }:
+    (optionalString (!isPositive) "-") + (concatStringsSep "" (map toString digits));
+
+  # https://silentmatt.com/blog/2011/10/how-bigintegers-work/
+
+  # BigInt -> BigInt -> BigInt
+  bigIntAdd = x: y:
+    assert isBigInt x;
+    assert isBigInt y;
+  let
+    # rem = lib.trivial.mod;
+    # mod = a: b: a - b * (builtins.floor (a / b));
+    repeat = item: times: map (const item) (range 1 times);
+    zfill = len: el: xs: if length xs > len then xs else (repeat el (len - (length xs))) ++ xs;
+    stripZeros = xs: if xs == [0] then [0]
+                     else if head xs == 0 then stripZeros (tail xs)
+                     else xs;
+
+    addDigits = xs: ys: let
+      addF = { fst, snd }: { carry ? false, digits ? [] }: let
+        n = if carry then 1 + fst + snd else fst + snd;
+      in {
+        carry = n >= base;
+        digits = [(mod n base)] ++ digits;
+      };
+      xs' = zfill (max (length xs) (length ys)) 0 xs;
+      ys' = zfill (max (length xs) (length ys)) 0 ys;
+      folded = foldr addF {} (zipLists xs' ys');
+    in if folded.carry then [1] ++ folded.digits else folded.digits;
+
+    subDigits = xs: ys: let
+      subF = { fst, snd }: { borrow ? false, digits ? [] }: let
+        n = if borrow then fst - snd - 1 else fst - snd;
+        n' = if n < 0 then n + base else n;
+      in {
+        borrow = n < 0;
+        digits = [n'] ++ digits;
+      };
+      xs' = zfill (max (length xs) (length ys)) 0 xs;
+      ys' = zfill (max (length xs) (length ys)) 0 ys;
+      folded = foldr subF {} (zipLists xs' ys');
+    in stripZeros folded.digits;
+
+  in if x.isPositive && y.isPositive then {
+    type = "bigint";
+    isPositive = true;
+    digits = addDigits x.digits y.digits;
+  }
+  else if !x.isPositive && !y.isPositive then {
+    type = "bigint";
+    isPositive = false;
+    digits = addDigits x.digits y.digits;
+  }
+  else if x.isPositive && !y.isPositive then rec {
+    type = "bigint";
+    isPositive = !(bigIntLessThan x (y // { isPositive = true; }));
+    digits = if isPositive
+               then subDigits x.digits y.digits
+               else subDigits y.digits x.digits;
+  }
+  else /* !x.isPositive && y.isPositive */ rec {
+    type = "bigint";
+    isPositive = !(bigIntLessThan y (x // { isPositive = true; }));
+    digits = if isPositive
+               then subDigits y.digits x.digits
+               else subDigits x.digits y.digits;
+  };
+
+  # BigInt -> BigInt -> BigInt
+  bigIntSub = x: y@{ isPositive, ... }:
+    bigIntAdd x (y // { isPositive = !isPositive; });
+
+  # https://silentmatt.com/blog/2011/10/how-bigintegers-work-part-2-multiplication/
+
+  # BigInt -> BigInt -> BigInt
+  bigIntMultiply = x: y:
+    assert isBigInt x;
+    assert isBigInt y;
+  let
+    xor = a: b: (a && !b) || (!a && b);
+
+    createPartial = y_digit: { carry ? 0, digits ? [] }: x_digit: let
+      n = (x_digit * y_digit) + carry;
+    in {
+      carry = builtins.floor (n / base);
+      digits = [(mod n base)] ++ digits;
+    };
+
+    partialProductDigits = let
+      mapYs = i: y_digit: let
+        partial = foldl (createPartial y_digit) { } ([0] ++ x.digits);
+        partialWithCorrectPower = partial.digits ++ (builtins.genList (_: 0) i);
+      in partialWithCorrectPower;
+    in imap0 mapYs y.digits;
+
+    partialProducts =
+      map (digits: { type = "bigint"; isPositive = true; inherit digits; }) partialProductDigits;
+
+  in foldl bigIntAdd (toBigInt 0) partialProducts // {
+    isPositive = !(xor x.isPositive y.isPositive);
+  };
+
+  # BigInt -> BigInt -> BigInt
+  bigIntEquals = x: y:
+    assert isBigInt x;
+    assert isBigInt y;
+    x == y;
+
+  # BigInt -> BigInt -> BigInt
+  bigIntGreaterThan = x: y:
+    assert isBigInt x;
+    assert isBigInt y;
+    if x.isPositive && !y.isPositive then true
+    else if !x.isPositive && y.isPositive then false
+    # Cheaper than the upcoming calculations
+    else if bigIntEquals x y then false
+
+    else if x.isPositive && y.isPositive then
+      if length x.digits < length y.digits then false
+      else if length x.digits > length y.digits then true
+      else let
+      compareDigits = xs: ys:
+        if xs == [] then false
+        else if (head xs) == (head ys) then compareDigits (tail xs) (tail ys)
+        else head xs > head ys;
+      in compareDigits x.digits y.digits
+
+    /* !x.isPositive && !y.isPositive */
+    else
+      if length x.digits > length y.digits then false
+      else if length x.digits < length y.digits then true
+      else let
+      compareDigits = xs: ys:
+        if xs == [] then false
+        else if (head xs) == (head ys) then compareDigits (tail xs) (tail ys)
+        else head xs < head ys;
+      in compareDigits x.digits y.digits;
+
+  # BigInt -> BigInt -> BigInt
+  bigIntLessThan = flip bigIntGreaterThan;
+
+  # BigInt -> BigInt -> BigInt
+  bigIntCmp = x: y: if bigIntEquals x y then 0 else
+                    if bigIntGreaterThan x y then 1 else
+                    -1;
+}