hyperreal 0.11.0

Exact rational and computable real arithmetic in Rust
Documentation

hyperreal provides exact rational arithmetic, symbolic real values, and lazy computable real approximation.

It is useful when code needs more information than an f64 can provide: structural sign facts, exact zero/nonzero knowledge, exact rational access, bounded sign refinement, and recognizable forms such as pi, e, square roots, logarithms, and rational trig constants.

Numeric Model

hyperreal is built around three layers that deliberately keep exact and symbolic information available before approximation:

  • Rational: is the exact arithmetic base. It stores arbitrary-precision numerator/denominator values and performs exact reduction, dyadic detection, square extraction, shared-denominator dot products, and exact IEEE-754 import.
  • Computable: is the lazy approximation layer. It represents exact-real expression graphs such as sums, products, inverses, roots, logs, trig kernels, and shared constants. It approximates only when a caller asks for a binary precision, then caches the result and conservative sign/magnitude facts.
  • Real: is the public symbolic scalar. It stores an exact rational scale plus a compact symbolic class and, when needed, a Computable certificate. Common classes include exact one, powers/products of pi and e, selected square roots, logarithms, trig forms, and factored constant products.
  • RealStructuralFacts: conservative public facts about sign, zero status, magnitude, and exact-rational state.
  • Simple: a small Lisp-like expression parser, enabled by the optional simple feature.

Relationship to Other Crates

  • realistic_blas uses hyperreal::Real as its default exact/symbolic scalar backend. It forwards hyperreal structural facts through its Scalar type and adds vector, matrix, transform, and retained-geometry facts around them.
  • liminal can consume hyperreal::Real directly, using structural facts, finite f64 approximations, and bounded sign refinement before robust fallback.
  • hypersolve is the experimental solver layer. Its current direction is to evaluate constraints through symbolic references to variables, reuse reductions across iterations, and route repeated residual and geometry kernels through hyperreal and realistic_blas instead of rebuilding scalar expressions from scratch.

hyperreal owns scalar representation and approximation. It does not own vector or matrix algebra, and it does not decide geometry topology. The stack is layered intentionally: scalar facts live here, object-level facts live in realistic_blas/geometry layers, and decision procedures live above them.

Problems This Stack Targets

hyperreal and the surrounding stack are aimed at problems where ordinary floating-point arithmetic is fast but loses too much information too early. The target workloads tend to have many cheap structural decisions, a smaller number of expensive exact or high-precision decisions, and repeated kernels where cached structure can be reused.

Representative problem families include:

  • robust geometric predicates such as orientation, sidedness, intersection, and clearance tests
  • CAD-style geometric constraint solving, where many residuals share variables, transforms, and symbolic subexpressions
  • PCB routing and autorouting constraints, where exact topology and clearance decisions matter before approximate coordinates are useful
  • toolpath planning and manufacturing geometry, where small sign or ordering errors can change topology
  • matrix/vector transform stacks with many affine, diagonal, triangular, homogeneous, point, or direction-specialized cases
  • exact-rational or symbolic scalar workloads that should remain exact until a caller explicitly asks for digits

The implementation strategy is intentionally thin:

  • Preserve exact rational, dyadic, symbolic, sign, zero, magnitude, and approximation-cache facts at the scalar layer.
  • Carry inexpensive object facts at higher layers, such as known point/direction w, affine form, diagonal or triangular matrix structure, retained transform facts, shared determinant/cofactor work, and known coordinate zeros.
  • Reduce symbolically before approximating. Common forms such as exact rationals, pi, e, roots, logarithms, and selected trig constants should simplify or classify without entering a generic approximation kernel.
  • Defer approximation until a decision or output precision requires it, then cache the result and any conservative sign or magnitude information.
  • Prefer deterministic fast paths guarded by cheap facts over speculative probing inside dense loops.
  • Keep similar functions flat: a fast path should improve the family it targets without making neighboring functions erratic.

Current State

The crate is active, benchmark-driven, and no longer just a direct port of computable real ideas. Current implementation work includes:

  • exact rational and dyadic fast paths
  • dedicated identity constructors for common exact ones and zeros
  • cached internal constants for pi, tau, e, common square roots, and common logarithms
  • symbolic classes for selected pi, e, sqrt, ln, sin(pi*q), and tan(pi*q) forms
  • exact trig, inverse-trig, logarithm, exponential, and inverse-hyperbolic shortcuts where the input structure is recognizable
  • argument reduction and prescaled kernels for transcendental approximation
  • structural sign, zero, nonzero, magnitude, and exact-rational queries
  • bounded sign refinement through sign_until and refine_sign_until
  • cached approximation and structural-fact propagation through computable nodes
  • borrowed arithmetic paths for Rational and Real
  • shared-denominator and signed-product-sum hooks used by matrix/vector callers to delay rational canonicalization
  • serde support for expression structure, excluding transient caches and abort signals
  • dispatch tracing and targeted benchmark suites for scalar, approximation, symbolic, adversarial, and stack-facing regressions
  • source-level READMEs for Rational, Real, and Computable, plus structural_facts.txt for planned and implemented fact propagation

