# g_math
**Author**: Niels Erik Toren — v0.4.24
Pure-Rust, zero-float, deterministic multi-domain fixed-point arithmetic.
`g_math` computes with scaled integers only. No `f32`/`f64` appears anywhere in the
arithmetic, validation, or comparison paths (float conversions exist solely as
user-convenience `from_f64`/`to_f64` wrappers). Every operation produces bit-identical
results on every architecture, which makes the crate suitable for blockchain consensus,
financial auditing, and reproducible scientific computation.
```toml
[dependencies]
g_math = "0.4"
```
## Key concepts
**Four numeric domains.** Values are routed to the representation that holds them best:
| Binary fixed-point | Q-format scaled integer (Q16.16 … Q256.256) | integers, powers of two |
| Decimal fixed-point | base-10 scaled integer | decimal literals like `0.1`, `19.99` |
| Balanced ternary | base-3 scaled integer, digits {-1, 0, +1} | powers of three; TQ1.9 weight storage |
| Symbolic rational | exact numerator/denominator pair | everything (`1/3`, repeating decimals) |
**Canonical API (FASC).** The user-friendly wrapper. `gmath("...")` builds a lazy
expression tree; `evaluate(...)` runs it through a thread-local stack evaluator with
fixed-size workspaces (FASC = Fixed Allocation Stack Computation). Parsing classifies
each literal and routes it to its natural domain — `"0.1"` becomes decimal, `"255"`
binary, `"1/3"` symbolic.
**Fractal topology router.** Cross-domain arithmetic (e.g. decimal + binary) is
dispatched by a router that classifies operands via shadow denominator factoring
(strip factors of 2 → binary-exact, 2 and 5 → decimal-exact, 3 → ternary-exact) and
coerces both sides to the optimal shared domain through a small const lookup table.
Only when no shared domain exists does arithmetic fall back to exact rational.
**Tier N+1 computation.** Every transcendental and every accumulating operation
(dot products, decompositions, matrix chains) computes one tier *wider* than the
storage format — Q64.64 storage computes at Q128.128 — and rounds down once at the
end. Intermediates between chained operations stay at the wide tier
(`sin(exp(x))` never narrows mid-chain).
**UGOD — tiered graceful overflow.** Arithmetic is attempted at the current tier;
on overflow it promotes to a wider tier and retries. The top of the ladder is the
symbolic rational domain, so overflow degrades into a wider or exact representation
instead of wrapping or failing silently.
**Shadow system.** A `CompactShadow` (0–32 bytes, stack-only) can ride alongside any
approximated value, carrying its exact rational identity (`1/3`) or a constant
reference (π, e, √2, φ, ln 2, ln 10, γ). The router reads shadows to classify
domain-exactness without reparsing.
## Quick start
```rust
use g_math::canonical::{gmath, evaluate};
let expr = (gmath("100") + gmath("50")) / gmath("3");
let value = evaluate(&expr).unwrap();
println!("{}", value);
// Transcendentals chain lazily; intermediates stay at the wide compute tier
let y = evaluate(&gmath("1.5").exp().sin()).unwrap();
// Runtime strings
use g_math::canonical::gmath_parse;
let parsed = gmath_parse("3.14159265358979323846").unwrap();
// Feed a result back into a new expression without reparsing
use g_math::canonical::LazyExpr;
let year0 = evaluate(&gmath("1000")).unwrap();
let year1 = evaluate(&(LazyExpr::from(year0) * gmath("1.05"))).unwrap();
```
Imperative path, for hot loops:
```rust
use g_math::fixed_point::{FixedPoint, FixedVector, FixedMatrix};
let a = FixedPoint::from_str("0.5");
let b = a + FixedPoint::from_int(2); // direct integer arithmetic
let e = a.exp(); // direct engine call, no expression tree
let (s, c) = a.sincos(); // fused: one shared range reduction
```
## Entry points — pick by cost, not by correctness
There are two main ways to compute, plus specialised surfaces. They differ in
performance and ergonomics; **the answer they return does not depend on which you
pick.**
| **Canonical / router** — `gmath("…")` → `evaluate` | A lazy expression run through the fractal router: each value goes to its natural domain, and chained intermediates stay at the compute tier. ~300 ns/op. | Domains are mixed (decimal money + integers + symbolic fractions), the input is user/config-supplied, or you're chaining transcendentals and want the library to keep precision across the chain. |
| **Imperative / single-op** — `FixedPoint`, `DecimalFixed`, `FixedVector`, `FixedMatrix` | Direct `Copy` types; one call is one engine invocation, no expression tree. ~15 ns arithmetic. | You're in a hot loop and already know the domain — matrix code, ML inference, tight numeric kernels. |
| **Fused** — `imperative::fused` | Compute-tier accumulation (`softmax`, `silu`, `rms_norm_factor`, `euclidean_distance`). | ML hot paths, where intermediate materialisation would cost precision and time. |
| **Geometric / TQ1.9** | Manifolds and Lie groups; standalone ternary-quantised inference. | Geometric ML; weight-quantised inference. |
**Correctness is path-independent.** The imperative `DecimalFixed` surface and the
canonical `gmath()` surface compute through the same compute-tier engines and
round the same way — for a given input and precision they return the same,
correctly-rounded value. This is checked, not asserted: the decimal domain on
**both** paths — all 15 transcendentals plus `atan2` and `add`/`sub`/`mul`/`div`
— grades **0 LSB** against [mootable/decimal-scaled](https://github.com/mootable/decimal-scaled)'s
independent, adversarial mpmath corpus on every push. The harness, fixtures and
findings live on the [`validation/decimal-scaled`](https://github.com/nierto/gMath/tree/validation/decimal-scaled)
branch (`tests/decimal_contract_validation.rs`); all reference values are
generated by mpmath at high precision and committed as exact integer strings —
no floats anywhere in the comparison.
So choose by cost and ergonomics — router for mixed or chained work, imperative
for known-domain hot loops — and trust the number is the same either way.
## Profiles
Storage width is a compile-time choice via the `GMATH_PROFILE` environment
variable (default: `embedded`).
cargo build # embedded (default)
GMATH_PROFILE=scientific cargo build # 77-digit precision
```
**Custom integer/fraction split (realtime only).** The realtime profile lets you
move the binary point inside the 32-bit storage word via `GMATH_FRAC_BITS`:
```bash
GMATH_FRAC_BITS=10 GMATH_PROFILE=realtime cargo build # Q22.10 — ±2M range, 3 digits
GMATH_FRAC_BITS=24 GMATH_PROFILE=realtime cargo build # Q8.24 — ±127 range, 7 digits
```
Valid range is 2–30 fractional bits. The other profiles use fixed splits.
**Switching profiles:** each profile compiles different code paths via `cfg` flags.
Clear the incremental cache first, or stale artifacts will cause crashes:
```bash
rm -rf target/debug/incremental/
```
Pre-built lookup tables are checked in; a default build takes ~2 seconds.
`--features rebuild-tables` regenerates them from `build.rs` (~20 minutes,
pure-Rust generation: π via Machin's formula, e via factorial series, √2 via
continued fractions — zero runtime dependencies).
## API overview
### Canonical (`g_math::canonical`)
| `gmath("...")` | build a `LazyExpr` from a literal (deferred parsing) |
| `gmath_parse(&str)` | build from a runtime string, returns `Result` |
| `evaluate(&LazyExpr)` | evaluate → `Result<StackValue, _>` |
| `evaluate_sincos(&LazyExpr)` | sin and cos from one shared range reduction |
| `evaluate_sinhcosh(&LazyExpr)` | sinh and cosh from one shared exp pair |
| `evaluate_matrix(&LazyMatrixExpr)` | evaluate a matrix expression chain |
| `set_gmath_mode("compute:output")` / `reset_gmath_mode()` | force compute/output domains (`auto`, `binary`, `decimal`, `symbolic`, `ternary`) |
| `LazyExpr::from(StackValue)` | feed a result back into a new expression |
`LazyExpr` supports the basic operators `+`, `-`, `*`, `/`, unary `-`, plus the
18 transcendental methods listed below. Literals may be decimals (`"0.1"`),
integers, fractions (`"1/3"`), repeating decimals (`"0.333..."`), hex/ternary
(`"0x1F"`, `"0t10"`), or named constants (`"pi"`, `"e"`, `"sqrt2"`, `"phi"`).
`LazyMatrixExpr` is the matrix analog of scalar chain persistence: `Add`, `Sub`,
`Mul`, `ScalarMul`, `Transpose`, `Neg`, `Inverse`, `Exp`, `Log`, `Sqrt`, `Pow` —
the whole chain runs at the wide tier with a single downscale at
`evaluate_matrix()`. `DomainMatrix` holds per-element domain-tagged values for
mixed-domain matrices.
A `gmath!()` proc-macro that pre-parses decimal and integer literals at compile
time exists in the repository (`g_math_macros/`) but is not yet published to
crates.io; fractions, constants, and hex/ternary literals fall back to the
runtime function.
### Transcendentals
18 functions, available on `LazyExpr`, `FixedPoint`, and `DecimalFixed`.
Dedicated engines (table-driven or Newton-Raphson, computed at tier N+1):
| `exp` | integer part by squaring + 3-stage table lookup + Taylor remainder |
| `ln` | multiplicative decomposition, 3-stage tables + Taylor |
| `sqrt` | integer Newton-Raphson |
| `sin`, `cos` | Cody-Waite range reduction + Horner Taylor (`sincos` fuses both) |
| `atan`, `atan2` | 3-level argument reduction + Taylor |
Composed from the dedicated engines, still at the wide tier:
| `tan` | sin/cos |
| `pow(x, y)` | exp(y·ln x) |
| `asin`, `acos` | atan(x/√(1−x²)), π/2 − asin |
| `sinh`, `cosh` | (eˣ ∓ e⁻ˣ)/2 (`sinhcosh` fuses both on one exp pair) |
| `tanh` | (e²ˣ−1)/(e²ˣ+1) |
| `asinh`, `acosh`, `atanh` | log forms |
On `FixedPoint`, every function also has a fallible `try_*` variant returning
`Result<_, OverflowDetected>`.
### Imperative (`g_math::fixed_point`)
- **`FixedPoint`** — `Copy` Q-format scalar. Arithmetic operators, comparisons,
`abs`, `from_str`/`from_int`/`from_raw`, all 18 transcendentals, `sincos`,
`sinhcosh`, float conversions for interop.
- **`FixedVector`** — `dot`, `length`, `length_fused`, `normalized`,
`distance_to`, `cross`, `outer_product`, `map`, indexing, operators. Dot
products accumulate at the compute tier.
- **`FixedMatrix`** — `identity`, `diagonal`, `from_fn`, `from_slice`,
`transpose`, `trace`, `row`/`col`, mat-mat and mat-vec multiply (compute-tier
dots per entry), `kronecker`, `submatrix`.
### Decimal (`DecimalFixed<DECIMALS>`)
Base-10 scaled integer with a const-generic decimal-place count. `0.1` is stored
exactly. Full basic arithmetic in pure decimal (add, subtract, multiply, divide,
negate, plus a batched multiply), and its own native transcendental engines
(all 18, plus fused `sincos` and `sinhcosh`) — no round-trip through binary, so
results are correctly rounded *in the decimal domain*. Conversions:
`try_convert`/`convert_with_rounding` between precisions,
`to_binary_q256`/`from_binary_q256`.
### Fused operations (`imperative::fused`)
Whole patterns computed at the wide tier with one downscale at the end:
| `sqrt_sum_sq(&[x])` | √(Σ xᵢ²) |
| `euclidean_distance(&a, &b)` | √(Σ (aᵢ−bᵢ)²) |
| `softmax(&scores)` | numerically stable softmax |
| `rms_norm_factor(&x, eps)` | 1/√(mean(x²)+ε) |
| `silu(x)` | x/(1+e⁻ˣ) |
### Linear algebra (`imperative::decompose`, `derived`, `matrix_functions`)
- **Decompositions**: LU (Doolittle, partial pivoting), QR (Householder),
Cholesky, SVD (Golub-Kahan), symmetric eigenvalues (Jacobi), Schur (Francis QR).
Each returns a struct with `solve`/`determinant`/`inverse` where applicable,
plus iterative refinement on LU.
- **Derived**: `frobenius_norm`, `norm_1`, `norm_inf`, `solve`, `solve_spd`,
`determinant`, `inverse`, `inverse_spd`, `pseudoinverse`, `rank`, `nullspace`,
`least_squares`, `condition_number_1`/`_2`.
- **Matrix functions**: `matrix_exp` (Padé + scaling-squaring), `matrix_sqrt`
(Denman-Beavers), `matrix_log` (inverse scaling-squaring), `matrix_pow` —
all chained through `ComputeMatrix` at the wide tier.
### Geometry (`imperative::{manifold, lie_group, curvature, projective, fiber_bundle}`)
- **Manifolds** (trait: `exp_map`, `log_map`, `distance`, `parallel_transport`,
`inner_product`): Euclidean, Sphere, Hyperbolic (hyperboloid model), SPD,
Grassmannian, Stiefel, products.
- **Lie groups** (trait adds `lie_exp`, `lie_log`, `hat`/`vee`, `adjoint`,
`bracket`, `act`): SO(3) via closed-form Rodrigues, SE(3) via closed-form
V-matrix, plus SO(n), GL(n), O(n), SL(n) via matrix exp/log.
- **Differential geometry**: Christoffel symbols, Riemann/Ricci/scalar/sectional
curvature, geodesic integration, parallel transport along curves.
- **Projective**: homogeneous coordinates, projective transforms, cross-ratios,
stereographic projection, Möbius transformations (real and complex).
- **Fiber bundles**: trivial, vector (connection coefficients, horizontal lift,
parallel transport, curvature 2-form), principal (transition cocycles).
### ODE solvers (`imperative::ode`)
RK4 (fixed step), Dormand-Prince RK45 (adaptive), symplectic Störmer-Verlet
(energy-preserving, for Hamiltonian systems). Weighted sums accumulate at the
compute tier; step halving is an exact bit shift.
### Tensors (`imperative::tensor`, `tensor_decompose`)
Arbitrary-rank tensors: contraction, outer product, trace, index raising/lowering
via a metric, (anti)symmetrization. Decompositions: `truncated_svd`,
`tucker_decompose` (HOSVD), `cp_decompose` (ALS).
### Balanced ternary (`domains::balanced_ternary`)
Basic arithmetic (add, subtract, multiply, divide, negate — checked and
unchecked variants) across six tier formats from TQ8.8 up to TQ256.256, plus
trit packing: `pack_trits`/`unpack_trits` store 5 balanced trits {-1, 0, +1}
per byte. Ternary is also reachable through the canonical API via `0t` literals
or `set_gmath_mode("...:ternary")`; transcendentals on ternary values route
through the binary engines.
### TQ1.9 ternary inference (`g_math::tq19`, feature `inference`)
Standalone 2-byte balanced-ternary format for neural network weights: 1 integer
trit + 9 fractional trits, range ±1.5, ~4.3 decimal digits of uniform precision.
Because weights are {-1, 0, +1} at the trit level, dot products need no
multiplications.
- `TQ19Matrix` with `matvec`, `matvec_batch` (and rayon `_par` variants)
- `tq19_dot`, `trit_dot`, `packed_trit_dot` (5 trits/byte), `packed_trit_matvec`
- AVX2 SIMD on x86_64 with runtime detection and scalar fallback
### Serialization (`imperative::serialization`)
Profile-tagged big-endian encoding for `FixedPoint`, `FixedVector`,
`FixedMatrix`, `Tensor`, `ManifoldPoint` — compact, deterministic, suitable for
wire transport and consensus. Optional serde support behind `--features serde`.
## Rounding
Each domain has a defined rounding behavior:
| Binary fixed-point | round-half-even (banker's) | round-half-away-from-zero | round-to-nearest, ties toward +∞ |
| Decimal fixed-point | round-half-away-from-zero | round-half-away-from-zero | round-half-away-from-zero |
| Balanced ternary | truncate toward zero | truncate toward zero | — (transcendentals route via binary) |
**The binary tie-breaking inconsistency is a development artifact, not a design
statement.** Each operation's rounding was chosen and validated independently
against reference values during iterative development, and the three rules were
never retroactively unified. It is documented here so nobody mistakes it for
numerical intent.
**Why it matters less than it looks: the downscale is the rounding that counts.**
Everything beyond a lone storage-tier multiply or divide — every transcendental,
dot product, decomposition, matrix chain, and fused op — runs at tier N+1 with
double the fractional bits, then rounds back to storage exactly once. In those
paths the per-op multiply/divide tie rules never fire; the single wide→storage
downscale (round-to-nearest) is the only rounding the result ever sees, and the
extra fractional bits absorb the intermediate error before that final round.
The mul/div tie rules apply only to direct storage-tier arithmetic, where all
three rules are round-to-nearest variants — they produce identical results
except on exact half-ULP ties, and each individual op stays within half an ULP
regardless of which rule breaks the tie.
All rounding is implemented in integer arithmetic and is therefore
deterministic across platforms — including the inconsistency itself.
## Precision and validation
The crate's accuracy claims are defined by its test suite, not slogans. The
approach:
- Reference values are generated with mpmath at 50–250 digit precision and
embedded in the tests as exact strings — never computed with floats.
- Transcendentals are validated pointwise against those references on every
profile. The wide-tier strategy means the final rounding step selects the
nearest representable value for the storage format in the measured cases.
- Linear algebra, manifolds, Lie groups, ODE, and tensor tests combine
structural checks (PA=LU, QᵀQ=I, exp/log roundtrips) with concrete
mpmath-validated numerical comparisons.
```bash
cargo test --release
```
Honest limits worth knowing:
- **Input representation**: values like `0.3` or `1/3` are repeating fractions
in binary and carry up to half an ULP of representation error before any
computation happens. No finite-precision system avoids this; the decimal and
symbolic domains exist precisely so you can pick a representation in which
your inputs *are* exact.
- **Conditioning**: error in a solved system scales with the condition number
of the matrix. An ill-conditioned system (e.g. Hilbert matrices) amplifies
input error by orders of magnitude in any finite precision; iterative
refinement recovers the residual but not the lost input information.
- **Determinism**: whatever the error is, it is the *same* error on every
platform — results are bit-identical across x86_64, ARM, and RISC-V.
## Feature flags
| `infinite-precision` | BigInt tier for the symbolic rational domain (pulls in `num-bigint`) |
| `serde` | `Serialize`/`Deserialize` for FixedPoint, vectors, matrices, tensors |
| `inference` | TQ1.9 ternary inference ops + rayon parallel matvec |
| `rebuild-tables` | regenerate lookup tables from `build.rs` (~20 min) |
| `realtime` / `compact` / `embedded` / `balanced` / `scientific` | select profile via Cargo feature instead of `GMATH_PROFILE` |
| `legacy-tests` | compile legacy test suites |
No feature gates around core functionality — all domains, transcendentals, wide
integers (I256/I512/I1024), and tiered overflow are always compiled.
## Author note
I build keystone libraries from first principles — this one because I needed
precise, deterministic fixed-point arithmetic and wanted the numeric domains to
coexist instead of collapsing everything into one representation. Use it, stress
it, break it, and tell me where it fails.
If you want to support the work:
| Bitcoin (BTC) | bc1qwf78fjgapt2gcts4mwf3gnfkclvqgtlg4gpu4d |
| Ethereum (ETH) | 0xf38b517Dd2005d93E0BDc1e9807665074c5eC731 / nierto.eth |
| Monero (XMR) | 8BPaSoq1pEJH4LgbGNQ92kFJA3oi2frE4igHvdP9Lz2giwhFo2VnNvGT8XABYasjtoVY2Qb3LVHv6CP3qwcJ8UnyRtjWRZ5 |
## Disclaimer
This software is provided **"as is"**, without warranty of any kind, express or
implied. Use of this library is entirely at your own risk. In no event shall the
author or contributors be held liable for any damages arising from the use or
inability to use this software.
## License
Licensed under either of
* [Apache License, Version 2.0](LICENSE-APACHE)
* [MIT License](LICENSE-MIT)
at your option.