This document describes the numeric tower implementation in values/numeric_tower.go.
Status: Stable (2026-02-05)
Scheme's numeric tower is biased toward algebra — symbolic manipulation and exact representation. The tower is designed so that exact operations on exact inputs produce exact results (R7RS §6.2.2), and types preserve mathematical identity where possible. In an ideal algebraic system, (* pi 1) yields pi (not a float64 approximation), and (log (exp 1)) yields exactly 1 (not 0.9999999999999998).
Wile does not implement symbolic evaluation. It maps R7RS numerics onto Go's concrete types, which creates a pragmatic boundary: operations are only as precise as their runtime representation allows.
Wile's arithmetic is optimized for the common case where values stay within machine-type domains:
| Domain | Condition | Guarantee |
|---|---|---|
int64 |
Integer operands with results fitting int64 | Exact, hardware-speed arithmetic |
float64 |
Float operands, or Integer operands < 2^53 promoted to Float | IEEE 754 precision, hardware speed |
complex128 |
Complex operands, or Float/Integer promoted to Complex | Two float64 components, hardware speed |
When operations remain within these domains, results are both fast and as precise as the representation permits.
All inexact floating-point types in Wile — both machine-width (Float, Complex) and arbitrary-precision (BigFloat, BigComplex) — follow IEEE 754 semantics for special values (Inf, NaN). This is a deliberate design decision:
values.BigFloatandvalues.BigComplexMUST represent both Inf and NaN, regardless of what Go'smath/big.Floatsupports natively. Go'sbig.Floatsupports Inf (big.Float.SetInf) but has no NaN representation; Wile extends beyond this with internal state to track NaN.- IEEE 754 is the single reference spec for all Inf/NaN behavior. When someone asks "what does
(+ +inf.0 x)do?", the answer is "IEEE 754" regardless of whetherxisFloatorBigFloat. - Code paths stay uniform:
BigFloat.Addhandles Inf/NaN the same wayfloat64addition does. No branching on "which numeric type am I?" to get special-value semantics right. - Operations stay in their domain:
float64 × float64 → float64.BigFloat × BigFloat → BigFloat. Inf/NaN is never a reason to switch domains or demote types.
This eliminates the need for Inf/NaN guard paths in the dispatch table — the promotion lattice works correctly for all values, including special values.
Certain operations promote values to arbitrary-precision types (BigInteger, BigFloat, Rational, BigComplex). Once a value enters the Big* domain, subsequent operations produce Big* results — there is no automatic demotion back to machine types during computation (only Simplify demotes after the fact).
This promotion is correct per R7RS but has practical consequences:
- Big* arithmetic is significantly slower (heap-allocated, no hardware acceleration)
- Mixed operations (e.g.,
Float × BigComplex) promote to the lattice LUB as usual
The following algebraic optimizations are acknowledged but not planned for the current version:
- Symbolic identity preservation:
(* pi 1) → pi,(+ x 0) → x(requires symbolic evaluation engine) - Algebraic simplification:
(log (exp x)) → x,(sqrt (* x x)) → (abs x)(requires term rewriting) - Singleton transcendental constants: Representing
e,π, etc. as singleton values so that operations like(log e) → 1produce exact results instead of floating-point approximations. This is philosophically aligned with Scheme's algebraic bias but crosses into Computer Algebra System (CAS) territory. No mainstream Scheme implementation (Guile, Racket, Chez, Chibi, MIT Scheme) does this — all representeandπas inexact flonums. R7RS §6.2.6 explicitly excludeslog,sin,cos,expfrom the list of exactness-preserving operations. Scope creep is the main risk: each recognized identity ((log e) → 1,(sin π) → 0,(log (* e e)) → 2) is a special case, and a complete solution requires term rewriting. If pursued, this would be an extension (e.g.,(scheme symbolic)library), not a change to the core numeric tower. - Machine-type constraint flags: Engine-level flags to keep ALL computations within
int64,float64, orcomplex128(desired feature, not yet a priority). This would prevent unexpected promotion to Big* types at the cost of raising implementation-restriction errors when results exceed machine-type range.
These are valid future directions but require significant design work beyond the current numeric tower architecture.
The numeric tower uses direct dispatch — each numeric type's Add, Subtract, Multiply, Divide, and Compare methods handle all 7 incoming types via type switches. This is the intentional architecture.
A unified tower dispatch (TowerAdd, etc.) was prototyped but abandoned because:
- Exact complex bug: Linear promotion (Integer → BigInteger → Rational → Float → Complex) loses exactness when combining exact reals with complex numbers
- Battle-tested code: Direct dispatch has been tested across all 49 type combinations
- Explicit cases: Each type switch case is explicit and debuggable
// Simplify reduces a number to simpler type when possible
func Simplify(n Number) Number
// Exactness classification
func ExactnessOf(n Number) Exactness // Returns Exact or Inexacttype Exactness int
const (
Exact Exactness = iota
Inexact
)Deleted (2026-02-05): NumericRank, Rank, Promote, PromoteBoth, CommonRank, BinaryOp, TowerAdd, TowerSubtract, TowerMultiply, TowerDivide, TowerCompare.
Direct dispatch implements a lattice with two dimensions:
BigComplex
↗ ↑ ↖
Complex BigFloat (exact BigComplex path)
↑ ↗ ↑ ↑
Float Rational ────┘
↑ ↑
Integer → BigInteger
| A ↓ / B → | Integer | BigInteger | Rational | Float | BigFloat | Complex | BigComplex |
|---|---|---|---|---|---|---|---|
| Integer | Integer¹ | BigInteger | Rational | Float | BigFloat | Complex | BigComplex |
| BigInteger | BigInteger | BigInteger | Rational | Float | BigFloat | Complex | BigComplex |
| Rational | Rational | Rational | Rational | Float | BigFloat | Complex | BigComplex |
| Float | Float | Float | Float | Float | BigFloat | Complex | BigComplex |
| BigFloat | BigFloat | BigFloat | BigFloat | BigFloat | BigFloat | BigComplex | BigComplex |
| Complex | Complex | Complex | Complex | Complex | BigComplex | Complex | BigComplex |
| BigComplex | BigComplex | BigComplex | BigComplex | BigComplex | BigComplex | BigComplex | BigComplex |
¹ Integer + Integer may overflow to BigInteger
| A ↓ / B → | Exact | Inexact |
|---|---|---|
| Exact | Exact | Inexact |
| Inexact | Inexact | Inexact |
Where:
- Exact: Integer, BigInteger, Rational, BigComplex (with exact parts)
- Inexact: Float, BigFloat, Complex, BigComplex (with inexact parts)
Simplify reduces numbers to simpler types when no information is lost:
| Input | Simplification |
|---|---|
| BigComplex with zero imaginary | → real part (recursive) |
| Complex with zero imaginary | → Float → possibly Integer |
| BigFloat that is an integer | → BigInteger → possibly Integer |
| Float that is a whole number | → Integer |
| Rational with denominator 1 | → BigInteger → possibly Integer |
| BigInteger that fits int64 | → Integer |
All types use consistent panic-based error handling for invalid inputs:
- Unknown types:
panic(werr.WrapForeignErrorf(werr.ErrNotANumber, "context: ...")) - Division by zero:
panic(werr.WrapForeignErrorf(werr.ErrDivisionByZero, "context: ..."))
No bare sentinel panics remain. All panics wrap the sentinel with location context, enforced by the noBareSentinelPanic ruleguard rule.
All 49 type combinations (7×7) are handled without panics for valid operations.
Coverage tests are in:
numeric_tower_coverage_test.go— 245-case coverage matrix (7×7×5 operations)numeric_lattice_test.go— Lattice-based promotion model validation
Run tests:
go test -v ./values/ -run "TestNumericTower|TestLattice"- R7RS §6.2.1 — Numerical types (tower definition)
- R7RS §6.2.2 — Exactness (contagion rules)
- R7RS §6.2.3 — Implementation restrictions
values/CLAUDE.md— Package documentation- Design rationale: Unified tower dispatch was prototyped and abandoned (see "Why Direct Dispatch" above)