Installation

[dependencies]
hyperreal = "0.10.6"

With the Simple parser and calculator binary:

[dependencies]
hyperreal = { version = "0.10.6", features = ["simple"] }

Feature flags:

Feature Default Purpose
simple no Enables Simple and the package calculator binary.

Examples

Exact Rationals

Rational is the exact base layer. It is useful for imported measurements, test fixtures, small coefficients, and any value that should not become binary floating-point noise before the rest of the stack sees it.

use hyperreal::Rational;
use std::convert::TryFrom;

let a = Rational::fraction(7, 8).unwrap();
let b = Rational::fraction(9, 10).unwrap();

assert_eq!(a + b, Rational::fraction(79, 40).unwrap());

// Finite floats import by exact IEEE-754 decoding, not by decimal rounding.
let half = Rational::try_from(0.5_f64).unwrap();
assert_eq!(half, Rational::fraction(1, 2).unwrap());

// Decimal and fraction strings parse into exact rationals.
let decimal: Rational = "12.125".parse().unwrap();
let fraction: Rational = "97/8".parse().unwrap();
assert_eq!(decimal, fraction);

Symbolic Reals

Real keeps a rational scale plus a symbolic/computable class. That lets common forms simplify or expose facts before approximation is needed.

use hyperreal::{Rational, Real, RealSign, ZeroKnowledge};

let x = Real::new(Rational::new(2)).sqrt().unwrap();
let y = Real::new(Rational::new(3)).sqrt().unwrap();
let z = x * y;

let approx: f64 = z.into();
assert!(approx > 2.44 && approx < 2.45);

let half = Real::new(Rational::fraction(1, 2).unwrap());
let angle = half * Real::pi();
let cosine = angle.cos().unwrap();

// Recognizable symbolic/trig forms can answer facts without a full equality
// proof or high-precision decimal expansion.
let facts = cosine.structural_facts();
assert_eq!(facts.zero, ZeroKnowledge::Zero);
assert_eq!(cosine.refine_sign_until(-32), Some(RealSign::Zero));

Computable Approximation

Computable is the lazy approximation layer. It stores an expression graph and only computes a scaled integer approximation when a precision is requested.

use hyperreal::{Computable, Rational, RealSign};

let x = Computable::rational(Rational::fraction(7, 5).unwrap()).sin();
let scaled = x.approx(-40);

assert_ne!(scaled, 0.into());

// Sign refinement asks for only enough precision to decide the sign down to a
// requested floor. The result may be `None` for unresolved or truly difficult
// cases, so callers can decide whether to refine further or use a fallback.
let near_pi = Computable::pi().add(Computable::rational(
    Rational::fraction(-22, 7).unwrap(),
));
assert_eq!(near_pi.sign_until(-8), Some(RealSign::Positive));

Structural Facts

Structural facts are conservative certificates. They are designed for filters, predicates, and higher-level kernels that want to avoid approximation unless a decision actually requires it.

use hyperreal::{Rational, Real, RealSign, ZeroKnowledge};

let value = Real::new(Rational::new(2)).sqrt().unwrap();
let facts = value.structural_facts();

assert_eq!(facts.sign, Some(RealSign::Positive));
assert_eq!(facts.zero, ZeroKnowledge::NonZero);
assert!(!facts.exact_rational);
assert_eq!(value.refine_sign_until(-64), Some(RealSign::Positive));

let exact = Real::new(Rational::fraction(9, 18).unwrap());
let exact_facts = exact.structural_facts();

assert_eq!(exact.exact_rational(), Some(Rational::fraction(1, 2).unwrap()));
assert_eq!(exact_facts.sign, Some(RealSign::Positive));
assert_eq!(exact_facts.zero, ZeroKnowledge::NonZero);
assert!(exact_facts.exact_rational);

Facts are conservative. Missing sign or magnitude information means the fact was not proven cheaply.

Stack-Facing Filters

