Skip to main content

Crate num_valid

Crate num_valid 

Source
Expand description

num-valid is a Rust library designed for robust, generic, and high-performance numerical computation. It provides an extensible framework for working with both real and complex numbers, addressing the challenges of floating-point arithmetic through validation policies and fallible operations.

§Key Features & Architecture

  • Safety by Construction with Validated Types: Instead of using raw primitives like f64 or Complex<f64> directly, num-valid encourages the use of validated wrappers like RealValidated and ComplexValidated. With an always-validating policy, these types guarantee that the value they hold conforms to that policy (for example, is finite), eliminating entire classes of numerical bugs.

  • Enhanced Type Safety with Hashing Support: Types validated with policies that guarantee finite values (like Native64StrictFinite) automatically implement Eq and Hash. This enables their use as keys in HashMap and other hash-based collections, while maintaining compatibility with the library’s PartialOrd-based comparison functions. The hashing implementation properly handles IEEE 754 floating-point edge cases and ensures that mathematically equal values produce identical hash codes.

  • 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 (kernels module):

      • The RawScalarTrait, RawRealTrait, and RawComplexTrait define 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.

      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.

    • Layer 2: Validation Policies (core::policies module):

      • The NumKernel trait 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., Native64StrictFinite, Native64StrictFiniteInDebug, etc.). This allows the entire behavior of the validated types to be configured with a single generic parameter.

      It acts as the central policy configuration point. By choosing a NumKernel, a user selects both a numerical backend (e.g., f64/Complex<f64> or rug::Float/rug::Complex) and a set of validation policies (e.g., StrictFinitePolicy vs. DebugValidationPolicy) for real and complex numbers, with a single generic parameter. This dramatically simplifies writing generic code that can be configured for different safety and performance trade-offs.

      Policy Comparison:

      Type AliasPolicyDebug BuildRelease BuildUse Case
      RealNative64StrictFiniteStrictFinitePolicy✅ Validates✅ ValidatesSafety-critical code, HashMap keys
      RealNative64StrictFiniteInDebugDebugValidationPolicy✅ Validates❌ No validationPerformance-critical hot paths
      RealNative32StrictFiniteStrictFinitePolicy✅ Validates✅ ValidatesNative single-precision code
      RealRugStrictFinite<P>StrictFinitePolicy✅ Validates✅ ValidatesHigh-precision calculations

      Key Differences:

      • StrictFinite: Always validates, implements Eq + Hash (safe for HashMap keys)
      • StrictFiniteInDebug: Zero overhead in release, catches bugs during development
    • Layer 3: Validated Wrappers:

      • RealValidated<K> and ComplexValidated<K> are the primary user-facing types.
      • These are newtype wrappers that are guaranteed to hold a value that conforms to the NumKernel K (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.

      These wrappers use the newtype pattern to enforce correctness at the type level. By construction, an instance of RealValidated has passed the validation policy. Always-validating policies therefore eliminate entire classes of errors (like NaN propagation) in user code.

    • Layer 4: High-Level Abstraction Traits:

      • The FpScalar trait 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.
      • RealScalar and ComplexScalar are specialized sub-traits of FpScalar that serve as markers for real and complex numbers, respectively.
      • Generic code in a consumer crate is written against these high-level traits.
      • The RealValidated and ComplexValidated structs from Layer 3 are the concrete implementors of these traits.

      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.

    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).

  • 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 Rust’s native f32 and f64 and their Complex<f32>/Complex<f64> types;
    • 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 types rug::Float and rug::Complex from the Rust library rug.
  • Comprehensive Mathematical Library. It includes a wide range of mathematical functions for trigonometry, logarithms, exponentials, and more, all implemented as traits (e.g., functions::Sin, functions::Cos, functions::Sqrt) and available on the validated types.

  • Numerically Robust Implementations. The library commits to numerical accuracy:

    • Neumaier compensated summation (NeumaierSum) for the default std::iter::Sum implementation to minimize precision loss.
    • BLAS/LAPACK-style L2 norm (algorithms::vector_norms::vector_norm_l2) with incremental scaling to prevent overflow and underflow.
  • 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 backtrace feature flag for debugging to get full stack traces in error types.

  • Conditional Copy Implementation: Validated types (RealValidated, ComplexValidated) automatically implement Copy when their underlying raw types support it. This enables zero-cost pass-by-value semantics for native 64-bit types while correctly requiring Clone for non-Copy backends like rug.

  • Constrained Scalar Types: The scalars module provides validated wrapper types for common domain-specific constraints: scalars::AbsoluteTolerance (non-negative), scalars::RelativeTolerance (non-negative, with conversion to absolute), scalars::PositiveRealScalar (strictly > 0), and scalars::NonNegativeRealScalar (≥ 0). scalars::HashableScalar wraps finite real or complex FpScalar values to provide Eq and Hash for hash-based collections. The constrained scalar types are generic over any RealScalar backend.

  • Approximate Equality Comparisons: All validated types implement the approx crate traits (AbsDiffEq, RelativeEq, UlpsEq) for tolerance-based floating-point comparisons. This is essential for testing numerical algorithms and handling floating-point precision limitations. The approx crate is re-exported for convenience.

  • 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.

  • Zero-Copy Conversions: The native 64-bit validated types (RealNative64StrictFinite, RealNative64StrictFiniteInDebug) implement bytemuck::CheckedBitPattern and bytemuck::NoUninit, enabling safe, zero-copy conversions between byte representations and validated types. This is particularly useful for interoperability with binary data formats, serialization, and performance-critical code. The conversion automatically validates the bit pattern at runtime, rejecting invalid values (NaN, Infinity, subnormal numbers) before exposing a validated reference.

