Skip to main content

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 five 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
HashableScalar<T>FiniteN/AHash-map and hash-set keys for real or complex scalars

AbsoluteTolerance<T>, RelativeTolerance<T>, PositiveRealScalar<T>, and NonNegativeRealScalar<T> are generic over RealScalar, enabling consistent validation across different numerical backends (native f64, arbitrary-precision rug, etc.). HashableScalar<T> is generic over FpScalar and supports both real and complex scalars.

§Architectural Design

§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::native::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(
    RealNative64StrictFinite::try_new(1e-10_f64).unwrap()
).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::*};
use try_create::TryNew;
use std::assert_matches;

// Negative tolerances are rejected
let invalid_tol = AbsoluteTolerance::try_new(
    RealNative64StrictFinite::try_new(-1e-6_f64).unwrap()
).unwrap_err();
assert_matches!(invalid_tol, ErrorsNonNegativeRealScalar::NegativeValue { .. });

// Zero is not positive
let invalid_pos = PositiveRealScalar::try_new(0.0_f64).unwrap_err();
assert_matches!(invalid_pos, ErrorsPositiveRealScalar::ZeroValue { .. });

§Error Handling

Both AbsoluteTolerance<T> and RelativeTolerance<T> use ErrorsNonNegativeRealScalar for error reporting, since they are wrappers around NonNegativeRealScalar<T>. This provides consistent error semantics:

  • No negative values: Both types reject negative inputs
  • Debug validation: NaN and infinity are panicked on in debug builds
  • Backtrace support: When the backtrace feature is enabled

§Tolerance Types

§AbsoluteTolerance<T>

Represents an absolute error bound for numerical comparisons. The tolerance value must be non-negative (≥ 0).

use num_valid::{*, scalars::*};
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
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_raw_scalar_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::*};
use try_create::TryNew;

// Zero is valid for NonNegativeRealScalar
let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
assert_eq!(*zero.as_raw_scalar_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_raw_scalar_ref(), &0.0);

§Generic Programming with RealScalar

All types work seamlessly with any scalar type implementing RealScalar:

use num_valid::{
    backends::native::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, approx::assert_abs_diff_eq};

// 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_raw_scalar_ref());

Structs§

AbsoluteTolerance
Type-safe wrapper for absolute tolerance values.
HashableScalar
Wrapper type that makes floating-point scalars hashable for use in hash-based collections.
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§

ErrorsHashableScalar
Error returned when constructing a HashableScalar from an invalid value.
ErrorsNonNegativeRealScalar
Error type for NonNegativeRealScalar<T> validation failures.
ErrorsPositiveRealScalar
Error type for PositiveRealScalar<T> validation failures.

Functions§

approximately_equal
Compares two scalar values within an absolute tolerance.