Expand description
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
ornum::complex<f64>
directly,num-valid
encourages the use of validated wrappers likeRealValidated
andComplexValidated
. 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 for both real and complex numbers, with specific validation policy 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
, andRawComplexTrait
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.
- The
- 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.
- 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
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 (likeNaN
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
andComplexScalar
are specialized sub-traits ofFpScalar
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
andComplexValidated
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.
- 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
f64
andnum::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::Float
andrug::Complex
from 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
NeumaierSum
for its defaultstd::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 feature 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 vesion
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 informations about the problem 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 defintionion
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 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::kernels::ComplexValidated;
pub use crate::kernels::NumKernel;
pub use crate::kernels::RawKernel;
pub use crate::kernels::RealValidated;
pub use crate::kernels::native64::Native64;
pub use crate::kernels::native64_validated::ComplexNative64StrictFinite;
pub use crate::kernels::native64_validated::ComplexNative64StrictFiniteInDebug;
pub use crate::kernels::native64_validated::Native64StrictFinite;
pub use crate::kernels::native64_validated::Native64StrictFiniteInDebug;
pub use crate::kernels::native64_validated::RealNative64StrictFinite;
pub use crate::kernels::native64_validated::RealNative64StrictFiniteInDebug;
Modules§
- functions
- Scalar Numerical Functions Module
- kernels
- Numerical Kernels and Validated Types
- neumaier_
compensated_ sum - validation
- Numerical Value Validation Module
Traits§
- Complex
Scalar - Trait for scalar complex numbers
- Constants
- Provides fundamental mathematical constants.
- FpScalar
- Core trait for a floating-point scalar number (real or complex)
- Random
Sample From F64 - A trait for types that can be randomly generated from a distribution over
f64
. - Real
Scalar - Trait for scalar real numbers
Functions§
- new_
random_ vec - Generates a
Vec<T>
of a specified length with random values. - try_
vec_ f64_ into_ vec_ real - vec_
f64_ into_ vec_ real - Converts the input
vec
off64
values into a vector of floating point number, as described by the generic numerical kernelT
.