The surrounding geometry stack uses hyperreal values as scalar certificates: try cheap structural facts first, use finite approximation when that is enough, and only then request bounded refinement.

use hyperreal::{Rational, Real, RealSign};

fn classify_positive(value: &Real) -> Option<bool> {
    if let Some(sign) = value.structural_facts().sign {
        return Some(sign == RealSign::Positive);
    }

    if let Some(approx) = value.to_f64_approx() {
        if approx > 1e-12 {
            return Some(true);
        }
        if approx < -1e-12 {
            return Some(false);
        }
    }

    value
        .refine_sign_until(-80)
        .map(|sign| sign == RealSign::Positive)
}

let offset = Real::pi() - Real::new(Rational::fraction(22, 7).unwrap());
assert_eq!(classify_positive(&offset), Some(true));

This pattern is the intended handoff to realistic_blas and liminal: cheap facts route the common case, approximation is delayed until useful, and bounded refinement remains available for hard predicate boundaries.

Simple Expressions

Requires the simple feature.

use hyperreal::Simple;

let expr: Simple = "(* (+ pi pi) (sin (/ 1 5)))".parse().unwrap();
let value = expr.evaluate(&Default::default()).unwrap();

let _: f64 = value.into();

Simple supports arithmetic, roots, powers, logs, exponentials, trig, inverse trig, inverse hyperbolic functions, integers, decimals, fractions, pi, and e.

It can also be useful for small configuration- or test-facing formulas:

use hyperreal::{Rational, Simple};

let expr: Simple = "(sqrt (/ 49 64))".parse().unwrap();
let value = expr.evaluate(&Default::default()).unwrap();

assert_eq!(value.exact_rational(), Some(Rational::fraction(7, 8).unwrap()));

Conversions

Supported conversions include:

  • integer types to Rational and Real
  • finite f32/f64 to Rational and Real by exact IEEE-754 decoding
  • Real to f32/f64 by approximation
  • Real::to_f64_approx() for borrowed finite approximation used by filters

Float import rejects NaN and infinities. to_f64_approx() returns None when no finite f64 approximation can be produced.

Finite decimal and fraction strings parse losslessly through Rational; the parser also accepts leading + signs and digit separators where the rational parser supports them. Scientific notation is not a supported exact text format. -0.0 imports as exact rational zero, so IEEE signed-zero information is not preserved.

PartialEq on Real is structural, not a full computer-algebra equality proof. Two expressions may print/debug similarly and approximate identically while still comparing unequal if they were built through different computable expression histories. Use structural facts, exact-rational extraction, or explicit approximation/refinement when semantic equivalence rather than representation identity is the question.

Performance Notes

Performance shortcuts are intentionally documented next to the code that uses them. The main techniques are:

  • keep exact rational and dyadic values outside generic computable graphs
  • build identity values through dedicated constructors and clone cached named constants instead of rebuilding them
  • preserve lightweight symbolic classes only where benchmarks show value
  • reduce trig and exponential arguments before entering series kernels
  • use endpoint and tiny-argument transforms for inverse trig and inverse hyperbolic functions
  • answer structural queries from certificates before refining approximations
  • use borrowed arithmetic to reduce expression-graph cloning in callers such as realistic_blas and liminal

Benchmark suites:

cargo bench --bench library_perf
cargo bench --bench numerical_micro
cargo bench --bench borrowed_ops
cargo bench --bench float_convert
cargo bench --bench scalar_micro
cargo bench --bench adversarial_transcendentals

The generated benchmark summary is in benchmarks.md. Hand-maintained profiling anchors and regression goals for the Rational, Real, and Computable paths are in PERFORMANCE.md.

Run dispatch tracing separately:

cargo bench --bench dispatch_trace --features dispatch-trace

The generated trace summary is in dispatch_trace.md.

Development

Common checks:

cargo fmt --check
cargo test
cargo bench --bench numerical_micro

When adding a shortcut, add a focused correctness test and a benchmark row for the smallest affected surface. Keep the shortcut only if it improves the target without regressing broader realistic_blas or liminal benchmarks.

Provenance and Acknowledgements

hyperreal descends from the realistic project and continues that project's interest in practical computable real arithmetic.

Special thanks to siefkenj, whose contributions improved realistic and hyperreal.

References

These are the papers and books which contribute ideas or methods to this crate. They are listed here in MLA style for easy citation.

License

(C) https://github.com/timschmidt Apache-2.0 / MIT

(C) https://github.com/tialaramex/realistic/ Apache-2.0

(C) https://github.com/siefkenj Apache-2.0