§Architecture Deep Dive

For a comprehensive understanding of the library’s architecture, design patterns, and implementation details, see the Architecture Guide (also available in the repository at docs/ARCHITECTURE.md).

The guide covers:

  • Detailed explanation of the 4-layer design with examples and rationale
  • How to add a new numerical backend (step-by-step guide with checklist)
  • How to implement new mathematical functions (complete template)
  • Error handling architecture (two-phase error model, backtrace handling)
  • Performance considerations (zero-cost abstractions, optimization patterns)
  • Macro system (code generation patterns and proposals)
  • Design patterns reference (newtype, marker traits, sealed traits, etc.)

For Contributors: Reading the architecture guide is essential before submitting PRs, as it explains the design philosophy and implementation conventions that keep the codebase consistent and maintainable.

§Compiler Requirement: Rust Nightly

This library currently requires the nightly toolchain because it uses some unstable Rust features which, at the time of writing (August 2026), are not yet available in stable or beta releases. These features are:

  • trait_alias: For ergonomic trait combinations
  • error_generic_member_access: For advanced error handling

If these features are stabilized in a future Rust release, the library will be updated to support the stable compiler.

To use the nightly toolchain, please run:

rustup install nightly
rustup override set nightly

This will set your environment to use the nightly compiler, enabling compatibility with the current version of the library.

§Getting Started

This guide will walk you through the basics of using num-valid.

§1. Add num-valid to your Project

Add the following to your Cargo.toml (change the versions to the latest ones):

[dependencies]
num-valid = "0.6.0"    # Change to the latest version

The easiest way to create validated numbers is using the real! and complex! macros:

use num_valid::{real, complex};
use std::f64::consts::PI;

// Create validated real numbers from literals
let x = real!(3.14159);
let angle = real!(PI / 4.0);
let radius = real!(5.0);

// Create validated complex numbers (real, imaginary)
let z1 = complex!(1.0, 2.0);   // 1 + 2i
let z2 = complex!(-3.0, 4.0);  // -3 + 4i

// StrictFinite values remain finite; fallible operations report domain and overflow errors.
let area = real!(PI) * radius.clone() * radius;  // Type-safe area calculation
let product = z1 * z2;                           // Safe complex arithmetic

Why use macros?

  • Ergonomic: Concise syntax similar to Rust’s built-in vec![], format!(), etc.
  • Safe with StrictFinite: Validates at construction time and after operations, rejecting non-finite values
  • Clear panics: Invalid literals (like real!(f64::NAN)) panic immediately with descriptive messages
  • Compile-time friendly: Works with constants and expressions evaluated at compile time

For single-precision values, use real32! and complex32!, the f32 counterparts of real! and complex!:

use num_valid::{real32, complex32};

let x = real32!(3.14159);
let y = real32!(std::f32::consts::PI);
let z = complex32!(1.0, 2.0);

For runtime values or when you need error handling, see the manual construction methods below.

§Feature Flags

The library provides several optional feature flags:

