Crates.io | num-valid |
lib.rs | num-valid |
version | 0.1.0 |
created_at | 2025-07-21 16:27:38.323018+00 |
updated_at | 2025-07-21 16:27:38.323018+00 |
description | A 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 | |
repository | https://gitlab.com/max.martinelli/num-valid |
max_upload_size | |
id | 1762246 |
size | 1,101,354 |
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.
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:
kernels
module):
RawScalarTrait
, RawRealTrait
, and RawComplexTrait
define the low-level, "unchecked" contract for any number type.unchecked_*
methods.NumKernel
trait is the bridge between the raw types and the validated wrappers.StrictFinitePolicy
, DebugValidationPolicy
, etc.). This allows the entire behavior of the validated types to be configured with a single generic parameter.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.RealValidated<K>
and ComplexValidated<K>
are the primary user-facing types.NumKernel
K
(and to the validation policies therein).unchecked_*
method from the raw trait, and then perform a check on the output value before returning it. This ensures safety and correctness.RealValidated
is guaranteed to contain a value that has passed the validation policy, eliminating entire classes of errors (like NaN
propagation) in user code.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.RealValidated
and ComplexValidated
structs from Layer 3 are the concrete implementors of these traits.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:
f64
and num::Complex<f64>
types, as described by the ANSI/IEEE Std 754-1985;--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.
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.
This guide will walk you through the basics of using num-valid
.
num-valid
to your ProjectAdd 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"
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
.
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., .. }}));
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);
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.
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.