Initial commit

This commit is contained in:
Oystein Kristoffer Tveit 2025-03-18 01:05:49 +01:00
commit 0d2ce70dd1
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
4 changed files with 293 additions and 0 deletions

1
.gitignore vendored Normal file

@ -0,0 +1 @@
result

27
flake.lock generated Normal file

@ -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
}

93
flake.nix Normal file

@ -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))}
'');
};
}

172
lib.nix Normal file

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