FeatureDescription
rugEnables the high-precision arbitrary arithmetic backend using rug. See LGPL-3.0 notice below.
backtraceEnables backtrace capture in error types for debugging. Disabled by default for performance.

Example with multiple features:

[dependencies]
num-valid = { version = "0.6.0", features = ["rug", "backtrace"] }

§2. Core Concept: Validated Types

The central idea in num-valid is to use validated types instead of raw primitives like f64. These are wrappers that guarantee their inner value is always valid (e.g., not NaN or Infinity) according to a specific policy.

The most common type you’ll use is RealNative64StrictFinite, which wraps an f64 and ensures it’s always finite, both in Debug and Release mode. For a similar type wrapping an f64 that ensures it’s always finite, but with the validity checks executed only in Debug mode (providing performance equal to the raw f64 type), you can use RealNative64StrictFiniteInDebug.

§3. Your First Calculation

Let’s perform a square root calculation. You’ll need to bring the necessary traits into scope.

// Import the validated type and necessary traits
use num_valid::{*, functions::{Sqrt, SqrtRealInputErrors, SqrtRealErrors}};
use num_valid::TryNew;

// 1. Create a validated number. try_new() returns a Result.
let x = RealNative64StrictFinite::try_new(4.0).unwrap();

// 2. Use the direct method for operations.
// This will panic if the operation is invalid (e.g., sqrt of a negative).
let sqrt_x = x.sqrt();
assert_eq!(sqrt_x.as_raw_scalar_ref(), &2.0);

// 3. Use the `try_*` methods for error handling.
// This is the safe way to handle inputs that might be out of the function's domain.
let neg_x = RealNative64StrictFinite::try_new(-4.0).unwrap();
let sqrt_neg_x_result = neg_x.try_sqrt();

// The operation fails gracefully, returning an Err.
assert!(sqrt_neg_x_result.is_err());

// The error gives information about the problem that occurred
if let Err(SqrtRealErrors::Input(source)) = sqrt_neg_x_result {
    assert!(matches!(source, SqrtRealInputErrors::NegativeValue { .. }));
}

§4. Writing Generic Functions

The real power of num-valid comes from writing generic functions that work with any supported numerical type. You can do this by using the FpScalar and RealScalar traits as bounds.

use num_valid::{
    RealScalar, RealNative64StrictFinite, FpScalar,
    functions::{Abs, Sin, Cos},
};
use num::One;
use num_valid::TryNew;
use try_create::IntoInner;

// This function works for any type that implements RealScalar,
// including f64, RealNative64StrictFinite, and RealRugStrictFinite.
fn verify_trig_identity<T: RealScalar>(angle: T) -> T {
    // We can use .sin(), .cos(), and arithmetic ops because they are
    // required by the RealScalar trait.
    let sin_x = angle.clone().sin();
    let cos_x = angle.cos();
    (sin_x.clone() * sin_x) + (cos_x.clone() * cos_x)
}

// Define a type alias for convenience
type MyReal = RealNative64StrictFinite;

// Call it with a validated f64 type.
let angle = MyReal::try_from_f64(0.5).unwrap();
let identity = verify_trig_identity(angle);

// The result should be very close to 1.0.
let one = MyReal::one();
assert!((identity - one).abs().into_inner() < MyReal::try_from_f64(1e-15).unwrap());

If the rug feature is enabled, you could call the exact same function with a high-precision number changing only the definition of the alias type MyReal. For example, for real numbers with precision of 100 bits:

// Import the rug-based types
use num_valid::{
    RealScalar, RealRugStrictFinite, FpScalar,
    functions::{Abs, Sin, Cos},
};
use num::One;
use num_valid::TryNew;
use try_create::IntoInner;

// This function works for any type that implements RealScalar,
// including f64, RealNative64StrictFinite, and RealRugStrictFinite.
fn verify_trig_identity<T: RealScalar>(angle: T) -> T {
    // We can use .sin(), .cos(), and arithmetic ops because they are
    // required by the RealScalar trait.
    let sin_x = angle.clone().sin();
    let cos_x = angle.cos();
    (sin_x.clone() * sin_x) + (cos_x.clone() * cos_x)
}

// Define a type alias for convenience - real number with precision of 100 bits
type MyReal = RealRugStrictFinite<100>;

// Initialize it with an f64 value.
let angle = MyReal::try_from_f64(0.5).unwrap();
let identity = verify_trig_identity(angle);

