# num-valid Cookbook
**Practical patterns and examples for using num-valid effectively**
---
## Table of Contents
1. [Pattern 1: Validated Input Processing](#pattern-1-validated-input-processing)
2. [Pattern 2: Generic Numerical Algorithms](#pattern-2-generic-numerical-algorithms)
3. [Pattern 3: High-Precision Constants](#pattern-3-high-precision-constants)
4. [Pattern 4: Error Propagation](#pattern-4-error-propagation)
5. [Pattern 5: Working with Collections](#pattern-5-working-with-collections)
6. [Pattern 6: Complex Number Computations](#pattern-6-complex-number-computations)
7. [Pattern 7: Conditional Validation (Debug/Release)](#pattern-7-conditional-validation-debugrelease)
8. [Pattern 8: HashMap Keys](#pattern-8-hashmap-keys)
9. [Pattern 9: Serialization/Deserialization](#pattern-9-serializationdeserialization)
10. [Performance Tips](#performance-tips)
---
## Pattern 1: Validated Input Processing
**Use Case**: Safely process user input or external data that may contain invalid values.
```rust
use num_valid::{RealNative64StrictFinite, RealScalar};
fn process_user_input(input: f64) -> Result<RealNative64StrictFinite, String> {
RealNative64StrictFinite::try_from_f64(input)
.map_err(|e| format!("Invalid input: {}", e))
}
fn calculate_with_validation(x: f64, y: f64) -> Result<RealNative64StrictFinite, String> {
let validated_x = process_user_input(x)?;
let validated_y = process_user_input(y)?;
// All operations are now safe - NaN/Inf impossible
Ok((validated_x * validated_y).sqrt())
}
// Example usage
fn main() {
match calculate_with_validation(16.0, 4.0) {
Ok(result) => println!("Result: {}", result.as_ref()),
Err(e) => eprintln!("Error: {}", e),
}
}
```
---
## Pattern 2: Generic Numerical Algorithms
**Use Case**: Write algorithms that work with any precision level or backend.
```rust
use num_valid::{RealScalar, functions::Abs};
use num::Zero;
/// Computes the Euclidean norm (L2 norm) of a vector.
/// Works with f64, RealNative64StrictFinite, RealRugStrictFinite, etc.
fn euclidean_norm<T: RealScalar>(values: &[T]) -> T {
values.iter()
.map(|x| x.clone() * x.clone())
.sum::<T>()
.sqrt()
}
/// Computes the mean absolute deviation.
fn mean_absolute_deviation<T: RealScalar>(values: &[T]) -> T {
let n = T::try_from_f64(values.len() as f64).unwrap();
let mean: T = values.iter().cloned().sum::<T>() / &n;
let sum_abs_dev: T = values.iter()
.map(|x| (x.clone() - &mean).abs())
.sum();
sum_abs_dev / n
}
// Example: Use with native f64
use num_valid::RealNative64StrictFinite;
let data = vec![
RealNative64StrictFinite::try_from_f64(1.0).unwrap(),
RealNative64StrictFinite::try_from_f64(2.0).unwrap(),
RealNative64StrictFinite::try_from_f64(3.0).unwrap(),
];
let norm = euclidean_norm(&data);
// Example: Use with arbitrary precision (if rug feature enabled)
#[cfg(feature = "rug")]
{
use num_valid::RealRugStrictFinite;
let high_precision_data: Vec<RealRugStrictFinite<200>> = vec![
RealRugStrictFinite::try_from_f64(1.0).unwrap(),
RealRugStrictFinite::try_from_f64(2.0).unwrap(),
];
let high_precision_norm = euclidean_norm(&high_precision_data);
}
```
---
## Pattern 3: High-Precision Constants
**Use Case**: Working with mathematical constants at different precision levels.
```rust
use num_valid::{Constants, RealScalar};
/// Compute π/4 using Machin's formula at compile-time specified precision.
fn compute_pi_over_4<T: RealScalar>() -> T {
T::pi() / T::try_from_f64(4.0).unwrap()
}
// Native f64 precision
use num_valid::RealNative64StrictFinite;
let pi_4_native = compute_pi_over_4::<RealNative64StrictFinite>();
// Arbitrary precision (200 bits ≈ 60 decimal digits)
#[cfg(feature = "rug")]
{
use num_valid::RealRugStrictFinite;
let pi_4_high = compute_pi_over_4::<RealRugStrictFinite<200>>();
}
// Common constants pattern
fn golden_ratio<T: RealScalar>() -> T {
let one = T::one();
let five = T::try_from_f64(5.0).unwrap();
(one + five.sqrt()) / T::two()
}
```
---
## Pattern 4: Error Propagation
**Use Case**: Chain multiple fallible operations with proper error handling.
```rust
use num_valid::{RealNative64StrictFinite, RealScalar, functions::Sqrt};
use thiserror::Error;
#[derive(Debug, Error)]
enum CalculationError {
#[error("Failed to validate input: {0}")]
ValidationError(String),
#[error("Square root of negative number")]
SqrtError(#[from] num_valid::functions::SqrtRealErrors<f64>),
#[error("Logarithm of non-positive number")]
LnError(#[from] num_valid::functions::LnRealErrors<f64>),
}
fn complex_calculation(x: f64) -> Result<RealNative64StrictFinite, CalculationError> {
// Convert and validate input
let validated = RealNative64StrictFinite::try_from_f64(x)
.map_err(|e| CalculationError::ValidationError(e.to_string()))?;
// Chain operations - each can fail
let sqrt_x = validated.try_sqrt()?;
let ln_sqrt = sqrt_x.try_ln()?;
Ok(ln_sqrt)
}
// Example usage
fn main() {
match complex_calculation(16.0) {
Ok(result) => println!("Result: {}", result.as_ref()),
Err(e) => eprintln!("Calculation failed: {}", e),
}
// This will fail with SqrtError
match complex_calculation(-4.0) {
Ok(_) => unreachable!(),
Err(CalculationError::SqrtError(_)) => println!("Expected sqrt error"),
Err(e) => panic!("Unexpected error: {}", e),
}
}
```
---
## Pattern 5: Working with Collections
**Use Case**: Process vectors and arrays of validated numbers efficiently.
```rust
use num_valid::{RealNative64StrictFinite, try_vec_f64_into_vec_real, vec_f64_into_vec_real};
// Fallible conversion with early exit on first error
fn safe_conversion(raw_data: Vec<f64>) -> Result<Vec<RealNative64StrictFinite>, String> {
try_vec_f64_into_vec_real(raw_data)
.map_err(|e| format!("Conversion failed: {}", e))
}
// Panicking conversion for known-valid data
fn fast_conversion(raw_data: Vec<f64>) -> Vec<RealNative64StrictFinite> {
vec_f64_into_vec_real(raw_data)
}
// Filter invalid values instead of failing
fn filter_invalid(raw_data: Vec<f64>) -> Vec<RealNative64StrictFinite> {
raw_data.into_iter()
.filter_map(|x| RealNative64StrictFinite::try_from_f64(x).ok())
.collect()
}
// Example: Statistical computation on validated data
use num_valid::functions::Abs;
fn compute_statistics(data: &[RealNative64StrictFinite]) -> (RealNative64StrictFinite, RealNative64StrictFinite) {
let mean = data.iter().cloned().sum::<RealNative64StrictFinite>()
/ RealNative64StrictFinite::try_from_f64(data.len() as f64).unwrap();
let variance = data.iter()
.map(|x| {
let diff = x.clone() - &mean;
diff.clone() * diff
})
.sum::<RealNative64StrictFinite>()
/ RealNative64StrictFinite::try_from_f64(data.len() as f64).unwrap();
(mean, variance.sqrt())
}
```
---
## Pattern 6: Complex Number Computations
**Use Case**: Safe complex arithmetic with validated types.
```rust
use num_valid::{
ComplexNative64StrictFinite, RealNative64StrictFinite,
complex, real,
functions::{ComplexScalarConstructors, Abs, Arg, Conjugate, Exp},
};
// Create complex numbers
let z1 = complex!(3.0, 4.0); // 3 + 4i
let z2 = complex!(1.0, -1.0); // 1 - i
// Basic operations
let sum = z1.clone() + z2.clone();
let product = z1.clone() * z2.clone();
// Complex-specific operations
let conjugate = z1.conjugate(); // 3 - 4i
// Euler's formula: e^(iθ) = cos(θ) + i·sin(θ)
fn euler_formula(theta: RealNative64StrictFinite) -> ComplexNative64StrictFinite {
let zero = RealNative64StrictFinite::zero();
complex!(0.0, 1.0) * theta // i·θ
.exp() // e^(i·θ)
}
// De Moivre's theorem: (cos(θ) + i·sin(θ))^n = cos(nθ) + i·sin(nθ)
fn de_moivre(theta: RealNative64StrictFinite, n: i32) -> ComplexNative64StrictFinite {
euler_formula(theta).try_pow_exponent(&n).unwrap()
}
```
---
## Pattern 7: Conditional Validation (Debug/Release)
**Use Case**: Maximum performance in release builds while maintaining safety during development.
```rust
use num_valid::{RealNative64StrictFinite, RealNative64StrictFiniteInDebug};
// Always validated (both debug and release)
type StrictReal = RealNative64StrictFinite;
// Validated only in debug builds (zero overhead in release)
type FastReal = RealNative64StrictFiniteInDebug;
fn performance_critical_loop() {
let mut accumulator = FastReal::zero();
for i in 0..1_000_000 {
let value = FastReal::try_from_f64(i as f64).unwrap();
accumulator = accumulator + value;
}
// In release mode: identical performance to raw f64
// In debug mode: full validation active
}
// Use strict validation for external input
fn process_external_data(input: f64) -> StrictReal {
StrictReal::try_from_f64(input).expect("Invalid external data")
}
// Use fast validation for internal computations
fn internal_computation(a: FastReal, b: FastReal) -> FastReal {
(a * b).sqrt() // Zero overhead in release
}
```
---
## Pattern 8: HashMap Keys
**Use Case**: Using validated numbers as dictionary keys (only with finite guarantees).
```rust
use num_valid::RealNative64StrictFinite;
use std::collections::HashMap;
// Create a lookup table with validated keys
let mut scores: HashMap<RealNative64StrictFinite, String> = HashMap::new();
let player1 = RealNative64StrictFinite::try_from_f64(1.5).unwrap();
let player2 = RealNative64StrictFinite::try_from_f64(2.0).unwrap();
scores.insert(player1, "Alice".to_string());
scores.insert(player2, "Bob".to_string());
// Lookup works correctly
let lookup_key = RealNative64StrictFinite::try_from_f64(1.5).unwrap();
assert_eq!(scores.get(&lookup_key), Some(&"Alice".to_string()));
// Signed zero handling is correct
let pos_zero = RealNative64StrictFinite::try_from_f64(0.0).unwrap();
let neg_zero = RealNative64StrictFinite::try_from_f64(-0.0).unwrap();
scores.insert(pos_zero, "Zero".to_string());
assert_eq!(scores.get(&neg_zero), Some(&"Zero".to_string())); // +0.0 == -0.0
// Note: Cannot use f64 as HashMap key (would be incorrect for NaN)
// let mut bad_map: HashMap<f64, String> = HashMap::new();
// bad_map.insert(f64::NAN, "test".to_string()); // ❌ NaN != NaN breaks HashMap invariants
```
---
## Pattern 9: Serialization/Deserialization
**Use Case**: Save and load validated numeric data.
```rust
use num_valid::RealNative64StrictFinite;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct DataPoint {
x: RealNative64StrictFinite,
y: RealNative64StrictFinite,
label: String,
}
fn serialize_example() -> String {
let point = DataPoint {
x: RealNative64StrictFinite::try_from_f64(3.14).unwrap(),
y: RealNative64StrictFinite::try_from_f64(2.71).unwrap(),
label: "Important Point".to_string(),
};
serde_json::to_string(&point).unwrap()
}
fn deserialize_example(json: &str) -> DataPoint {
serde_json::from_str(json).unwrap()
}
// Example usage
fn main() {
let json = serialize_example();
println!("Serialized: {}", json);
let restored = deserialize_example(&json);
println!("Restored: ({}, {})", restored.x.as_ref(), restored.y.as_ref());
}
```
---
## Performance Tips
### 1. Choose the Right Validation Policy
```rust
// For production code with external input: Always validate
use num_valid::RealNative64StrictFinite;
// For internal hot loops: Validate only in debug
use num_valid::RealNative64StrictFiniteInDebug;
// Measured overhead:
// - StrictFinite: ~5-15% in release mode
// - StrictFiniteInDebug: ~0% in release mode (identical to raw f64)
```
### 2. Use Macros for Constants
```rust
use num_valid::real;
// Fast: Macro evaluates at compile time for constants
let pi = real!(std::f64::consts::PI);
// Slower: Runtime conversion (though still validated)
let pi2 = RealNative64StrictFinite::from_f64(std::f64::consts::PI);
// For many constants, use the macro
let circle_area = real!(std::f64::consts::PI) * real!(25.0);
```
### 3. Prefer References for Large Precision Types
```rust
#[cfg(feature = "rug")]
use num_valid::RealRugStrictFinite;
#[cfg(feature = "rug")]
fn good_performance(a: &RealRugStrictFinite<1000>, b: &RealRugStrictFinite<1000>) {
let result = a + b; // Only one allocation (for result)
}
#[cfg(feature = "rug")]
fn bad_performance(a: RealRugStrictFinite<1000>, b: RealRugStrictFinite<1000>) {
let result = a + b; // Two moves, same allocation, but less ergonomic
}
// All arithmetic ops support: T op T, T op &T, &T op T, &T op &T
```
### 4. Use Sum for Accurate Accumulation
```rust
use num_valid::RealNative64StrictFinite;
let values: Vec<_> = (0..1_000_000)
.map(|i| RealNative64StrictFinite::try_from_f64(i as f64).unwrap())
.collect();
// Good: Uses Neumaier compensated sum (accurate)
let accurate_sum = values.iter().cloned().sum::<RealNative64StrictFinite>();
// Bad: Naive sum (accumulates rounding errors)
let naive_sum = values.iter().fold(
RealNative64StrictFinite::zero(),
|acc, x| acc + x.clone()
);
// Neumaier sum has error O(ε) regardless of n
// Naive sum has error O(n·ε)
```
### 5. Profile Before Optimizing
```rust
// Use criterion for benchmarking
#[cfg(test)]
mod benches {
use criterion::{black_box, Criterion};
use num_valid::RealNative64StrictFinite;
fn benchmark_validated_ops(c: &mut Criterion) {
c.bench_function("validated_sqrt", |b| {
let x = RealNative64StrictFinite::try_from_f64(2.0).unwrap();
b.iter(|| black_box(x.clone()).sqrt())
});
}
}
// Measure, don't guess!
```
---
## Additional Resources
- **[Architecture Guide](../docs/ARCHITECTURE.md)**: Deep dive into the 4-layer design
- **[API Documentation](https://docs.rs/num-valid)**: Complete API reference
- **[Migration Guide](../docs/MIGRATION.md)**: Migrating from raw primitives
- **[GitHub Repository](https://gitlab.com/max.martinelli/num-valid)**: Source code and issues
---
**Document Version**: 1.0
**Last Updated**: December 3, 2025