Expand description
Type-safe scalar wrappers for numerical tolerances and constrained real values.
This module provides strongly-typed wrappers around primitive scalar types to prevent value confusion and enforce mathematical constraints at compile time. These types are fundamental building blocks for numerical computations that require validated tolerances and constrained real number values.
§Overview
The module provides four primary types:
| Type | Constraint | Zero Valid? | Use Cases |
|---|---|---|---|
AbsoluteTolerance<T> | ≥ 0 | ✅ Yes | Fixed error bounds for comparisons |
RelativeTolerance<T> | ≥ 0 | ✅ Yes | Proportional error bounds |
PositiveRealScalar<T> | > 0 | ❌ No | Lengths, positive quantities |
NonNegativeRealScalar<T> | ≥ 0 | ✅ Yes | Distances, magnitudes, absolute values |
All types are generic over RealScalar, enabling consistent validation across
different numerical backends (native f64, arbitrary-precision rug, etc.).
§Design Philosophy
§Type Safety Through Distinct Types
Rather than using raw primitives like f64 directly, this module provides
semantically meaningful types that encode mathematical constraints:
use num_valid::{
backends::native64::validated::RealNative64StrictFinite, RealScalar,
scalars::{AbsoluteTolerance, RelativeTolerance, PositiveRealScalar},
};
use try_create::TryNew;
// These are different types that cannot be confused:
let abs_tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap(); // For absolute comparisons
let rel_tol = RelativeTolerance::try_new(1e-6_f64).unwrap(); // For relative comparisons
let length = PositiveRealScalar::try_new(5.0_f64).unwrap(); // Must be > 0
// Type system prevents mixing them up:
// let wrong: AbsoluteTolerance<f64> = rel_tol; // ← Compilation error!§Validation at Construction Time
All types validate their input at construction time, failing fast on invalid values:
use num_valid::scalars::{AbsoluteTolerance, PositiveRealScalar, ErrorsTolerance, ErrorsPositiveRealScalar};
use try_create::TryNew;
// Negative tolerances are rejected
let invalid_tol = AbsoluteTolerance::try_new(-1e-6_f64);
assert!(matches!(invalid_tol, Err(ErrorsTolerance::NegativeValue { .. })));
// Zero is not positive
let invalid_pos = PositiveRealScalar::try_new(0.0_f64);
assert!(matches!(invalid_pos, Err(ErrorsPositiveRealScalar::ZeroValue { .. })));§Tolerance Types
§AbsoluteTolerance<T>
Represents an absolute error bound for numerical comparisons. The tolerance value must be non-negative (≥ 0).
use num_valid::scalars::AbsoluteTolerance;
use try_create::TryNew;
// Create tolerances
let tight = AbsoluteTolerance::try_new(1e-12_f64).unwrap();
let loose = AbsoluteTolerance::try_new(1e-6_f64).unwrap();
let zero = AbsoluteTolerance::<f64>::zero(); // Exact comparison
let epsilon = AbsoluteTolerance::<f64>::epsilon(); // Machine epsilon
// Use in approximate comparison
fn approximately_equal<T: num_valid::RealScalar + Clone>(
a: T,
b: T,
tolerance: &AbsoluteTolerance<T>
) -> bool {
let diff = (a - b).abs();
&diff <= tolerance.as_ref()
}
assert!(approximately_equal(1.0, 1.0 + 1e-13, &tight));
assert!(!approximately_equal(1.0, 1.0 + 1e-11, &tight));§RelativeTolerance<T>
Represents a relative (proportional) error bound. Can be converted to an absolute tolerance based on a reference value.
use num_valid::scalars::RelativeTolerance;
use try_create::TryNew;
let rel_tol = RelativeTolerance::try_new(0.01_f64).unwrap(); // 1% tolerance
// Convert to absolute tolerance based on reference value
let reference = 1000.0_f64;
let abs_tol = rel_tol.absolute_tolerance(reference);
assert_eq!(*abs_tol.as_ref(), 10.0); // 1% of 1000 = 10§Constrained Real Number Types
§PositiveRealScalar<T>
Wraps a real scalar that must be strictly positive (> 0). Zero is not valid.
use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
use try_create::TryNew;
// Valid positive values
let length = PositiveRealScalar::try_new(2.5_f64).unwrap();
let tiny = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
// Zero is NOT positive (x > 0 required)
let zero_result = PositiveRealScalar::try_new(0.0_f64);
assert!(matches!(zero_result, Err(ErrorsPositiveRealScalar::ZeroValue { .. })));
// Negative values rejected
let neg_result = PositiveRealScalar::try_new(-1.0_f64);
assert!(matches!(neg_result, Err(ErrorsPositiveRealScalar::NegativeValue { .. })));§NonNegativeRealScalar<T>
Wraps a real scalar that must be non-negative (≥ 0). Zero is valid.
use num_valid::scalars::{NonNegativeRealScalar, PositiveRealScalar};
use try_create::TryNew;
// Zero is valid for NonNegativeRealScalar
let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
assert_eq!(*zero.as_ref(), 0.0);
// But NOT for PositiveRealScalar
assert!(PositiveRealScalar::try_new(0.0_f64).is_err());
// Use case: computing distances (can be zero)
fn distance(a: f64, b: f64) -> NonNegativeRealScalar<f64> {
NonNegativeRealScalar::try_new((a - b).abs()).unwrap()
}
let d = distance(5.0, 5.0); // Zero distance is valid
assert_eq!(*d.as_ref(), 0.0);§Generic Programming with RealScalar
All types work seamlessly with any scalar type implementing RealScalar:
use num_valid::{
backends::native64::validated::{RealNative64StrictFinite, RealNative64StrictFiniteInDebug},
RealScalar,
scalars::AbsoluteTolerance
};
use try_create::TryNew;
// Same tolerance type works with different backends
type FastTol = AbsoluteTolerance<f64>;
type SafeTol = AbsoluteTolerance<RealNative64StrictFinite>;
type DebugTol = AbsoluteTolerance<RealNative64StrictFiniteInDebug>;
let fast = FastTol::try_new(1e-10).unwrap();
let safe = SafeTol::try_new(RealNative64StrictFinite::try_from_f64(1e-10).unwrap()).unwrap();§Performance Characteristics
All wrapper types are designed as zero-cost abstractions:
| Type | Memory Layout | Runtime Overhead |
|---|---|---|
AbsoluteTolerance<T> | Same as T | Zero (validation at construction only) |
RelativeTolerance<T> | Same as T | Zero (validation at construction only) |
PositiveRealScalar<T> | Same as T | Zero (validation at construction only) |
NonNegativeRealScalar<T> | Same as T | Zero (validation at construction only) |
The #[repr(transparent)] attribute ensures that each wrapper has the exact same
memory layout as its underlying type.
§Relationship to approx Crate
These tolerance types complement the approx crate’s comparison traits. While approx
uses raw f64 for epsilon values (which can be negative, causing silent failures),
these wrappers guarantee non-negativity at construction time:
use num_valid::scalars::AbsoluteTolerance;
use num_valid::approx::assert_abs_diff_eq;
use try_create::TryNew;
// Create a validated tolerance
let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
// Use with approx (extract the inner value)
let a = 1.0_f64;
let b = 1.0 + 1e-11;
assert_abs_diff_eq!(a, b, epsilon = *tol.as_ref());Structs§
- Absolute
Tolerance - Type-safe wrapper for absolute tolerance values.
- NonNegative
Real Scalar - Type-safe wrapper for non-negative real scalar values.
- Positive
Real Scalar - Type-safe wrapper for strictly positive real scalar values.
- Relative
Tolerance - Type-safe wrapper for relative tolerance values.
Enums§
- Errors
NonNegative Real Scalar - Error type for
NonNegativeRealScalar<T>validation failures. - Errors
Positive Real Scalar - Error type for
PositiveRealScalar<T>validation failures. - Errors
Tolerance - Error type for tolerance validation failures.