// The result should be very close to 1.0.
let one = MyReal::one();
assert!((identity - one).abs().into_inner() < MyReal::try_from_f64(1e-15).unwrap());

§5. Working with Complex Numbers

The library also provides validated complex number types:

use num_valid::{*, functions::Abs};
use num::Complex;
use num_valid::TryNew;

// Create a validated complex number
let z = ComplexNative64StrictFinite::try_new(Complex::new(3.0, 4.0)).unwrap();

// Complex operations work the same way
let magnitude = z.abs();
assert_eq!(magnitude.as_raw_scalar_ref(), &5.0); // |3 + 4i| = 5

§6. Zero-Copy Conversions with Bytemuck

For performance-critical applications working with binary data, the library provides safe, zero-copy conversions through the bytemuck crate. The native 64-bit validated types (RealNative64StrictFinite, RealNative64StrictFiniteInDebug) implement CheckedBitPattern and NoUninit, enabling safe conversions that automatically validate bit patterns:

use num_valid::*;
use bytemuck::checked::try_from_bytes;

// Convert from bytes - validation happens automatically
let value = 42.0_f64;
let bytes = value.to_ne_bytes();
let validated: &RealNative64StrictFinite = try_from_bytes(&bytes).unwrap();
assert_eq!(validated.as_raw_scalar_ref(), &42.0);

// Invalid values (NaN, Infinity, subnormals) are rejected
let nan_bytes = f64::NAN.to_ne_bytes();
assert!(try_from_bytes::<RealNative64StrictFinite>(&nan_bytes).is_err());

This is particularly useful for:

  • Loading validated numbers from binary file formats
  • Interoperability with serialization libraries
  • Processing validated numerical data in performance-critical loops
  • Working with memory-mapped files containing numerical data

For comprehensive examples including slice operations and error handling, see the test module in the backends::native::validated module source code.

§7. Validated Tolerances and Constrained Scalars

The scalars module provides validated wrapper types for common domain-specific constraints:

use num_valid::*;
use num_valid::scalars::{
    AbsoluteTolerance, RelativeTolerance,
    PositiveRealScalar, NonNegativeRealScalar, HashableScalar,
};
use num_valid::TryNew;
use std::collections::HashMap;

// AbsoluteTolerance: Non-negative tolerance (≥ 0)
let abs_tol = AbsoluteTolerance::try_new(
    RealNative64StrictFinite::from_f64(1e-10)
)?;

// RelativeTolerance: Non-negative, with conversion to absolute tolerance
let rel_tol = RelativeTolerance::try_new(
    RealNative64StrictFinite::from_f64(1e-6)
)?;
let reference = RealNative64StrictFinite::from_f64(1000.0);
let abs_from_rel = rel_tol.absolute_tolerance(reference); // = 1e-3

// PositiveRealScalar: Strictly positive (> 0), rejects zero
let step_size = PositiveRealScalar::try_new(
    RealNative64StrictFinite::from_f64(0.001)
)?;
// PositiveRealScalar::try_new(zero) would return an error!

// NonNegativeRealScalar: Non-negative (≥ 0), accepts zero
let threshold = NonNegativeRealScalar::try_new(
    RealNative64StrictFinite::from_f64(0.0)
)?;  // OK!

// HashableScalar: finite real or complex value usable as a hash-map key
let key = HashableScalar::try_new(RealNative64StrictFinite::from_f64(42.0))?;
let mut labels = HashMap::new();
labels.insert(key, "answer");

Key differences:

§Common Pitfalls

Here are common mistakes to avoid when using num-valid:

§1. Forgetting Clone for Reused Values

Validated types consume self in mathematical operations. Clone before reuse:

use num_valid::{real, functions::{Sin, Cos}};
let x = real!(1.0);

// ❌ Wrong - x is moved after first use:
// let sin_x = x.sin();
// let cos_x = x.cos(); // Error: x already moved!

// ✅ Correct - clone before move:
let sin_x = x.clone().sin();
let cos_x = x.cos();

§2. Using from_f64 with Untrusted Input

RealScalar::from_f64 panics on invalid input. Use RealScalar::try_from_f64 for user data:

use num_valid::*;

fn process_user_input(user_input: f64) -> Result<(), Box<dyn std::error::Error>> {
    // ❌ Dangerous - panics on NaN/Inf:
    // let x = RealNative64StrictFinite::from_f64(user_input);

    // ✅ Safe - handles errors gracefully:
    let x = RealNative64StrictFinite::try_from_f64(user_input)?;
    println!("Validated: {}", x);
    Ok(())
}

