num-valid
Robust numerical computation in Rust with type-level safety guarantees.
Why num-valid?
Floating-point arithmetic is tricky. NaN propagates silently, overflow produces Infinity, and precision loss is common. num-valid solves these problems by enforcing correctness at the type level.
Quick Example
use RealNative64StrictFinite;
use TryNew;
// ✅ Valid value - compiles and runs safely
let x = try_new?;
let sqrt_x = x.sqrt; // Always safe - no NaN surprises!
// ❌ Invalid value - caught immediately
let bad = try_new; // Returns Err
# Ok::
Core Benefits
✅ Type Safety - No more NaN surprises
✅ Generic Precision - Switch between f64 and arbitrary-precision seamlessly
✅ Zero Cost - Validation overhead only where you choose
✅ Complete - Trigonometry, logarithms, complex numbers, and more
✅ Production Ready - 96%+ test coverage, comprehensive error handling
Installation
Add to your Cargo.toml:
[]
= "0.2"
= "0.1"
Feature Flags
| Feature | Description |
|---|---|
rug |
Enables arbitrary-precision arithmetic using rug. See LGPL-3.0 notice below. |
backtrace |
Enables backtrace capture in error types for debugging. Disabled by default for performance. |
Example with features:
[]
= { = "0.2", = ["rug", "backtrace"] }
Quick Start
-
Safety by Construction with Validated Types: Instead of using raw primitives like
f64ornum::Complex<f64>directly,num-validencourages the use of validated wrappers likeRealValidatedandComplexValidated. These types guarantee that the value they hold is always valid (e.g., finite) according to a specific policy, eliminating entire classes of numerical bugs. -
Support for Real and Complex Numbers: The library supports both real and complex numbers, with specific validation policies for each type.
-
Layered and Extensible Design: The library has a well-defined, layered, and highly generic architecture. It abstracts the concept of a "numerical kernel" (the underlying number representation and its operations) from the high-level mathematical traits.
The architecture can be understood in four main layers:
- Layer 1: Raw Trait Contracts (in the
kernelsmodule):- The
RawScalarTrait,RawRealTrait, andRawComplexTraitdefine the low-level, "unchecked" contract for any number type. - These traits are the foundation, providing a standard set of
unchecked_*methods. - The contract is that the caller must guarantee the validity of inputs. This is a strong design choice, separating the raw, potentially unsafe operations from the validated, safe API.
- Why? This design separates the pure, high-performance computational logic from the safety and validation logic. It creates a clear, minimal contract for backend implementors and allows the validated wrappers in Layer 3 to be built on a foundation of trusted, high-speed operations.
- The
- Layer 2: Validation Policies:
- The
NumKerneltrait is the bridge between the raw types and the validated wrappers. - It bundles together the raw real/complex types and their corresponding validation policies (e.g.,
StrictFinitePolicy,DebugValidationPolicy, etc.). This allows the entire behavior of the validated types to be configured with a single generic parameter. - Why? It acts as the central policy configuration point. By choosing a
NumKernel, a user selects both a numerical backend (e.g.,f64vs.rug) and a set of safety rules (e.g.,StrictFinitePolicyvs.DebugValidationPolicy<StrictFinitePolicy>) with a single generic parameter. This dramatically simplifies writing generic code that can be configured for different safety and performance trade-offs.
- The
- Layer 3: Validated Wrappers:
RealValidated<K>andComplexValidated<K>are the primary user-facing types.- These are newtype wrappers that are guaranteed to hold a value that conforms to the
NumKernelK(and to the validation policies therein). - They use extensive macros to implement high-level traits. The logic is clean: perform a check (if necessary) on the input value, then call the corresponding
unchecked_*method from the raw trait, and then perform a check on the output value before returning it. This ensures safety and correctness. - Why? These wrappers use the newtype pattern to enforce correctness at the type level. By construction, an instance of
RealValidatedis guaranteed to contain a value that has passed the validation policy, eliminating entire classes of errors (likeNaNpropagation) in user code.
- Layer 4: High-Level Abstraction Traits:
- The
FpScalartrait is the central abstraction, defining a common interface for all scalar types. It uses an associated type sealed type (Kind), to enforce that a scalar is either real or complex, but not both. RealScalarandComplexScalarare specialized sub-traits ofFpScalarthat serve as markers for real and complex numbers, respectively.- Generic code in a consumer crate is written against these high-level traits.
- The
RealValidatedandComplexValidatedstructs from Layer 3 are the concrete implementors of these traits. - Why? These traits provide the final, safe, and generic public API. Library consumers write their algorithms against these traits, making their code independent of the specific numerical kernel being used.
- The
This layered approach is powerful, providing both high performance (by using unchecked methods internally) and safety (through the validated wrappers). The use of generics and traits makes it extensible to new numerical backends (as demonstrated by the rug implementation).
- Layer 1: Raw Trait Contracts (in the
-
Multiple Numerical Backends. At the time of writing, 2 numerical backends can be used:
- the standard (high-performance) numerical backend is the one in which the raw floating point and complex numbers are described by the Rust's native
f64andnum::Complex<f64>types, as described by the ANSI/IEEE Std 754-1985; - an optional (high-precision) numerical backend is available if the library is compiled with the optional flag
--features=rug, and uses the arbitrary precision raw typesrug::Floatandrug::Complexfrom the Rust libraryrug.
- the standard (high-performance) numerical backend is the one in which the raw floating point and complex numbers are described by the Rust's native
-
Comprehensive Mathematical Library. It includes a wide range of mathematical functions for trigonometry, logarithms, exponentials, and more, all implemented as traits (e.g., Sin, Cos, Sqrt) and available on the validated types.
-
Numerically Robust Implementations. The library commits to numerical accuracy. As an example, by using
NeumaierSumfor its defaultstd::iter::Sumimplementation to minimize precision loss. -
Robust Error Handling: The library defines detailed error types for various numerical operations, ensuring that invalid inputs and outputs are properly handled and reported. Errors are categorized into input and output errors, with specific variants for different types of numerical issues such as division by zero, invalid values, and subnormal numbers.
-
Conditional Backtrace Capture: Backtrace capture in error types is disabled by default for maximum performance. Enable the
backtracefeature flag for debugging to get full stack traces in error types. -
Conditional
CopyImplementation: Validated types (RealValidated<K>,ComplexValidated<K>) automatically implementCopywhen their underlying raw types support it. This enables zero-cost pass-by-value semantics for native 64-bit types while correctly requiringClonefor non-Copybackends likerug. -
Comprehensive Documentation: The library includes detailed documentation for each struct, trait, method, and error type, making it easy for users to understand and utilize the provided functionality. Examples are provided for key functions to demonstrate their usage and expected behavior.
Key Features
- Type-Level Safety: Validated wrappers prevent NaN/Infinity propagation at compile time
- Dual Backends: Native
f64for speed, arbitrary-precisionrugfor accuracy - Complete Math Library: Trigonometry, logarithms, exponentials, hyperbolic functions, and more
- Generic API: Write once, run with any precision level
- Robust Error Handling: Detailed error types with optional backtrace support
- Numerically Accurate: Uses Neumaier compensated summation to minimize precision loss
- Zero-Copy Conversions: Efficient bytemuck integration for binary data
- Conditional Copy: Automatic
Copyimplementation when underlying types support it - Production Ready: 96%+ test coverage, comprehensive documentation
Architecture (For Library Developers)
The library uses a sophisticated 4-layer design:
Layer 1: Raw Trait Contracts
Low-level unchecked_* operations on primitives (f64, Complex<f64>, rug::Float, etc.). These assume caller-validated inputs for maximum performance.
Layer 2: Validation Policies
The NumKernel trait bundles raw types with validation policies like:
StrictFinitePolicy- Always validates (debug + release)DebugValidationPolicy<P>- Validates only in debug mode
Layer 3: Validated Wrappers
RealValidated<K> and ComplexValidated<K> enforce correctness:
- Validate input → call
unchecked_*→ validate output - Type aliases:
RealNative64StrictFinite,ComplexRugStrictFinite<100>, etc.
Layer 4: High-Level Traits
Generic interface for all scalars:
FpScalar- Universal scalar traitRealScalar- Real number specializationComplexScalar- Complex number specialization
This design separates performance-critical operations from safety guarantees, enabling both high speed and correctness.
Why This Design?
Layer 1 separates pure computational logic from validation, creating a minimal contract for backend implementors and enabling high-performance operations.
Layer 2 acts as the central policy configuration point. By choosing a NumKernel, you select both a numerical backend and safety rules with a single generic parameter.
Layer 3 uses the newtype pattern to enforce correctness at the type level. By construction, instances are guaranteed valid, eliminating entire error classes.
Layer 4 provides the final safe, generic public API. Library consumers write algorithms against these traits, making code independent of the specific numerical kernel.
This layered approach provides both high performance (unchecked methods internally) and safety (validated wrappers). Generics and traits make it extensible to new numerical backends.
Compiler Requirement
This library requires Rust nightly due to use of unstable features:
trait_aliasassociated_const_equalityerror_generic_member_access
Set up nightly:
The library will support stable Rust once these features are stabilized.
2. Basic Usage
The central idea in num-valid is to use validated types instead of raw primitives like f64. These wrappers guarantee their inner value is always valid (e.g., not NaN or Infinity).
Most common type: RealNative64StrictFinite - wraps f64 with strict finite validation.
use ;
use TryNew;
// Create a validated number
let x = try_new?;
// Panicking version - for known-valid inputs
let sqrt_x = x.sqrt;
assert_eq!;
// Fallible version - for runtime validation
let neg = try_new?;
match neg.try_sqrt
# Ok::
4. Generic Functions
Write once, run with any precision:
use ;
use One;
// Generic function works with any RealScalar type
// Use with native f64 precision
let angle = try_from_f64?;
let result = pythagorean_identity;
let one = one;
// The result should be very close to 1.0 (sin²x + cos²x = 1)
let diff = .abs;
assert!;
# Ok::
5. Arbitrary Precision (Optional)
Enable the rug feature for arbitrary-precision arithmetic:
[]
= { = "0.2", = ["rug"] }
Then use the same generic function with high precision:
#
# Ok::
6. Complex Numbers
Full support for complex arithmetic:
use ;
use ;
// Create complex number: 3 + 4i
let re = from_f64;
let im = from_f64;
let z = new_complex;
// Complex operations
let magnitude = z.abs; // |3 + 4i| = 5.0
assert_eq!;
let conjugate = z.conjugate; // 3 - 4i
let reciprocal = z.reciprocal; // 1/z
// Complex math functions
let exp_z = z.exp;
let log_z = z.ln;
let sqrt_z = z.sqrt;
7. Zero-Copy Conversions
Safe, zero-copy conversions with bytemuck:
use RealNative64StrictFinite;
use try_from_bytes;
// From bytes to validated type
let bytes = 42.0_f64.to_ne_bytes;
let validated: &RealNative64StrictFinite = try_from_bytes.unwrap;
assert_eq!;
// Invalid values automatically rejected
let nan_bytes = f64NAN.to_ne_bytes;
assert!;
Documentation
📚 API Documentation - Complete API reference with examples
🏗️ Architecture Guide - Deep dive into the 4-layer design, implementation patterns, and how to contribute
Contributing
Contributions are welcome! Please read the Architecture Guide to understand the design philosophy before submitting PRs.
Key resources for contributors:
- docs/ARCHITECTURE.md - Essential reading for understanding the codebase
- .github/copilot-instructions.md - Detailed development guidelines