ludic/runtime/native/bignum.ludic
Orkuncakilkaya 57b66bdf47 feat(lang): L4 type checker between parse and emit
selfhost/check/ walks every function, the entry, tests, globals' initializers and
@On listeners with real scopes, and refuses mixed number kinds, text and numbers,
two record types, mismatched slices and fn types, wrong argument counts, wrong
returns and wrong push elements - every mix-up at once, each at its line.
LUDIC_CHECK_REPORT=1 lists them by category. pointer stays untyped (L7's).

What it found is fixed: render3d's HDR scan calling the float-bits extern f_lt
with floats; ludic.shooter's right-stick aim overflowing past half a push;
prof.ludic storing longs in []int; extern arguments now coerced to their
parameters. Text-returning runtime functions say string; Assets.ready says bool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 01:34:49 +03:00

380 lines
11 KiB
Text

# ============================================================================
# bignum.ludic — arbitrary-precision integers (`BigInt.*`) and exact base-10
# decimals (`Decimal.*`), written in Ludic.
#
# The thing a tycoon or idle game must never get wrong: money that adds up
# exactly, and counters that grow past what a 32- or 64-bit int can hold. Both
# types are EXACT, so they are deterministic — the same guarantee the rest of
# the runtime gives, with no binary floating point anywhere.
#
# BigInt — a sign-magnitude integer of unbounded size. Limbs are base 1e9
# (nine decimal digits each), little-endian, in a `words` buffer, so
# printing is just per-limb decimal with zero-padding. Multiplication
# accumulates in i64 (`long`) so a limb-by-limb product never
# overflows. add / sub / mul / pow / compare / divide-by-int.
# Decimal — a BigInt mantissa plus a decimal `scale` (digits after the point),
# so a value is mantissa x 10^-scale. add / sub align scales and stay
# exact; mul adds scales; `rescale` truncates toward zero. Perfect for
# prices, balances and taxes on values like 0.10 that binary can't
# represent.
#
# ludicc splices this file into any program that mentions `BigInt.*` or
# `Decimal.*` (like the regex/value runtimes); it is a self-contained fragment
# (only compiler intrinsics), so it works in a plain `program { entry }` tool as
# well as a game. The namespaces (emit_call.ludic) alias each method to the
# matching `bigint_*` / `decimal_*` function below.
# ============================================================================
const BN_BASE: int = 1000000000 # 1e9 — nine decimal digits per limb
# a sign-magnitude big integer. sign is -1 / 0 / +1 (0 only for the value zero);
# limbs are base-1e9, little-endian; n is the number of significant limbs.
property BigNum { sign: int = 0, n: int = 0, limbs: words }
# allocate a BigNum with room for `cap` limbs (at least one), all zeroed
function bn_alloc(cap: int) -> BigNum {
var c = cap
if c < 1 { c = 1 }
let b = new BigNum
b.sign = 0; b.n = 0; b.limbs = words(c)
var i = 0
while i < c { b.limbs[i] = 0; i += 1 }
return b
}
# drop high zero limbs; a magnitude of zero forces sign 0
function bn_norm(b: BigNum) -> BigNum {
var k = b.n
while k > 0 and b.limbs[k - 1] == 0 { k -= 1 }
b.n = k
if k == 0 { b.sign = 0 }
return b
}
function bigint_zero() -> BigNum { return bn_alloc(1) }
# an int -> BigNum (widened through i64 so INT_MIN's magnitude is representable)
function bigint_from(value: int) -> BigNum {
if value == 0 { return bigint_zero() }
var sign = 1
var v: long = value
if v < 0 { sign = -1; v = -v }
let b = bn_alloc(3)
var i = 0
while v > 0 {
let q: long = v / BN_BASE
let lo: int = v - q * BN_BASE
b.limbs[i] = lo
v = q
i += 1
}
b.n = i; b.sign = sign
return bn_norm(b)
}
# decimal text (optionally signed) -> BigNum. Assumes valid digits.
function bigint_from_str(text: pointer) -> BigNum {
let e = len(text)
var i = 0
var sign = 1
if e > 0 and text[0] == '-' { sign = -1; i = 1 } # '-'
if e > 0 and text[0] == '+' { i = 1 } # '+'
while i < e - 1 and text[i] == '0' { i += 1 } # skip leading zeros
let ndig = e - i
if ndig <= 0 { return bigint_zero() }
let nlimb = (ndig + 8) / 9
let b = bn_alloc(nlimb)
var li = 0
var pos = e
while pos > i {
var start = pos - 9
if start < i { start = i }
var v = 0
var k = start
while k < pos { v = v * 10 + (text[k] - 48); k += 1 }
b.limbs[li] = v; li += 1
pos = start
}
b.n = nlimb; b.sign = sign
return bn_norm(b)
}
# compare magnitudes: -1 / 0 / 1
function bn_ucmp(a: BigNum, b: BigNum) -> int {
if a.n != b.n { if a.n > b.n { return 1 }; return -1 }
var i = a.n - 1
while i >= 0 {
if a.limbs[i] != b.limbs[i] { if a.limbs[i] > b.limbs[i] { return 1 }; return -1 }
i -= 1
}
return 0
}
# magnitude add (ignores signs)
function bn_uadd(a: BigNum, b: BigNum) -> BigNum {
var m = a.n
if b.n > m { m = b.n }
let r = bn_alloc(m + 1)
var carry = 0
var i = 0
while i < m {
var s = carry
if i < a.n { s += a.limbs[i] }
if i < b.n { s += b.limbs[i] }
if s >= BN_BASE { r.limbs[i] = s - BN_BASE; carry = 1 } else { r.limbs[i] = s; carry = 0 }
i += 1
}
r.limbs[m] = carry
r.n = m + 1
return bn_norm(r)
}
# magnitude subtract, assuming |a| >= |b|
function bn_usub(a: BigNum, b: BigNum) -> BigNum {
let r = bn_alloc(a.n)
var borrow = 0
var i = 0
while i < a.n {
var s = a.limbs[i] - borrow
if i < b.n { s -= b.limbs[i] }
if s < 0 { s += BN_BASE; borrow = 1 } else { borrow = 0 }
r.limbs[i] = s
i += 1
}
r.n = a.n
return bn_norm(r)
}
function bigint_neg(a: BigNum) -> BigNum {
let r = bn_alloc(a.n)
var i = 0
while i < a.n { r.limbs[i] = a.limbs[i]; i += 1 }
r.n = a.n; r.sign = -a.sign
return r
}
function bigint_add(a: BigNum, b: BigNum) -> BigNum {
if a.sign == 0 { return b }
if b.sign == 0 { return a }
if a.sign == b.sign {
let r = bn_uadd(a, b); r.sign = a.sign
return bn_norm(r)
}
let c = bn_ucmp(a, b)
if c == 0 { return bigint_zero() }
if c > 0 { let r = bn_usub(a, b); r.sign = a.sign; return bn_norm(r) }
let r = bn_usub(b, a); r.sign = b.sign
return bn_norm(r)
}
function bigint_sub(a: BigNum, b: BigNum) -> BigNum { return bigint_add(a, bigint_neg(b)) }
function bigint_mul(a: BigNum, b: BigNum) -> BigNum {
if a.sign == 0 or b.sign == 0 { return bigint_zero() }
let r = bn_alloc(a.n + b.n)
var i = 0
while i < a.n {
var carry: long = 0
let ai: long = a.limbs[i]
var j = 0
while j < b.n {
let bj: long = b.limbs[j]
let cur: long = r.limbs[i + j]
let t: long = cur + ai * bj + carry
let q: long = t / BN_BASE
let lo: int = t - q * BN_BASE
r.limbs[i + j] = lo
carry = q
j += 1
}
var k = i + b.n
while carry > 0 {
let cur2: long = r.limbs[k]
let t2: long = cur2 + carry
let q2: long = t2 / BN_BASE
let lo2: int = t2 - q2 * BN_BASE
r.limbs[k] = lo2
carry = q2
k += 1
}
i += 1
}
r.n = a.n + b.n; r.sign = a.sign * b.sign
return bn_norm(r)
}
# a^exp for exp >= 0, by binary exponentiation
function bigint_pow(a: BigNum, exp: int) -> BigNum {
var result = bigint_from(1)
var base = a
var e = exp
while e > 0 {
if e - (e / 2) * 2 == 1 { result = bigint_mul(result, base) }
base = bigint_mul(base, base)
e /= 2
}
return result
}
# floor-toward-zero divide by a nonzero int; quotient is a BigNum
function bigint_div_int(a: BigNum, d: int) -> BigNum {
var dd = d
var dsign = 1
if d < 0 { dsign = -1; dd = -d }
let r = bn_alloc(a.n)
var rem: long = 0
let ddl: long = dd
var i = a.n - 1
while i >= 0 {
let cur: long = rem * BN_BASE + a.limbs[i]
let q: long = cur / ddl
rem = cur - q * ddl
r.limbs[i] = q
i -= 1
}
r.n = a.n; r.sign = a.sign * dsign
return bn_norm(r)
}
# remainder of dividing by a nonzero int; carries the sign of `a`
function bigint_mod_int(a: BigNum, d: int) -> int {
var dd = d
if d < 0 { dd = -d }
let ddl: long = dd
var rem: long = 0
var i = a.n - 1
while i >= 0 {
let cur: long = rem * BN_BASE + a.limbs[i]
rem = cur - (cur / ddl) * ddl
i -= 1
}
let r: int = rem
return r * a.sign
}
function bigint_cmp(a: BigNum, b: BigNum) -> int {
if a.sign != b.sign { if a.sign > b.sign { return 1 }; return -1 }
if a.sign == 0 { return 0 }
let c = bn_ucmp(a, b)
if a.sign > 0 { return c }
return -c
}
function bigint_eq(a: BigNum, b: BigNum) -> bool { return bigint_cmp(a, b) == 0 }
function bigint_is_zero(a: BigNum) -> bool { return a.sign == 0 }
# the value as a plain int when it fits in 32 bits, else clamped to the extreme
function bigint_to_int(a: BigNum) -> int {
if a.sign == 0 { return 0 }
var v: long = 0
var i = a.n - 1
while i >= 0 { v = v * BN_BASE + a.limbs[i]; i -= 1 }
if a.sign < 0 { v = -v }
let hi: long = 2147483647
let lo: long = -hi - 1
if v > hi { let r: int = hi; return r }
if v < lo { return -2147483647 - 1 }
let r: int = v
return r
}
# a limb (< 1e9) as exactly nine digits, left-padded with zeros
function bn_pad9(x: int) -> string {
var s = string(x)
var pad = 9 - len(s)
var out = ""
while pad > 0 { out += "0"; pad -= 1 }
return out + s
}
function bigint_str(a: BigNum) -> string {
if a.sign == 0 { return "0" }
var out = ""
if a.sign < 0 { out = "-" }
out += string(a.limbs[a.n - 1])
var i = a.n - 2
while i >= 0 { out += bn_pad9(a.limbs[i]); i -= 1 }
return out
}
# ---- Decimal: a BigNum mantissa scaled by 10^-scale ------------------------
property Dec { m: BigNum, scale: int = 0 }
function dec_make(m: BigNum, scale: int) -> Dec {
let d = new Dec
d.m = m; d.scale = scale
return d
}
function decimal_from(value: int) -> Dec { return dec_make(bigint_from(value), 0) }
# "[-]int[.frac]" -> Dec, scale = number of fractional digits
function decimal_from_str(text: pointer) -> Dec {
let e = len(text)
var dot = -1
var i = 0
while i < e { if text[i] == '.' { dot = i }; i += 1 } # '.'
if dot < 0 { return dec_make(bigint_from_str(text), 0) }
let intp = text[0..dot]
let fracp = text[dot + 1..e]
let scale = len(fracp)
return dec_make(bigint_from_str(intp + fracp), scale)
}
# mantissa of `d` scaled up to `newscale` (newscale >= d.scale), exact
function dec_scaled(d: Dec, newscale: int) -> BigNum {
let diff = newscale - d.scale
if diff <= 0 { return d.m }
return bigint_mul(d.m, bigint_pow(bigint_from(10), diff))
}
function decimal_add(a: Dec, b: Dec) -> Dec {
var sc = a.scale
if b.scale > sc { sc = b.scale }
return dec_make(bigint_add(dec_scaled(a, sc), dec_scaled(b, sc)), sc)
}
function decimal_sub(a: Dec, b: Dec) -> Dec {
var sc = a.scale
if b.scale > sc { sc = b.scale }
return dec_make(bigint_sub(dec_scaled(a, sc), dec_scaled(b, sc)), sc)
}
function decimal_mul(a: Dec, b: Dec) -> Dec { return dec_make(bigint_mul(a.m, b.m), a.scale + b.scale) }
function decimal_neg(a: Dec) -> Dec { return dec_make(bigint_neg(a.m), a.scale) }
function decimal_cmp(a: Dec, b: Dec) -> int {
var sc = a.scale
if b.scale > sc { sc = b.scale }
return bigint_cmp(dec_scaled(a, sc), dec_scaled(b, sc))
}
function decimal_eq(a: Dec, b: Dec) -> bool { return decimal_cmp(a, b) == 0 }
function decimal_scale(d: Dec) -> int { return d.scale }
# change the number of fractional digits: scaling up is exact, scaling down
# truncates toward zero (drop the extra low digits)
function decimal_rescale(d: Dec, places: int) -> Dec {
if places >= d.scale { return dec_make(dec_scaled(d, places), places) }
var diff = d.scale - places
var m = d.m
var i = 0
while i < diff { m = bigint_div_int(m, 10); i += 1 }
return dec_make(m, places)
}
function decimal_str(d: Dec) -> string {
var neg = ""
var m = d.m
if m.sign < 0 { neg = "-"; m = bigint_neg(m) }
var digits = bigint_str(m)
if d.scale == 0 { return neg + digits }
var nd = len(digits)
var pad = d.scale + 1 - nd
while pad > 0 { digits = "0" + digits; pad -= 1; nd += 1 }
let cut = nd - d.scale
let ip = digits[0..cut]
let fp = digits[cut..nd]
return neg + ip + "." + fp
}