§3. Validation Policy Mismatch

Choose the right policy for your use case:

PolicyDebug BuildRelease BuildUse Case
StrictFinite✅ Validates✅ ValidatesSafety-critical code
StrictFiniteInDebug✅ Validates❌ No validationPerformance-critical code
use num_valid::{RealNative64StrictFinite, RealNative64StrictFiniteInDebug};

// For safety-critical code (always validated)
type SafeReal = RealNative64StrictFinite;

// For performance-critical code (validated only in debug builds)
type FastReal = RealNative64StrictFiniteInDebug;

§4. Ignoring #[must_use] Warnings

All try_* methods return Result. Don’t ignore them:

use num_valid::{RealNative64StrictFinite, functions::Sqrt};
use try_create::TryNew;

let x = RealNative64StrictFinite::try_new(4.0).unwrap();

// ❌ Compiler warning: unused Result
// x.try_sqrt();

// ✅ Handle the result
let sqrt_x = x.try_sqrt()?;

§License

Copyright 2023-2025, C.N.R. - Consiglio Nazionale delle Ricerche

Licensed under either of

  • Apache License, Version 2.0
  • MIT license

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

§License Notice for Optional Feature Dependencies (LGPL-3.0 Compliance)

If you enable the rug feature, this project will depend on the rug library, which is licensed under the LGPL-3.0 license. Activating this feature may introduce LGPL-3.0 requirements to your project. Please review the terms of the LGPL-3.0 license to ensure compliance.

Re-exports§

pub use crate::backends::native::validated::ComplexNative32StrictFinite;
pub use crate::backends::native::validated::ComplexNative64StrictFinite;
pub use crate::backends::native::validated::ComplexNative64StrictFiniteInDebug;
pub use crate::backends::native::validated::Native32StrictFinite;
pub use crate::backends::native::validated::Native32StrictFiniteInDebug;
pub use crate::backends::native::validated::Native64StrictFinite;
pub use crate::backends::native::validated::Native64StrictFiniteInDebug;
pub use crate::backends::native::validated::RealNative32StrictFinite;
pub use crate::backends::native::validated::RealNative64StrictFinite;
pub use crate::backends::native::validated::RealNative64StrictFiniteInDebug;
pub use crate::kernels::PrecisionBits;
pub use crate::backends::rug::validated::ComplexRugStrictFinite;
pub use crate::backends::rug::validated::RealRugStrictFinite;
pub use crate::backends::rug::validated::RugStrictFinite;
pub use approx;

Modules§

algorithms
Numerical algorithms for improved accuracy and stability.
backends
Backend implementations for different numerical types.
core
Core abstractions for the num-valid library.
functions
Scalar Numerical Functions Module
kernels
Numerical Kernels and Validated Types
prelude
Convenient re-exports for common use cases.
scalars
Type-safe scalar wrappers for numerical tolerances and constrained real values.

Macros§

complex
Creates a validated complex number from two f64 literals (real and imaginary parts).
complex32
Creates a validated single-precision complex number from two f32 literals (real and imaginary parts).
real
Creates a validated real number from an f64 literal.
real32
Creates a validated single-precision real number from an f32 literal.

Enums§

RandomSampleComplexErrors
Errors that can occur while constructing a complex random sample.

Traits§

ComplexScalar
Trait for complex scalar numbers
Constants
Provides fundamental mathematical constants for floating-point scalar types.
FpScalar
Core trait for a floating-point scalar number (real or complex)
IntegerScalar
RealScalar
Trait for scalar real numbers
ScalarCore
Fundamental trait defining core requirements for all scalar types.
TryNew
Re-export of try_create::TryNew, the public fallible constructor trait implemented by validated scalar types. A trait for creating new instances of a type with fallible validation.
TryRandomSampleFrom
A fallible conversion from a distribution sample into Self at a requested precision.
TryRandomSampleRawReal

Functions§

try_random_vec
Generates a vector of random values, converting every sample at precision_out.
try_vec_raw_into_vec_real
Attempts to convert a vector of raw real values into a vector of the specified real scalar type.
vec_raw_into_vec_real
Converts a vector of raw real values into a vector of the specified real scalar type.

Trait Aliases§

PowIntExponent
A trait alias for scalar types that can be raised to the power of any primitive integer.