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; +}