feat(types): Huge + Angle + Percent polish numeric types (#55)
Types phase 5 (polish). A splice-on-demand numeric runtime (runtime/native/numeric.ludic, built on the inline Math.* trig) behind three namespaces: - Huge.* — idle big numbers (normalized mantissa x 10^exponent): from/add/ sub/mul/neg/cmp/sign/mantissa/exp/str (scientific 1.23e45). Display-scale, not lockstep-exact (BigInt/Decimal for exactness). - Angle.* — auto-wrapping radians: from_degrees/to_degrees/wrap/sin/cos/add/ diff (shortest signed rotation)/lerp (shortest arc). - Percent.* — clamped [0,1]: clamp/of/lerp/apply. Remaining phase-5 items are already covered (duration=Duration.*, rune=Unicode.*, i64=long) or need a type-checking pass (handle, typed name, other sized ints) — tracked for later. Wired: parser splice trigger (g_uses_numeric), emit_call dispatch, reseeded seed, a self-asserting example (examples/library/numeric.ludic + feat_case), per-symbol docs + inventory. All suites green incl. golden renders byte- identical and the bootstrap fixpoint. NOTE: fixed `const`s lower to raw-int-typed values (emit_call N_CONST), which breaks fixed comparisons — the runtime uses inline fixed literals instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
539f258d92
commit
5dc8394f22
33 changed files with 23162 additions and 21092 deletions
165
runtime/native/numeric.ludic
Normal file
165
runtime/native/numeric.ludic
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# ============================================================================
|
||||
# numeric.ludic — three "polish" numeric types, written in Ludic:
|
||||
# Huge.* — idle/incremental big numbers (mantissa x 10^exponent)
|
||||
# Angle.* — an auto-wrapping angle in radians
|
||||
# Percent.* — a value clamped to [0, 1]
|
||||
#
|
||||
# ludicc splices this file into any program that mentions `Huge.*`, `Angle.*`
|
||||
# or `Percent.*`. It builds on the deterministic fixed-point `Math.*` namespace
|
||||
# (spliced inline, no runtime of its own), so it works in a plain tool as well
|
||||
# as a game. The namespaces (emit_call.ludic) alias each method to the matching
|
||||
# `huge_*` / `angle_*` / `percent_*` function below.
|
||||
# ============================================================================
|
||||
|
||||
# ---- Huge: mantissa + exponent, for idle-game magnitudes -------------------
|
||||
#
|
||||
# A value is m x 10^e with the mantissa m normalized to [1, 10) (or 0 for zero);
|
||||
# e is an unbounded int, so magnitudes far past 1e308 are representable. The
|
||||
# mantissa is a Q16.16 `fixed`, giving ~4 significant digits — this is a
|
||||
# DISPLAY-SCALE number for showing 1.23e45, NOT a lockstep-exact value; keep it
|
||||
# out of the deterministic simulation and use BigInt/Decimal when exactness
|
||||
# matters.
|
||||
|
||||
# NOTE: fixed values live as inline `10.0` / `1.0` literals, not `const`s — a
|
||||
# `const` reference lowers to its raw integer value typed `int` (emit_call.ludic
|
||||
# N_CONST), which silently breaks fixed-point comparisons.
|
||||
|
||||
property Huge { m: fixed = 0.0, e: int = 0 }
|
||||
|
||||
# normalize (mantissa, exponent) so the mantissa lands in [1, 10)
|
||||
function huge_make(m: fixed, e: int) -> Huge {
|
||||
let h = new Huge
|
||||
if m == 0.0 { h.m = 0.0; h.e = 0; return h }
|
||||
var neg = false
|
||||
var mm = m
|
||||
if mm < 0.0 { neg = true; mm = 0.0 - mm }
|
||||
var ee = e
|
||||
while mm >= 10.0 { mm = mm / 10.0; ee = ee + 1 }
|
||||
while mm < 1.0 { mm = mm * 10.0; ee = ee - 1 }
|
||||
if neg { mm = 0.0 - mm }
|
||||
h.m = mm; h.e = ee
|
||||
return h
|
||||
}
|
||||
|
||||
# an int -> Huge (built via i64 so the whole int range normalizes cleanly)
|
||||
function huge_from(value: int) -> Huge {
|
||||
if value == 0 { return huge_make(0.0, 0) }
|
||||
var neg = false
|
||||
var v: long = value
|
||||
if v < 0 { neg = true; v = 0 - v }
|
||||
var e = 0
|
||||
var p: long = 1
|
||||
while p * 10 <= v { p = p * 10; e = e + 1 }
|
||||
let raw: long = (v * 65536) / p # v / 10^e in Q16.16 (in [1,10))
|
||||
let rawi: int = raw
|
||||
var m: fixed = as_fixed(rawi)
|
||||
if neg { m = 0.0 - m }
|
||||
return huge_make(m, e)
|
||||
}
|
||||
|
||||
function huge_sign(a: Huge) -> int {
|
||||
if a.m > 0.0 { return 1 }
|
||||
if a.m < 0.0 { return -1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
function huge_neg(a: Huge) -> Huge { return huge_make(0.0 - a.m, a.e) }
|
||||
function huge_mul(a: Huge, b: Huge) -> Huge {
|
||||
if a.m == 0.0 or b.m == 0.0 { return huge_make(0.0, 0) }
|
||||
return huge_make(a.m * b.m, a.e + b.e)
|
||||
}
|
||||
|
||||
function huge_add(a: Huge, b: Huge) -> Huge {
|
||||
if a.m == 0.0 { return b }
|
||||
if b.m == 0.0 { return a }
|
||||
var big = a
|
||||
var small = b
|
||||
if b.e > a.e { big = b; small = a }
|
||||
let diff = big.e - small.e
|
||||
if diff > 8 { return big } # negligible at display precision
|
||||
var sm = small.m
|
||||
var k = 0
|
||||
while k < diff { sm = sm / 10.0; k = k + 1 }
|
||||
return huge_make(big.m + sm, big.e)
|
||||
}
|
||||
|
||||
function huge_sub(a: Huge, b: Huge) -> Huge { return huge_add(a, huge_neg(b)) }
|
||||
|
||||
function huge_cmp(a: Huge, b: Huge) -> int {
|
||||
let sa = huge_sign(a)
|
||||
let sb = huge_sign(b)
|
||||
if sa != sb { if sa > sb { return 1 }; return -1 }
|
||||
if sa == 0 { return 0 }
|
||||
var c = 0
|
||||
if a.e != b.e { if a.e > b.e { c = 1 } else { c = 0 - 1 } }
|
||||
else { if a.m > b.m { c = 1 } else { if a.m < b.m { c = 0 - 1 } else { c = 0 } } }
|
||||
if sa < 0 { c = 0 - c }
|
||||
return c
|
||||
}
|
||||
|
||||
function huge_mantissa(a: Huge) -> fixed { return a.m }
|
||||
function huge_exp(a: Huge) -> int { return a.e }
|
||||
|
||||
# scientific text like "1.23e45" (two-decimal mantissa); plain "0" for zero
|
||||
function huge_str(a: Huge) -> pointer {
|
||||
if a.m == 0.0 { return "0" }
|
||||
var neg = ""
|
||||
var m = a.m
|
||||
if m < 0.0 { neg = "-"; m = 0.0 - m }
|
||||
var ip: int = floor(m) # 1..9
|
||||
let frac: fixed = m - fixed(ip)
|
||||
var dd: int = floor(frac * 100.0 + 0.5) # round the two decimals to nearest
|
||||
var ex = a.e
|
||||
if dd >= 100 { dd = 0; ip = ip + 1 } # rounding carried into the ones place
|
||||
if ip >= 10 { ip = 1; ex = ex + 1 } # ...and on into the exponent
|
||||
var ds = string(dd)
|
||||
if dd < 10 { ds = "0" + ds }
|
||||
return neg + string(ip) + "." + ds + "e" + string(ex)
|
||||
}
|
||||
|
||||
# ---- Angle: an auto-wrapping radian angle ----------------------------------
|
||||
|
||||
# wrap any radian value into the half-open range [-pi, pi). pi = 3.14159265,
|
||||
# tau = 6.28318531 (inline literals — see the note above on fixed consts).
|
||||
function angle_wrap(a: fixed) -> fixed {
|
||||
var x = a
|
||||
while x >= 3.14159265 { x = x - 6.28318531 }
|
||||
while x < 0.0 - 3.14159265 { x = x + 6.28318531 }
|
||||
return x
|
||||
}
|
||||
|
||||
function angle_from_degrees(d: fixed) -> fixed { return angle_wrap(Math.deg_to_rad(d)) }
|
||||
function angle_to_degrees(a: fixed) -> fixed { return Math.rad_to_deg(a) }
|
||||
function angle_sin(a: fixed) -> fixed { return Math.sin(a) }
|
||||
function angle_cos(a: fixed) -> fixed { return Math.cos(a) }
|
||||
function angle_add(a: fixed, b: fixed) -> fixed { return angle_wrap(a + b) }
|
||||
|
||||
# the shortest signed rotation from `a` to `b`, in [-pi, pi)
|
||||
function angle_diff(a: fixed, b: fixed) -> fixed { return angle_wrap(b - a) }
|
||||
|
||||
# interpolate from `a` toward `b` along the shortest arc (t is a fixed 0..1)
|
||||
function angle_lerp(a: fixed, b: fixed, t: fixed) -> fixed { return angle_wrap(a + angle_diff(a, b) * t) }
|
||||
|
||||
# ---- Percent: a value clamped to [0, 1] ------------------------------------
|
||||
|
||||
# clamp any fixed into [0, 1]
|
||||
function percent_clamp(v: fixed) -> fixed {
|
||||
if v < 0.0 { return 0.0 }
|
||||
if v > 1.0 { return 1.0 }
|
||||
return v
|
||||
}
|
||||
|
||||
# num / den as a clamped ratio (0 when den is 0)
|
||||
function percent_of(num: fixed, den: fixed) -> fixed {
|
||||
if den == 0.0 { return 0.0 }
|
||||
return percent_clamp(num / den)
|
||||
}
|
||||
|
||||
# linear interpolation a..b by a clamped t
|
||||
function percent_lerp(a: fixed, b: fixed, t: fixed) -> fixed {
|
||||
let p = percent_clamp(t)
|
||||
return a + (b - a) * p
|
||||
}
|
||||
|
||||
# value scaled by a clamped percent
|
||||
function percent_apply(value: fixed, p: fixed) -> fixed { return value * percent_clamp(p) }
|
||||
Loading…
Add table
Add a link
Reference in a new issue