Numeric Tower: BigInt, Rational, Complex

For new readers

A "numeric tower" is a hierarchy of number types (plain integer → arbitrary-precision integer → exact fraction → complex number) that a language promotes between automatically, so ordinary arithmetic never silently loses precision or overflows — the key point below is that this happens invisibly, without you writing any type annotation to opt in. The demo further down is runnable live in your browser. If you're new to why this matters in practice, the Paradigms Guide's numeric-tower section gives the shorter version first.

Ordinary Int arithmetic stays on the fast path until it can't: factorial(30) overflows a 64-bit integer partway through and the runtime silently promotes to an exact, arbitrary-precision BigInt — no annotation, no wraparound, no lost digits. 10 / 3 doesn't divide evenly, so instead of quietly rounding to a float it promotes to an exact Rational and prints 10/3. sqrt of a negative number produces a Complex value. The last two lines call into self_hosting/lib/math.patlang (hypot, is_prime, mean), a small PatLang-authored library built entirely on top of a handful of Host:: primitives (sqrt/floor/abs/numeric_kind) — the hybrid design that keeps the primitive surface small while still growing a real standard library. This card runs on the exact same in-browser WASM playground engine as the demos above (no rustc needed to try it live); the compiled path's arithmetic is exact because the emitted Rust program carries its own hand-rolled limb-based BigInt/Rational/Complex implementation rather than depending on a crate (compiled programs are single-file rustc invocations with no Cargo.toml), and a cross-implementation test in the Rust test suite verifies that hand-rolled bignum arithmetic is byte-identical, operation for operation, to the interpreter's own num-bigint-crate-based arithmetic — two independent implementations kept honest against each other.

PatLang source
# Stage 39 -- math library, built entirely in terms of the Host:: primitives
# added in rust-runtime/src/ir/hosts.rs (sqrt/pow/floor/ceil/round/trunc/abs/
# numeric_kind), so this file is plain PatLang, not new host functions. Include
# it via `include "lib/math.patlang"` (path relative to the including file);
# it is pulled in as a normal whole-chunk inclusion, not symbol-level shaking
# (that finer-grained mechanism is Stage 37 Part C, explicitly out of scope
# for this stage).

make a function called hypot takes a, b returns h
  return sqrt(a * a + b * b)
end

# Iterative (not recursive) so it stays O(1) stack regardless of n -- and,
# per the doc, a good smoke test for bignum promotion via repeated
# multiplication on a large n.
make a function called factorial takes n returns result
  let acc = 1
  let i = 2
  while i <= n do
    let acc = acc * i
    let i = i + 1
  end
  return acc
end

make a function called gcd takes a, b returns g
  let a = abs(a)
  let b = abs(b)
  while b != 0 do
    let t = b
    let b = a % b
    let a = t
  end
  return a
end

make a function called lcm takes a, b returns l
  if a == 0 then
    if b == 0 then
      return 0
    end
  end
  let g = gcd(a, b)
  return abs(a * b) / g
end

make a function called clamp takes x, lo, hi returns v
  if x < lo then
    return lo
  end
  if x > hi then
    return hi
  end
  return x
end

# sign(x): -1/0/1 for real x, via comparisons rather than re-deriving sign
# logic against float bit patterns; complex inputs are not orderable, so
# numeric_kind is used to reject them with a clear error rather than a
# nonsensical comparison result.
make a function called sign takes x returns s
  if numeric_kind(x) == "complex" then
    print("sign: complex input is not supported")
    return 0
  end
  if x > 0 then
    return 1
  end
  if x < 0 then
    return -1
  end
  return 0
end

# is_prime(n): trial division up to sqrt(n) -- exercises bignum via % for
# large n per the doc.
make a function called is_prime takes n returns result
  if n < 2 then
    return false
  end
  if n == 2 then
    return true
  end
  if n % 2 == 0 then
    return false
  end
  let limit = floor(sqrt(n))
  let i = 3
  while i <= limit do
    if n % i == 0 then
      return false
    end
    let i = i + 2
  end
  return true
end

make a function called sum takes xs returns total
  let total = 0
  let i = 0
  while i < xs.length do
    let total = total + xs[i]
    let i = i + 1
  end
  return total
end

make a function called mean takes xs returns m
  if xs.length == 0 then
    return 0
  end
  return sum(xs) / xs.length
end

# Numeric tower demo (Stages 36-39): a single narrative walking small-int
# arithmetic up through every rung of the tower -- fast-path Int, silent
# BigInt promotion on overflow, exact Rational on inexact division, Complex
# from sqrt of a negative number -- then closing with a couple of
# self_hosting/lib/math.patlang library calls to show the hybrid
# primitives+library design (sqrt/floor/abs/numeric_kind are Host::
# primitives; hypot/is_prime/mean are plain PatLang built on top of them).
#
# Concatenate after lib/math.patlang when compiling (matches the
# pos_demo.patlang/report_demo.patlang "concatenate after lib/X" convention
# used by self_hosting/tools/build_portfolio.patlang, and required because
# the self-hosted parser has no `include` support of its own -- unlike the
# Rust CLI's preprocess.rs, which is why math_compare_demo.patlang/
# math_lib_demo.patlang can get away with an `include "../lib/math.patlang"`
# line when run standalone via `pat --ir-run`/`--compare`). To run this file
# standalone the same way, prepend self_hosting/lib/math.patlang's contents
# yourself (e.g. `cat lib/math.patlang examples/numeric_tower_demo.patlang`).

# --- 1. Ordinary small-integer arithmetic: fast Int path, no promotion ---
let a = 6
let b = 7
print("6 * 7 = " + (a * b))

# --- 2. Overflow silently promotes to exact BigInt ---
# 20! is far beyond i64::MAX (~9.2e18); the tower detects the i64 overflow
# inside `*` and promotes both operands to BigInt automatically -- no
# annotation, no error, just an exact (not approximated) answer.
print("factorial(20) = " + factorial(20))
print("factorial(30) = " + factorial(30))

# --- 3. Inexact integer division produces an exact Rational, not a float ---
# 10 / 3 does not divide evenly, so instead of silently losing precision to
# a float, the tower promotes to an exact Rational and prints it as a
# reduced fraction "a/b".
print("10 / 3 = " + (10 / 3))
print("22 / 7 = " + (22 / 7))
# Division that DOES divide evenly stays a plain Int, for contrast:
print("12 / 4 = " + (12 / 4))

# --- 4. sqrt of a negative number produces a Complex value ---
print("sqrt(16) = " + sqrt(16))
print("sqrt(-4) = " + sqrt(-4))
print("numeric_kind(sqrt(-4)) = " + numeric_kind(sqrt(-4)))

# --- 5. Hybrid design in action: math.patlang library calls built on top of
#        the Host:: primitives ---
print("hypot(3, 4) = " + hypot(3, 4))
print("is_prime(999983) = " + is_prime(999983))
print("mean([1, 2, 3, 4, 5]) = " + mean([1, 2, 3, 4, 5]))

(not run yet)

Native run on the build machine:

6 * 7 = 42
factorial(20) = 2432902008176640000
factorial(30) = 265252859812191058636308480000000
10 / 3 = 10/3
22 / 7 = 22/7
12 / 4 = 3
sqrt(16) = 4
sqrt(-4) = 0+2i
numeric_kind(sqrt(-4)) = complex
hypot(3, 4) = 5
is_prime(999983) = true
mean([1, 2, 3, 4, 5]) = 3