Module scalars

Module scalars 

Source
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:

TypeConstraintZero Valid?Use Cases
AbsoluteTolerance<T>≥ 0✅ YesFixed error bounds for comparisons
RelativeTolerance<T>≥ 0✅ YesProportional error bounds
PositiveRealScalar<T>> 0❌ NoLengths, positive quantities
NonNegativeRealScalar<T>≥ 0✅ YesDistances, 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:

TypeMemory LayoutRuntime Overhead
AbsoluteTolerance<T>Same as TZero (validation at construction only)
RelativeTolerance<T>Same as TZero (validation at construction only)
PositiveRealScalar<T>Same as TZero (validation at construction only)
NonNegativeRealScalar<T>Same as TZero (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§

AbsoluteTolerance
Type-safe wrapper for absolute tolerance values.
NonNegativeRealScalar
Type-safe wrapper for non-negative real scalar values.
PositiveRealScalar
Type-safe wrapper for strictly positive real scalar values.
RelativeTolerance
Type-safe wrapper for relative tolerance values.

Enums§

ErrorsNonNegativeRealScalar
Error type for NonNegativeRealScalar<T> validation failures.
ErrorsPositiveRealScalar
Error type for PositiveRealScalar<T> validation failures.
ErrorsTolerance
Error type for tolerance validation failures.