num-valid

Crates.ionum-valid
lib.rsnum-valid
version0.1.0
created_at2025-07-21 16:27:38.323018+00
updated_at2025-07-21 16:27:38.323018+00
descriptionA robust numerical library providing validated types for real and complex numbers to prevent common floating-point errors like NaN propagation. Features a generic, layered architecture with support for native f64 and optional arbitrary-precision arithmetic.
homepage
repositoryhttps://gitlab.com/max.martinelli/num-valid
max_upload_size
id1762246
size1,101,354
Massimiliano Martinelli (max-1975)

documentation

https://docs.rs/num-valid

README

num-valid

Crates.io Docs.rs License: MIT OR Apache-2.0 Pipeline Status Coverage GitLab last commit

num-valid is a Rust library designed for robust, generic, and high-performance numerical computation. It provides a safe and extensible framework for working with both real and complex numbers, addressing the challenges of floating-point arithmetic by ensuring correctness and preventing common errors like NaN propagation.

Key Features & Architecture

  • Safety by Construction with Validated Types: Instead of using raw primitives like f64 or num::Complex<f64> directly, num-valid encourages the use of validated wrappers like RealValidated and ComplexValidated. 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 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.
      • 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.
    • Layer 2: Validation Policies:
      • 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., 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., f64 vs. rug) and a set of safety rules (e.g., StrictFinitePolicy vs. DebugValidationPolicy<StrictFinitePolicy>) with a single generic parameter. This dramatically simplifies writing generic code that can be configured for different safety and performance trade-offs.
    • 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.
      • Why? These wrappers use the newtype pattern to enforce correctness at the type level. By construction, an instance of RealValidated is guaranteed to contain a value that has passed the validation policy, eliminating 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.
      • 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.

    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 the Rust's native f64 and num::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 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., Sin, Cos, Sqrt) and available on the validated types.

  • Numerically Robust Implementations. The library commits to numerical accuracy. As an example, by using NeumaierSum for its default std::iter::Sum implementation 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.

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

Compiler Requirement: Rust Nightly

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

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.1.1" # Change to the latest version
try_create = "0.1.2" # Needed for the TryNew trait

To enable the arbitrary-precision backend, use the rug feature:

[dependencies]
num-valid = { version = "0.1.1", features = ["rug"] }
try_create = "0.1.2"

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 a 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 traits for the constructor, the sqrt function and the sqrt errors.
use num_valid::{
    RealNative64StrictFinite,
    functions::{Sqrt, SqrtRealInputErrors, SqrtRealErrors},
};
use try_create::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, 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
assert!(matches!(sqrt_neg_x_result.unwrap_err(),
    SqrtRealErrors::Input{ source: SqrtRealInputErrors::NegativeValue{ value: -4., .. }}));

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 try_create::TryNew;

// 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() < 1e-15);

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:

// we need to add the proper module
use num_valid::RealRugStrictFinite;
// ... same modules as above

// ... same verify_trig_identity() function as above

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

// 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() < 1e-15);

License

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

Licensed under either of

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.

Commit count: 0

cargo fmt