num_valid/scalars.rs
1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! Type-safe scalar wrappers for numerical tolerances and constrained real values.
4//!
5//! This module provides strongly-typed wrappers around primitive scalar types to prevent
6//! value confusion and enforce mathematical constraints at compile time. These types are
7//! fundamental building blocks for numerical computations that require validated tolerances
8//! and constrained real number values.
9//!
10//! # Overview
11//!
12//! The module provides four primary types:
13//!
14//! | Type | Constraint | Zero Valid? | Use Cases |
15//! |------|------------|-------------|-----------|
16//! | [`AbsoluteTolerance<T>`] | `≥ 0` | ✅ Yes | Fixed error bounds for comparisons |
17//! | [`RelativeTolerance<T>`] | `≥ 0` | ✅ Yes | Proportional error bounds |
18//! | [`PositiveRealScalar<T>`] | `> 0` | ❌ No | Lengths, positive quantities |
19//! | [`NonNegativeRealScalar<T>`] | `≥ 0` | ✅ Yes | Distances, magnitudes, absolute values |
20//!
21//! All types are generic over [`RealScalar`], enabling consistent validation across
22//! different numerical backends (native `f64`, arbitrary-precision `rug`, etc.).
23//!
24//! # Design Philosophy
25//!
26//! ## Type Safety Through Distinct Types
27//!
28//! Rather than using raw primitives like `f64` directly, this module provides
29//! semantically meaningful types that encode mathematical constraints:
30//!
31//! ```rust
32//! use num_valid::{
33//! backends::native64::validated::RealNative64StrictFinite, RealScalar,
34//! scalars::{AbsoluteTolerance, RelativeTolerance, PositiveRealScalar},
35//! };
36//! use try_create::TryNew;
37//!
38//! // These are different types that cannot be confused:
39//! let abs_tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap(); // For absolute comparisons
40//! let rel_tol = RelativeTolerance::try_new(1e-6_f64).unwrap(); // For relative comparisons
41//! let length = PositiveRealScalar::try_new(5.0_f64).unwrap(); // Must be > 0
42//!
43//! // Type system prevents mixing them up:
44//! // let wrong: AbsoluteTolerance<f64> = rel_tol; // ← Compilation error!
45//! ```
46//!
47//! ## Validation at Construction Time
48//!
49//! All types validate their input at construction time, failing fast on invalid values:
50//!
51//! ```rust
52//! use num_valid::scalars::{AbsoluteTolerance, PositiveRealScalar, ErrorsTolerance, ErrorsPositiveRealScalar};
53//! use try_create::TryNew;
54//!
55//! // Negative tolerances are rejected
56//! let invalid_tol = AbsoluteTolerance::try_new(-1e-6_f64);
57//! assert!(matches!(invalid_tol, Err(ErrorsTolerance::NegativeValue { .. })));
58//!
59//! // Zero is not positive
60//! let invalid_pos = PositiveRealScalar::try_new(0.0_f64);
61//! assert!(matches!(invalid_pos, Err(ErrorsPositiveRealScalar::ZeroValue { .. })));
62//! ```
63//!
64//! # Tolerance Types
65//!
66//! ## [`AbsoluteTolerance<T>`]
67//!
68//! Represents an absolute error bound for numerical comparisons. The tolerance value
69//! must be non-negative (≥ 0).
70//!
71//! ```rust
72//! use num_valid::scalars::AbsoluteTolerance;
73//! use try_create::TryNew;
74//!
75//! // Create tolerances
76//! let tight = AbsoluteTolerance::try_new(1e-12_f64).unwrap();
77//! let loose = AbsoluteTolerance::try_new(1e-6_f64).unwrap();
78//! let zero = AbsoluteTolerance::<f64>::zero(); // Exact comparison
79//! let epsilon = AbsoluteTolerance::<f64>::epsilon(); // Machine epsilon
80//!
81//! // Use in approximate comparison
82//! fn approximately_equal<T: num_valid::RealScalar + Clone>(
83//! a: T,
84//! b: T,
85//! tolerance: &AbsoluteTolerance<T>
86//! ) -> bool {
87//! let diff = (a - b).abs();
88//! &diff <= tolerance.as_ref()
89//! }
90//!
91//! assert!(approximately_equal(1.0, 1.0 + 1e-13, &tight));
92//! assert!(!approximately_equal(1.0, 1.0 + 1e-11, &tight));
93//! ```
94//!
95//! ## [`RelativeTolerance<T>`]
96//!
97//! Represents a relative (proportional) error bound. Can be converted to an absolute
98//! tolerance based on a reference value.
99//!
100//! ```rust
101//! use num_valid::scalars::RelativeTolerance;
102//! use try_create::TryNew;
103//!
104//! let rel_tol = RelativeTolerance::try_new(0.01_f64).unwrap(); // 1% tolerance
105//!
106//! // Convert to absolute tolerance based on reference value
107//! let reference = 1000.0_f64;
108//! let abs_tol = rel_tol.absolute_tolerance(reference);
109//! assert_eq!(*abs_tol.as_ref(), 10.0); // 1% of 1000 = 10
110//! ```
111//!
112//! # Constrained Real Number Types
113//!
114//! ## [`PositiveRealScalar<T>`]
115//!
116//! Wraps a real scalar that must be strictly positive (> 0). Zero is **not** valid.
117//!
118//! ```rust
119//! use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
120//! use try_create::TryNew;
121//!
122//! // Valid positive values
123//! let length = PositiveRealScalar::try_new(2.5_f64).unwrap();
124//! let tiny = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
125//!
126//! // Zero is NOT positive (x > 0 required)
127//! let zero_result = PositiveRealScalar::try_new(0.0_f64);
128//! assert!(matches!(zero_result, Err(ErrorsPositiveRealScalar::ZeroValue { .. })));
129//!
130//! // Negative values rejected
131//! let neg_result = PositiveRealScalar::try_new(-1.0_f64);
132//! assert!(matches!(neg_result, Err(ErrorsPositiveRealScalar::NegativeValue { .. })));
133//! ```
134//!
135//! ## [`NonNegativeRealScalar<T>`]
136//!
137//! Wraps a real scalar that must be non-negative (≥ 0). Zero **is** valid.
138//!
139//! ```rust
140//! use num_valid::scalars::{NonNegativeRealScalar, PositiveRealScalar};
141//! use try_create::TryNew;
142//!
143//! // Zero is valid for NonNegativeRealScalar
144//! let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
145//! assert_eq!(*zero.as_ref(), 0.0);
146//!
147//! // But NOT for PositiveRealScalar
148//! assert!(PositiveRealScalar::try_new(0.0_f64).is_err());
149//!
150//! // Use case: computing distances (can be zero)
151//! fn distance(a: f64, b: f64) -> NonNegativeRealScalar<f64> {
152//! NonNegativeRealScalar::try_new((a - b).abs()).unwrap()
153//! }
154//!
155//! let d = distance(5.0, 5.0); // Zero distance is valid
156//! assert_eq!(*d.as_ref(), 0.0);
157//! ```
158//!
159//! # Generic Programming with [`RealScalar`]
160//!
161//! All types work seamlessly with any scalar type implementing [`RealScalar`]:
162//!
163//! ```rust
164//! use num_valid::{
165//! backends::native64::validated::{RealNative64StrictFinite, RealNative64StrictFiniteInDebug},
166//! RealScalar,
167//! scalars::AbsoluteTolerance
168//! };
169//! use try_create::TryNew;
170//!
171//! // Same tolerance type works with different backends
172//! type FastTol = AbsoluteTolerance<f64>;
173//! type SafeTol = AbsoluteTolerance<RealNative64StrictFinite>;
174//! type DebugTol = AbsoluteTolerance<RealNative64StrictFiniteInDebug>;
175//!
176//! let fast = FastTol::try_new(1e-10).unwrap();
177//! let safe = SafeTol::try_new(RealNative64StrictFinite::try_from_f64(1e-10).unwrap()).unwrap();
178//! ```
179//!
180//! # Performance Characteristics
181//!
182//! All wrapper types are designed as zero-cost abstractions:
183//!
184//! | Type | Memory Layout | Runtime Overhead |
185//! |------|---------------|------------------|
186//! | [`AbsoluteTolerance<T>`] | Same as `T` | Zero (validation at construction only) |
187//! | [`RelativeTolerance<T>`] | Same as `T` | Zero (validation at construction only) |
188//! | [`PositiveRealScalar<T>`] | Same as `T` | Zero (validation at construction only) |
189//! | [`NonNegativeRealScalar<T>`] | Same as `T` | Zero (validation at construction only) |
190//!
191//! The `#[repr(transparent)]` attribute ensures that each wrapper has the exact same
192//! memory layout as its underlying type.
193//!
194//! # Relationship to `approx` Crate
195//!
196//! These tolerance types complement the [`approx`] crate's comparison traits. While `approx`
197//! uses raw `f64` for epsilon values (which can be negative, causing silent failures),
198//! these wrappers guarantee non-negativity at construction time:
199//!
200//! ```rust
201//! use num_valid::scalars::AbsoluteTolerance;
202//! use num_valid::approx::assert_abs_diff_eq;
203//! use try_create::TryNew;
204//!
205//! // Create a validated tolerance
206//! let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
207//!
208//! // Use with approx (extract the inner value)
209//! let a = 1.0_f64;
210//! let b = 1.0 + 1e-11;
211//! assert_abs_diff_eq!(a, b, epsilon = *tol.as_ref());
212//! ```
213
214use crate::{RealScalar, core::errors::capture_backtrace};
215use derive_more::{AsRef, Display, LowerExp};
216use into_inner::IntoInner;
217use serde::{Deserialize, Serialize};
218use std::backtrace::Backtrace;
219use thiserror::Error;
220use try_create::TryNew;
221
222//------------------------------------------------------------------------------------------------------------
223// Error Types
224//------------------------------------------------------------------------------------------------------------
225
226/// Error type for tolerance validation failures.
227///
228/// This enum provides detailed error information when attempting to construct
229/// tolerance types ([`AbsoluteTolerance<T>`] and [`RelativeTolerance<T>`]) with
230/// invalid input values.
231///
232/// # Error Variants
233///
234/// - [`ErrorsTolerance::NegativeValue`]: The input value was negative, which violates
235/// the non-negativity constraint required for all tolerance values.
236///
237/// # Examples
238///
239/// ```rust
240/// use num_valid::scalars::{AbsoluteTolerance, ErrorsTolerance};
241/// use try_create::TryNew;
242///
243/// // Negative values are rejected
244/// match AbsoluteTolerance::try_new(-1e-6_f64) {
245/// Err(ErrorsTolerance::NegativeValue { value, .. }) => {
246/// println!("Rejected negative tolerance: {}", value);
247/// assert_eq!(value, -1e-6);
248/// }
249/// _ => unreachable!(),
250/// }
251/// ```
252///
253/// # Backtrace Support
254///
255/// The error includes backtrace information when the `backtrace` feature is enabled,
256/// which can aid in debugging by showing where the invalid value originated.
257#[derive(Debug, Error)]
258pub enum ErrorsTolerance<RealType: RealScalar> {
259 /// The input value was negative (must be ≥ 0).
260 #[error("Negative value detected: {value}")]
261 NegativeValue {
262 /// The negative value that was rejected.
263 value: RealType,
264 /// Captured backtrace for debugging.
265 backtrace: Backtrace,
266 },
267}
268
269/// Error type for [`PositiveRealScalar<T>`] validation failures.
270///
271/// This enum provides detailed error information when attempting to construct
272/// a [`PositiveRealScalar<T>`] with invalid input values.
273///
274/// # Error Variants
275///
276/// - [`ErrorsPositiveRealScalar::NegativeValue`]: The input was negative (< 0).
277/// - [`ErrorsPositiveRealScalar::ZeroValue`]: The input was zero (not strictly positive).
278///
279/// # Mathematical Distinction
280///
281/// Positive real numbers are defined as `ℝ⁺ = {x ∈ ℝ : x > 0}`, which **excludes** zero.
282/// This is distinct from non-negative reals `ℝ₀⁺ = {x ∈ ℝ : x ≥ 0}`.
283///
284/// # Examples
285///
286/// ```rust
287/// use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
288/// use try_create::TryNew;
289///
290/// // Zero is NOT positive
291/// match PositiveRealScalar::try_new(0.0_f64) {
292/// Err(ErrorsPositiveRealScalar::ZeroValue { .. }) => {
293/// println!("Zero is not strictly positive (x > 0 required)");
294/// }
295/// _ => unreachable!(),
296/// }
297///
298/// // Negative values rejected with the value included in the error
299/// match PositiveRealScalar::try_new(-2.5_f64) {
300/// Err(ErrorsPositiveRealScalar::NegativeValue { value, .. }) => {
301/// assert_eq!(value, -2.5);
302/// }
303/// _ => unreachable!(),
304/// }
305/// ```
306#[derive(Debug, Error)]
307pub enum ErrorsPositiveRealScalar<RealType: RealScalar> {
308 /// The input value was negative (< 0).
309 #[error("Negative value detected: {value}")]
310 NegativeValue {
311 /// The specific negative value that was rejected.
312 value: RealType,
313 /// Stack trace for debugging.
314 backtrace: Backtrace,
315 },
316
317 /// The input value was exactly zero (not strictly positive).
318 ///
319 /// Zero is NOT positive in mathematical terms. The positive real numbers
320 /// are defined as `ℝ⁺ = {x ∈ ℝ : x > 0}`, which excludes zero.
321 #[error("Zero value detected")]
322 ZeroValue {
323 /// Stack trace for debugging.
324 backtrace: Backtrace,
325 },
326}
327
328/// Error type for [`NonNegativeRealScalar<T>`] validation failures.
329///
330/// This enum provides error information when attempting to construct a
331/// [`NonNegativeRealScalar<T>`] with a negative value.
332///
333/// # Error Variants
334///
335/// - [`ErrorsNonNegativeRealScalar::NegativeValue`]: The input was negative (< 0).
336///
337/// Note that zero is **valid** for [`NonNegativeRealScalar`], unlike [`PositiveRealScalar`].
338///
339/// # Examples
340///
341/// ```rust
342/// use num_valid::scalars::{NonNegativeRealScalar, ErrorsNonNegativeRealScalar};
343/// use try_create::TryNew;
344///
345/// // Zero is valid for NonNegativeRealScalar
346/// let zero = NonNegativeRealScalar::try_new(0.0_f64);
347/// assert!(zero.is_ok());
348///
349/// // Negative values are rejected
350/// match NonNegativeRealScalar::try_new(-1.0_f64) {
351/// Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) => {
352/// assert_eq!(value, -1.0);
353/// }
354/// _ => unreachable!(),
355/// }
356/// ```
357#[derive(Debug, Error)]
358pub enum ErrorsNonNegativeRealScalar<RealType: RealScalar> {
359 /// The input value was negative (must be ≥ 0).
360 #[error("Negative value: {value} (must be non-negative, i.e., ≥ 0)")]
361 NegativeValue {
362 /// The negative value that was rejected.
363 value: RealType,
364 /// Stack trace for debugging.
365 backtrace: Backtrace,
366 },
367}
368
369//------------------------------------------------------------------------------------------------------------
370// AbsoluteTolerance
371//------------------------------------------------------------------------------------------------------------
372
373/// Type-safe wrapper for absolute tolerance values.
374///
375/// [`AbsoluteTolerance<T>`] represents a non-negative (≥ 0) tolerance value used for
376/// absolute error comparisons. It ensures that the tolerance is always valid by
377/// rejecting negative values at construction time.
378///
379/// # Mathematical Definition
380///
381/// An absolute tolerance `ε` is used in comparisons like:
382/// ```text
383/// |a - b| ≤ ε
384/// ```
385///
386/// Since `ε` represents a distance or error bound, it must be non-negative.
387///
388/// # Type Parameters
389///
390/// - `RealType`: The underlying real scalar type (e.g., `f64`, [`RealNative64StrictFinite`](crate::RealNative64StrictFinite))
391///
392/// # Examples
393///
394/// ## Basic Usage
395///
396/// ```rust
397/// use num_valid::scalars::AbsoluteTolerance;
398/// use try_create::TryNew;
399///
400/// // Create from f64
401/// let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
402/// assert_eq!(*tol.as_ref(), 1e-10);
403///
404/// // Use convenience constructors
405/// let zero = AbsoluteTolerance::<f64>::zero();
406/// let eps = AbsoluteTolerance::<f64>::epsilon();
407/// ```
408///
409/// ## In Numerical Comparisons
410///
411/// ```rust
412/// use num_valid::scalars::AbsoluteTolerance;
413/// use num_valid::RealScalar;
414/// use try_create::TryNew;
415///
416/// fn approximately_equal<T: RealScalar + Clone>(a: T, b: T, tol: &AbsoluteTolerance<T>) -> bool {
417/// let diff = (a - b).abs();
418/// &diff <= tol.as_ref()
419/// }
420///
421/// let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
422/// assert!(approximately_equal(1.0, 1.0 + 1e-11, &tol));
423/// assert!(!approximately_equal(1.0, 1.0 + 1e-9, &tol));
424/// ```
425///
426/// # Zero-Cost Abstraction
427///
428/// This type uses `#[repr(transparent)]` and has zero runtime overhead beyond
429/// the initial validation at construction time.
430#[derive(
431 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, LowerExp, Serialize, Deserialize,
432)]
433#[repr(transparent)]
434pub struct AbsoluteTolerance<RealType>(RealType);
435
436impl<RealType: RealScalar> AbsoluteTolerance<RealType> {
437 /// Creates an absolute tolerance of zero.
438 ///
439 /// A zero tolerance represents an exact comparison (no error allowed).
440 ///
441 /// # Examples
442 ///
443 /// ```rust
444 /// use num_valid::scalars::AbsoluteTolerance;
445 ///
446 /// let zero_tol = AbsoluteTolerance::<f64>::zero();
447 /// assert_eq!(*zero_tol.as_ref(), 0.0);
448 /// ```
449 #[inline(always)]
450 pub fn zero() -> Self {
451 AbsoluteTolerance(RealType::zero())
452 }
453
454 /// Creates an absolute tolerance equal to machine epsilon.
455 ///
456 /// Machine epsilon is the smallest value such that `1.0 + epsilon != 1.0`.
457 /// This is often a good default tolerance for floating-point comparisons.
458 ///
459 /// # Examples
460 ///
461 /// ```rust
462 /// use num_valid::scalars::AbsoluteTolerance;
463 ///
464 /// let eps_tol = AbsoluteTolerance::<f64>::epsilon();
465 /// assert_eq!(*eps_tol.as_ref(), f64::EPSILON);
466 /// ```
467 #[inline(always)]
468 pub fn epsilon() -> Self {
469 AbsoluteTolerance(RealType::epsilon())
470 }
471}
472
473impl<RealType: RealScalar> TryNew for AbsoluteTolerance<RealType> {
474 type Error = ErrorsTolerance<RealType>;
475
476 /// Attempts to create an [`AbsoluteTolerance`] from a value.
477 ///
478 /// # Errors
479 ///
480 /// Returns [`ErrorsTolerance::NegativeValue`] if the input is negative.
481 ///
482 /// # Panics (Debug Mode Only)
483 ///
484 /// In debug builds, panics if the input is not finite (NaN or infinity).
485 ///
486 /// # Examples
487 ///
488 /// ```rust
489 /// use num_valid::scalars::{AbsoluteTolerance, ErrorsTolerance};
490 /// use try_create::TryNew;
491 ///
492 /// // Valid tolerances
493 /// assert!(AbsoluteTolerance::try_new(1e-10_f64).is_ok());
494 /// assert!(AbsoluteTolerance::try_new(0.0_f64).is_ok());
495 ///
496 /// // Invalid (negative)
497 /// assert!(matches!(
498 /// AbsoluteTolerance::try_new(-1e-10_f64),
499 /// Err(ErrorsTolerance::NegativeValue { .. })
500 /// ));
501 /// ```
502 fn try_new(value: RealType) -> Result<Self, Self::Error> {
503 debug_assert!(value.is_finite(), "The input value {value} is not finite!");
504 if value.kernel_is_sign_negative() {
505 Err(ErrorsTolerance::NegativeValue {
506 value,
507 backtrace: capture_backtrace(),
508 })
509 } else {
510 Ok(Self(value))
511 }
512 }
513}
514
515//------------------------------------------------------------------------------------------------------------
516// RelativeTolerance
517//------------------------------------------------------------------------------------------------------------
518
519/// Type-safe wrapper for relative tolerance values.
520///
521/// [`RelativeTolerance<T>`] represents a non-negative (≥ 0) tolerance value used for
522/// relative (proportional) error comparisons. It can be converted to an absolute
523/// tolerance based on a reference value.
524///
525/// # Mathematical Definition
526///
527/// A relative tolerance `ε_rel` is used in comparisons like:
528/// ```text
529/// |a - b| ≤ ε_rel × |reference|
530/// ```
531///
532/// Common relative tolerances:
533/// - `0.01` = 1% tolerance
534/// - `0.001` = 0.1% tolerance
535/// - `1e-6` = one part per million
536///
537/// # Examples
538///
539/// ## Basic Usage
540///
541/// ```rust
542/// use num_valid::scalars::RelativeTolerance;
543/// use try_create::TryNew;
544///
545/// let one_percent = RelativeTolerance::try_new(0.01_f64).unwrap();
546/// assert_eq!(*one_percent.as_ref(), 0.01);
547/// ```
548///
549/// ## Converting to Absolute Tolerance
550///
551/// ```rust
552/// use num_valid::scalars::RelativeTolerance;
553/// use try_create::TryNew;
554///
555/// let rel_tol = RelativeTolerance::try_new(0.01_f64).unwrap(); // 1%
556///
557/// // Convert based on reference value
558/// let abs_tol = rel_tol.absolute_tolerance(1000.0);
559/// assert_eq!(*abs_tol.as_ref(), 10.0); // 1% of 1000 = 10
560///
561/// // Works with negative references (uses absolute value)
562/// let abs_tol_neg = rel_tol.absolute_tolerance(-500.0);
563/// assert_eq!(*abs_tol_neg.as_ref(), 5.0); // 1% of |-500| = 5
564/// ```
565#[derive(
566 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, LowerExp, Serialize, Deserialize,
567)]
568#[repr(transparent)]
569pub struct RelativeTolerance<RealType>(RealType);
570
571impl<RealType: RealScalar> RelativeTolerance<RealType> {
572 /// Creates a relative tolerance of zero.
573 ///
574 /// A zero relative tolerance represents an exact comparison.
575 ///
576 /// # Examples
577 ///
578 /// ```rust
579 /// use num_valid::scalars::RelativeTolerance;
580 ///
581 /// let zero_tol = RelativeTolerance::<f64>::zero();
582 /// assert_eq!(*zero_tol.as_ref(), 0.0);
583 /// ```
584 #[inline(always)]
585 pub fn zero() -> Self {
586 RelativeTolerance(RealType::zero())
587 }
588
589 /// Creates a relative tolerance equal to machine epsilon.
590 ///
591 /// # Examples
592 ///
593 /// ```rust
594 /// use num_valid::scalars::RelativeTolerance;
595 ///
596 /// let eps_tol = RelativeTolerance::<f64>::epsilon();
597 /// assert_eq!(*eps_tol.as_ref(), f64::EPSILON);
598 /// ```
599 #[inline(always)]
600 pub fn epsilon() -> Self {
601 RelativeTolerance(RealType::epsilon())
602 }
603
604 /// Converts this relative tolerance to an absolute tolerance based on a reference value.
605 ///
606 /// The absolute tolerance is computed as:
607 /// ```text
608 /// absolute_tolerance = relative_tolerance × |reference|
609 /// ```
610 ///
611 /// # Parameters
612 ///
613 /// - `reference`: The reference value to scale by. The absolute value is used.
614 ///
615 /// # Examples
616 ///
617 /// ```rust
618 /// use num_valid::scalars::RelativeTolerance;
619 /// use try_create::TryNew;
620 ///
621 /// let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap(); // 10%
622 ///
623 /// // 10% of 100 = 10
624 /// let abs_tol = rel_tol.absolute_tolerance(100.0);
625 /// assert_eq!(*abs_tol.as_ref(), 10.0);
626 ///
627 /// // 10% of |-50| = 5
628 /// let abs_tol_neg = rel_tol.absolute_tolerance(-50.0);
629 /// assert_eq!(*abs_tol_neg.as_ref(), 5.0);
630 ///
631 /// // 10% of 0 = 0
632 /// let abs_tol_zero = rel_tol.absolute_tolerance(0.0);
633 /// assert_eq!(*abs_tol_zero.as_ref(), 0.0);
634 /// ```
635 #[inline(always)]
636 pub fn absolute_tolerance(&self, reference: RealType) -> AbsoluteTolerance<RealType> {
637 let abs_tol = reference.abs() * &self.0;
638 AbsoluteTolerance(abs_tol)
639 }
640}
641
642impl<RealType: RealScalar> TryNew for RelativeTolerance<RealType> {
643 type Error = ErrorsTolerance<RealType>;
644
645 /// Attempts to create a [`RelativeTolerance`] from a value.
646 ///
647 /// # Errors
648 ///
649 /// Returns [`ErrorsTolerance::NegativeValue`] if the input is negative.
650 ///
651 /// # Panics (Debug Mode Only)
652 ///
653 /// In debug builds, panics if the input is not finite (NaN or infinity).
654 fn try_new(value: RealType) -> Result<Self, Self::Error> {
655 debug_assert!(value.is_finite(), "The input value {value} is not finite!");
656 if value.kernel_is_sign_negative() {
657 Err(ErrorsTolerance::NegativeValue {
658 value,
659 backtrace: capture_backtrace(),
660 })
661 } else {
662 Ok(Self(value))
663 }
664 }
665}
666
667//------------------------------------------------------------------------------------------------------------
668// PositiveRealScalar
669//------------------------------------------------------------------------------------------------------------
670
671/// Type-safe wrapper for strictly positive real scalar values.
672///
673/// [`PositiveRealScalar<T>`] guarantees that wrapped values are strictly greater than zero.
674/// Zero is **not** valid for this type. For values that can be zero, use [`NonNegativeRealScalar`].
675///
676/// # Mathematical Definition
677///
678/// ```text
679/// PositiveRealScalar<T> = { x ∈ T : x > 0 }
680/// ```
681///
682/// This is the set of positive real numbers `ℝ⁺`, which **excludes** zero.
683///
684/// # Use Cases
685///
686/// - Lengths and distances that cannot be zero
687/// - Positive tolerances
688/// - Scaling factors that must be positive
689/// - Any quantity that is mathematically required to be > 0
690///
691/// # Examples
692///
693/// ## Basic Usage
694///
695/// ```rust
696/// use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
697/// use try_create::TryNew;
698///
699/// // Valid positive values
700/// let length = PositiveRealScalar::try_new(2.5_f64).unwrap();
701/// let tiny = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
702///
703/// // Zero is NOT positive
704/// assert!(matches!(
705/// PositiveRealScalar::try_new(0.0_f64),
706/// Err(ErrorsPositiveRealScalar::ZeroValue { .. })
707/// ));
708///
709/// // Negative values rejected
710/// assert!(matches!(
711/// PositiveRealScalar::try_new(-1.0_f64),
712/// Err(ErrorsPositiveRealScalar::NegativeValue { .. })
713/// ));
714/// ```
715///
716/// ## Difference from [`NonNegativeRealScalar`]
717///
718/// ```rust
719/// use num_valid::scalars::{PositiveRealScalar, NonNegativeRealScalar};
720/// use try_create::TryNew;
721///
722/// // Zero is INVALID for PositiveRealScalar (x > 0)
723/// assert!(PositiveRealScalar::try_new(0.0_f64).is_err());
724///
725/// // Zero is VALID for NonNegativeRealScalar (x ≥ 0)
726/// assert!(NonNegativeRealScalar::try_new(0.0_f64).is_ok());
727/// ```
728#[derive(
729 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, Serialize, Deserialize,
730)]
731#[repr(transparent)]
732#[serde(bound(deserialize = "RealType: for<'a> Deserialize<'a>"))]
733pub struct PositiveRealScalar<RealType: RealScalar>(RealType);
734
735impl<RealType: RealScalar> TryNew for PositiveRealScalar<RealType> {
736 type Error = ErrorsPositiveRealScalar<RealType>;
737
738 /// Attempts to create a [`PositiveRealScalar`] from a value.
739 ///
740 /// # Errors
741 ///
742 /// - [`ErrorsPositiveRealScalar::NegativeValue`]: If the input is negative (< 0).
743 /// - [`ErrorsPositiveRealScalar::ZeroValue`]: If the input is zero.
744 ///
745 /// # Panics (Debug Mode Only)
746 ///
747 /// In debug builds, panics if the input is not finite (NaN or infinity).
748 ///
749 /// # Examples
750 ///
751 /// ```rust
752 /// use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
753 /// use try_create::TryNew;
754 ///
755 /// // Positive value succeeds
756 /// assert!(PositiveRealScalar::try_new(1.0_f64).is_ok());
757 ///
758 /// // Zero fails
759 /// assert!(matches!(
760 /// PositiveRealScalar::try_new(0.0_f64),
761 /// Err(ErrorsPositiveRealScalar::ZeroValue { .. })
762 /// ));
763 ///
764 /// // Negative fails
765 /// assert!(matches!(
766 /// PositiveRealScalar::try_new(-1.0_f64),
767 /// Err(ErrorsPositiveRealScalar::NegativeValue { value: v, .. }) if v == -1.0
768 /// ));
769 /// ```
770 fn try_new(value: RealType) -> Result<Self, Self::Error> {
771 debug_assert!(value.is_finite(), "The input value {value} is not finite!");
772 if value.kernel_is_sign_negative() {
773 Err(ErrorsPositiveRealScalar::NegativeValue {
774 value,
775 backtrace: capture_backtrace(),
776 })
777 } else if value.is_zero() {
778 Err(ErrorsPositiveRealScalar::ZeroValue {
779 backtrace: capture_backtrace(),
780 })
781 } else {
782 Ok(Self(value))
783 }
784 }
785}
786
787//------------------------------------------------------------------------------------------------------------
788// NonNegativeRealScalar
789//------------------------------------------------------------------------------------------------------------
790
791/// Type-safe wrapper for non-negative real scalar values.
792///
793/// [`NonNegativeRealScalar<T>`] guarantees that wrapped values are greater than or equal to zero.
794/// Zero **is** valid for this type. For values that must be strictly positive, use [`PositiveRealScalar`].
795///
796/// # Mathematical Definition
797///
798/// ```text
799/// NonNegativeRealScalar<T> = { x ∈ T : x ≥ 0 }
800/// ```
801///
802/// This is the set of non-negative real numbers `ℝ₀⁺`, which **includes** zero.
803///
804/// # Use Cases
805///
806/// - Distances (can be zero when comparing a value to itself)
807/// - Absolute values
808/// - Magnitudes
809/// - Any quantity that is mathematically required to be ≥ 0
810///
811/// # Examples
812///
813/// ## Basic Usage
814///
815/// ```rust
816/// use num_valid::scalars::{NonNegativeRealScalar, ErrorsNonNegativeRealScalar};
817/// use try_create::TryNew;
818///
819/// // Zero is valid
820/// let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
821/// assert_eq!(*zero.as_ref(), 0.0);
822///
823/// // Positive values are valid
824/// let positive = NonNegativeRealScalar::try_new(5.0_f64).unwrap();
825///
826/// // Negative values are rejected
827/// assert!(matches!(
828/// NonNegativeRealScalar::try_new(-1.0_f64),
829/// Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
830/// ));
831/// ```
832///
833/// ## Use Case: Computing Distances
834///
835/// ```rust
836/// use num_valid::scalars::NonNegativeRealScalar;
837/// use try_create::TryNew;
838///
839/// fn distance(a: f64, b: f64) -> NonNegativeRealScalar<f64> {
840/// NonNegativeRealScalar::try_new((a - b).abs()).unwrap()
841/// }
842///
843/// // Different points
844/// let d1 = distance(0.0, 5.0);
845/// assert_eq!(*d1.as_ref(), 5.0);
846///
847/// // Same point (zero distance is valid!)
848/// let d2 = distance(3.0, 3.0);
849/// assert_eq!(*d2.as_ref(), 0.0);
850/// ```
851#[derive(
852 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, Serialize, Deserialize,
853)]
854#[repr(transparent)]
855#[serde(bound(deserialize = "RealType: for<'a> Deserialize<'a>"))]
856pub struct NonNegativeRealScalar<RealType: RealScalar>(RealType);
857
858impl<RealType: RealScalar> TryNew for NonNegativeRealScalar<RealType> {
859 type Error = ErrorsNonNegativeRealScalar<RealType>;
860
861 /// Attempts to create a [`NonNegativeRealScalar`] from a value.
862 ///
863 /// # Errors
864 ///
865 /// Returns [`ErrorsNonNegativeRealScalar::NegativeValue`] if the input is negative.
866 ///
867 /// # Panics (Debug Mode Only)
868 ///
869 /// In debug builds, panics if the input is not finite (NaN or infinity).
870 ///
871 /// # Examples
872 ///
873 /// ```rust
874 /// use num_valid::scalars::{NonNegativeRealScalar, ErrorsNonNegativeRealScalar};
875 /// use try_create::TryNew;
876 ///
877 /// // Zero is valid
878 /// assert!(NonNegativeRealScalar::try_new(0.0_f64).is_ok());
879 ///
880 /// // Positive is valid
881 /// assert!(NonNegativeRealScalar::try_new(1.0_f64).is_ok());
882 ///
883 /// // Negative is rejected
884 /// assert!(matches!(
885 /// NonNegativeRealScalar::try_new(-1.0_f64),
886 /// Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
887 /// ));
888 /// ```
889 fn try_new(value: RealType) -> Result<Self, Self::Error> {
890 debug_assert!(value.is_finite(), "The input value {value} is not finite!");
891 if value.kernel_is_sign_negative() {
892 Err(ErrorsNonNegativeRealScalar::NegativeValue {
893 value,
894 backtrace: capture_backtrace(),
895 })
896 } else {
897 Ok(Self(value))
898 }
899 }
900}
901
902//------------------------------------------------------------------------------------------------------------
903// Tests
904//------------------------------------------------------------------------------------------------------------
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909 use crate::backends::native64::validated::RealNative64StrictFinite;
910
911 mod absolute_tolerance {
912 use super::*;
913
914 #[test]
915 fn try_new_valid() {
916 let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
917 assert_eq!(*tol.as_ref(), 1e-10);
918 }
919
920 #[test]
921 fn try_new_zero() {
922 let tol = AbsoluteTolerance::try_new(0.0_f64).unwrap();
923 assert_eq!(*tol.as_ref(), 0.0);
924 }
925
926 #[test]
927 fn try_new_negative() {
928 let result = AbsoluteTolerance::try_new(-1e-10_f64);
929 assert!(
930 matches!(result, Err(ErrorsTolerance::NegativeValue { value, .. }) if value == -1e-10)
931 );
932 }
933
934 #[test]
935 fn zero_constructor() {
936 let tol = AbsoluteTolerance::<f64>::zero();
937 assert_eq!(*tol.as_ref(), 0.0);
938 }
939
940 #[test]
941 fn epsilon_constructor() {
942 let tol = AbsoluteTolerance::<f64>::epsilon();
943 assert_eq!(*tol.as_ref(), f64::EPSILON);
944 }
945
946 #[test]
947 fn display_trait() {
948 let tol = AbsoluteTolerance::try_new(0.5_f64).unwrap();
949 assert_eq!(format!("{}", tol), "0.5");
950 }
951
952 #[test]
953 fn lower_exp_trait() {
954 let tol = AbsoluteTolerance::try_new(0.00001_f64).unwrap();
955 assert_eq!(format!("{:e}", tol), "1e-5");
956 }
957
958 #[test]
959 fn into_inner() {
960 let tol = AbsoluteTolerance::try_new(0.5_f64).unwrap();
961 let inner = tol.into_inner();
962 assert_eq!(inner, 0.5);
963 }
964
965 #[test]
966 fn clone_and_partial_eq() {
967 let tol1 = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
968 let tol2 = tol1.clone();
969 assert_eq!(tol1, tol2);
970 }
971
972 #[test]
973 fn partial_ord() {
974 let tol1 = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
975 let tol2 = AbsoluteTolerance::try_new(1e-9_f64).unwrap();
976 assert!(tol1 < tol2);
977 }
978
979 #[test]
980 fn serialize_deserialize() {
981 let tol = AbsoluteTolerance::try_new(0.5_f64).unwrap();
982 let serialized = serde_json::to_string(&tol).unwrap();
983 let deserialized: AbsoluteTolerance<f64> = serde_json::from_str(&serialized).unwrap();
984 assert_eq!(tol, deserialized);
985 }
986
987 #[test]
988 fn with_validated_type() {
989 let val = RealNative64StrictFinite::try_from_f64(1e-10).unwrap();
990 let tol = AbsoluteTolerance::try_new(val).unwrap();
991 assert_eq!(*tol.as_ref().as_ref(), 1e-10);
992 }
993
994 #[test]
995 #[cfg(debug_assertions)]
996 #[should_panic(expected = "is not finite")]
997 fn debug_panics_on_nan() {
998 let _ = AbsoluteTolerance::try_new(f64::NAN);
999 }
1000
1001 #[test]
1002 #[cfg(debug_assertions)]
1003 #[should_panic(expected = "is not finite")]
1004 fn debug_panics_on_infinity() {
1005 let _ = AbsoluteTolerance::try_new(f64::INFINITY);
1006 }
1007
1008 #[test]
1009 fn try_new() {
1010 // Test creating an AbsoluteTolerance instance with a value of 0.0
1011 let v = AbsoluteTolerance::<f64>::zero();
1012 assert_eq!(*v.as_ref(), 0.0);
1013
1014 // Test creating an AbsoluteTolerance instance with a positive value
1015 let v = AbsoluteTolerance::try_new(2.0).unwrap();
1016 assert_eq!(*v.as_ref(), 2.0);
1017 }
1018
1019 #[test]
1020 fn try_new_invalid_negative() {
1021 // Test creating an invalid AbsoluteTolerance instance with a negative value
1022 let tol = AbsoluteTolerance::try_new(-0.1);
1023 assert!(matches!(tol, Err(ErrorsTolerance::NegativeValue { .. })));
1024 }
1025
1026 #[test]
1027 #[cfg(debug_assertions)]
1028 #[should_panic = "The input value inf is not finite!"]
1029 fn try_new_invalid_infinite() {
1030 // Test creating an invalid AbsoluteTolerance instance with an infinite value
1031 let _tol = AbsoluteTolerance::try_new(f64::INFINITY);
1032 }
1033
1034 #[test]
1035 #[cfg(debug_assertions)]
1036 #[should_panic = "The input value NaN is not finite!"]
1037 fn try_new_invalid_nan() {
1038 // Test creating an invalid AbsoluteTolerance instance with a NaN value
1039 let _tol = AbsoluteTolerance::try_new(f64::NAN);
1040 }
1041
1042 #[test]
1043 fn epsilon() {
1044 // Test creating an AbsoluteTolerance instance with epsilon value
1045 let epsilon_tol = AbsoluteTolerance::<f64>::epsilon();
1046 assert_eq!(*epsilon_tol.as_ref(), f64::EPSILON);
1047 }
1048
1049 #[test]
1050 fn equality_operator() {
1051 // Test equality operator for AbsoluteTolerance instances
1052 let a = AbsoluteTolerance::<f64>::zero();
1053 let b = AbsoluteTolerance::<f64>::zero();
1054 assert!(a == b);
1055
1056 let c = AbsoluteTolerance::try_new(1.0).unwrap();
1057 let d = AbsoluteTolerance::try_new(1.0).unwrap();
1058 assert!(c == d);
1059 }
1060
1061 #[test]
1062 fn inequality_operator() {
1063 // Test inequality operator for AbsoluteTolerance instances
1064 let a = AbsoluteTolerance::<f64>::zero();
1065 let b = AbsoluteTolerance::try_new(1.0).unwrap();
1066 assert!(a != b);
1067
1068 let c = AbsoluteTolerance::try_new(1.0).unwrap();
1069 let d = AbsoluteTolerance::try_new(2.0).unwrap();
1070 assert!(c != d);
1071 }
1072
1073 #[test]
1074 fn debug_trait() {
1075 // Test the Debug trait
1076 let tolerance = AbsoluteTolerance::try_new(0.5).unwrap();
1077 assert_eq!(format!("{:?}", tolerance), "AbsoluteTolerance(0.5)");
1078 }
1079
1080 #[test]
1081 fn clone_trait() {
1082 // Test the Clone trait
1083 let tolerance = AbsoluteTolerance::try_new(0.5).unwrap();
1084 let cloned_tolerance = tolerance.clone();
1085 assert_eq!(tolerance, cloned_tolerance);
1086 }
1087
1088 #[test]
1089 fn partial_eq_trait() {
1090 // Test the PartialEq trait
1091 let tolerance1 = AbsoluteTolerance::try_new(0.5).unwrap();
1092 let tolerance2 = AbsoluteTolerance::try_new(0.5).unwrap();
1093 assert_eq!(tolerance1, tolerance2);
1094 }
1095
1096 #[test]
1097 fn partial_ord_trait() {
1098 // Test the PartialOrd trait
1099 let tolerance1 = AbsoluteTolerance::try_new(0.5).unwrap();
1100 let tolerance2 = AbsoluteTolerance::try_new(1.0).unwrap();
1101 assert!(tolerance1 < tolerance2);
1102 }
1103
1104 #[test]
1105 fn as_ref_trait() {
1106 // Test the AsRef trait
1107 let tolerance = AbsoluteTolerance::try_new(0.5).unwrap();
1108 let inner: &f64 = tolerance.as_ref();
1109 assert_eq!(*inner, 0.5);
1110 }
1111
1112 #[test]
1113 fn zero() {
1114 // Test the zero constructor
1115 let zero_tol = AbsoluteTolerance::<f64>::zero();
1116 assert_eq!(*zero_tol.as_ref(), 0.0);
1117 }
1118
1119 #[test]
1120 fn edge_cases() {
1121 // Test with very small positive values
1122 let tiny_tol = AbsoluteTolerance::try_new(f64::MIN_POSITIVE).unwrap();
1123 assert_eq!(*tiny_tol.as_ref(), f64::MIN_POSITIVE);
1124
1125 // Test with large positive values
1126 let large_tol = AbsoluteTolerance::try_new(1e100).unwrap();
1127 assert_eq!(*large_tol.as_ref(), 1e100);
1128 }
1129 }
1130
1131 mod relative_tolerance {
1132 use super::*;
1133
1134 #[test]
1135 fn try_new_valid() {
1136 let tol = RelativeTolerance::try_new(0.01_f64).unwrap();
1137 assert_eq!(*tol.as_ref(), 0.01);
1138 }
1139
1140 #[test]
1141 fn try_new_zero() {
1142 let tol = RelativeTolerance::try_new(0.0_f64).unwrap();
1143 assert_eq!(*tol.as_ref(), 0.0);
1144 }
1145
1146 #[test]
1147 fn try_new_negative() {
1148 let result = RelativeTolerance::try_new(-0.01_f64);
1149 assert!(matches!(result, Err(ErrorsTolerance::NegativeValue { .. })));
1150 }
1151
1152 #[test]
1153 fn zero_constructor() {
1154 let tol = RelativeTolerance::<f64>::zero();
1155 assert_eq!(*tol.as_ref(), 0.0);
1156 }
1157
1158 #[test]
1159 fn epsilon_constructor() {
1160 let tol = RelativeTolerance::<f64>::epsilon();
1161 assert_eq!(*tol.as_ref(), f64::EPSILON);
1162 }
1163
1164 #[test]
1165 fn absolute_tolerance_positive_reference() {
1166 let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap();
1167 let abs_tol = rel_tol.absolute_tolerance(100.0);
1168 assert_eq!(*abs_tol.as_ref(), 10.0);
1169 }
1170
1171 #[test]
1172 fn absolute_tolerance_negative_reference() {
1173 let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap();
1174 let abs_tol = rel_tol.absolute_tolerance(-50.0);
1175 assert_eq!(*abs_tol.as_ref(), 5.0);
1176 }
1177
1178 #[test]
1179 fn absolute_tolerance_zero_reference() {
1180 let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap();
1181 let abs_tol = rel_tol.absolute_tolerance(0.0);
1182 assert_eq!(*abs_tol.as_ref(), 0.0);
1183 }
1184
1185 #[test]
1186 fn display_trait() {
1187 let tol = RelativeTolerance::try_new(0.5_f64).unwrap();
1188 assert_eq!(format!("{}", tol), "0.5");
1189 }
1190
1191 #[test]
1192 fn into_inner() {
1193 let tol = RelativeTolerance::try_new(0.01_f64).unwrap();
1194 let inner = tol.into_inner();
1195 assert_eq!(inner, 0.01);
1196 }
1197
1198 #[test]
1199 fn serialize_deserialize() {
1200 let tol = RelativeTolerance::try_new(0.01_f64).unwrap();
1201 let serialized = serde_json::to_string(&tol).unwrap();
1202 let deserialized: RelativeTolerance<f64> = serde_json::from_str(&serialized).unwrap();
1203 assert_eq!(tol, deserialized);
1204 }
1205
1206 #[test]
1207 fn try_new() {
1208 // Test creating a RelativeTolerance instance with a value of 0.0
1209 let v = RelativeTolerance::<f64>::zero();
1210 assert_eq!(*v.as_ref(), 0.0);
1211
1212 // Test creating a RelativeTolerance instance with a positive value
1213 let v = RelativeTolerance::try_new(2.0).unwrap();
1214 assert_eq!(*v.as_ref(), 2.0);
1215 }
1216
1217 #[test]
1218 fn try_new_invalid_negative() {
1219 // Test creating an invalid RelativeTolerance instance with a negative value
1220 let tol = RelativeTolerance::try_new(-0.1);
1221 assert!(matches!(tol, Err(ErrorsTolerance::NegativeValue { .. })));
1222 }
1223
1224 #[test]
1225 #[cfg(debug_assertions)]
1226 #[should_panic = "The input value inf is not finite!"]
1227 fn try_new_invalid_infinite() {
1228 // Test creating an invalid RelativeTolerance instance with an infinite value
1229 let _tol = RelativeTolerance::try_new(f64::INFINITY);
1230 }
1231
1232 #[test]
1233 #[cfg(debug_assertions)]
1234 #[should_panic = "The input value NaN is not finite!"]
1235 fn try_new_invalid_nan() {
1236 // Test creating an invalid RelativeTolerance instance with a NaN value
1237 let _tol = RelativeTolerance::try_new(f64::NAN);
1238 }
1239
1240 #[test]
1241 fn epsilon() {
1242 // Test creating a RelativeTolerance instance with epsilon value
1243 let epsilon_tol = RelativeTolerance::<f64>::epsilon();
1244 assert_eq!(*epsilon_tol.as_ref(), f64::EPSILON);
1245 }
1246
1247 #[test]
1248 fn absolute_tolerance() {
1249 // Test converting relative tolerance to absolute tolerance
1250 let rel_tol = RelativeTolerance::try_new(0.1).unwrap();
1251 let reference = 10.0;
1252 let abs_tol = rel_tol.absolute_tolerance(reference);
1253 assert_eq!(*abs_tol.as_ref(), 1.0); // 0.1 * |10.0| = 1.0
1254
1255 // Test with negative reference value
1256 let reference = -5.0;
1257 let abs_tol = rel_tol.absolute_tolerance(reference);
1258 assert_eq!(*abs_tol.as_ref(), 0.5); // 0.1 * |-5.0| = 0.5
1259
1260 // Test with zero reference value
1261 let reference = 0.0;
1262 let abs_tol = rel_tol.absolute_tolerance(reference);
1263 assert_eq!(*abs_tol.as_ref(), 0.0); // 0.1 * |0.0| = 0.0
1264 }
1265
1266 #[test]
1267 fn equality_operator() {
1268 // Test equality operator for RelativeTolerance instances
1269 let a = RelativeTolerance::<f64>::zero();
1270 let b = RelativeTolerance::<f64>::zero();
1271 assert!(a == b);
1272
1273 let c = RelativeTolerance::try_new(1.0).unwrap();
1274 let d = RelativeTolerance::try_new(1.0).unwrap();
1275 assert!(c == d);
1276 }
1277
1278 #[test]
1279 fn inequality_operator() {
1280 // Test inequality operator for RelativeTolerance instances
1281 let a = RelativeTolerance::<f64>::zero();
1282 let b = RelativeTolerance::try_new(1.0).unwrap();
1283 assert!(a != b);
1284
1285 let c = RelativeTolerance::try_new(1.0).unwrap();
1286 let d = RelativeTolerance::try_new(2.0).unwrap();
1287 assert!(c != d);
1288 }
1289
1290 #[test]
1291 fn debug_trait() {
1292 // Test the Debug trait
1293 let tolerance = RelativeTolerance::try_new(0.5).unwrap();
1294 assert_eq!(format!("{:?}", tolerance), "RelativeTolerance(0.5)");
1295 }
1296
1297 #[test]
1298 fn clone_trait() {
1299 // Test the Clone trait
1300 let tolerance = RelativeTolerance::try_new(0.5).unwrap();
1301 let cloned_tolerance = tolerance.clone();
1302 assert_eq!(tolerance, cloned_tolerance);
1303 }
1304
1305 #[test]
1306 fn partial_eq_trait() {
1307 // Test the PartialEq trait
1308 let tolerance1 = RelativeTolerance::try_new(0.5).unwrap();
1309 let tolerance2 = RelativeTolerance::try_new(0.5).unwrap();
1310 assert_eq!(tolerance1, tolerance2);
1311 }
1312
1313 #[test]
1314 fn partial_ord_trait() {
1315 // Test the PartialOrd trait
1316 let tolerance1 = RelativeTolerance::try_new(0.5).unwrap();
1317 let tolerance2 = RelativeTolerance::try_new(1.0).unwrap();
1318 assert!(tolerance1 < tolerance2);
1319 }
1320
1321 #[test]
1322 fn as_ref_trait() {
1323 // Test the AsRef trait
1324 let tolerance = RelativeTolerance::try_new(0.5).unwrap();
1325 let inner: &f64 = tolerance.as_ref();
1326 assert_eq!(*inner, 0.5);
1327 }
1328
1329 #[test]
1330 fn lower_exp_trait() {
1331 // Test the LowerExp trait (scientific notation formatting)
1332 let tolerance = RelativeTolerance::try_new(0.00001).unwrap();
1333 assert_eq!(format!("{:e}", tolerance), "1e-5");
1334 }
1335 }
1336
1337 mod positive_real_scalar {
1338 use super::*;
1339 use crate::backends::native64::validated::RealNative64StrictFiniteInDebug;
1340
1341 #[test]
1342 fn try_new_positive() {
1343 let val = PositiveRealScalar::try_new(2.5_f64).unwrap();
1344 assert_eq!(*val.as_ref(), 2.5);
1345 }
1346
1347 #[test]
1348 fn try_new_min_positive() {
1349 let val = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
1350 assert_eq!(*val.as_ref(), f64::MIN_POSITIVE);
1351 }
1352
1353 #[test]
1354 fn try_new_zero_fails() {
1355 let result = PositiveRealScalar::try_new(0.0_f64);
1356 assert!(matches!(
1357 result,
1358 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
1359 ));
1360 }
1361
1362 #[test]
1363 fn try_new_negative_fails() {
1364 let result = PositiveRealScalar::try_new(-1.0_f64);
1365 assert!(
1366 matches!(result, Err(ErrorsPositiveRealScalar::NegativeValue { value, .. }) if value == -1.0)
1367 );
1368 }
1369
1370 #[test]
1371 fn display_trait() {
1372 let val = PositiveRealScalar::try_new(1.618_f64).unwrap();
1373 assert_eq!(format!("{}", val), "1.618");
1374 }
1375
1376 #[test]
1377 fn into_inner() {
1378 let val = PositiveRealScalar::try_new(42.0_f64).unwrap();
1379 let inner = val.into_inner();
1380 assert_eq!(inner, 42.0);
1381 }
1382
1383 #[test]
1384 fn partial_ord() {
1385 let a = PositiveRealScalar::try_new(1.0_f64).unwrap();
1386 let b = PositiveRealScalar::try_new(2.0_f64).unwrap();
1387 assert!(a < b);
1388 }
1389
1390 #[test]
1391 fn serialize_deserialize() {
1392 let val = PositiveRealScalar::try_new(3.0_f64).unwrap();
1393 let serialized = serde_json::to_string(&val).unwrap();
1394 let deserialized: PositiveRealScalar<f64> = serde_json::from_str(&serialized).unwrap();
1395 assert_eq!(val, deserialized);
1396 }
1397
1398 #[test]
1399 fn with_validated_type() {
1400 let inner = RealNative64StrictFinite::try_from_f64(5.0).unwrap();
1401 let val = PositiveRealScalar::try_new(inner).unwrap();
1402 assert_eq!(*val.as_ref().as_ref(), 5.0);
1403 }
1404
1405 #[test]
1406 #[cfg(debug_assertions)]
1407 #[should_panic(expected = "is not finite")]
1408 fn debug_panics_on_nan() {
1409 let _ = PositiveRealScalar::try_new(f64::NAN);
1410 }
1411
1412 #[test]
1413 fn try_new() {
1414 // Test creating a PositiveRealScalar instance with a positive value
1415 let v = PositiveRealScalar::try_new(2.5).unwrap();
1416 assert_eq!(*v.as_ref(), 2.5);
1417
1418 // Test creating a PositiveRealScalar instance with a very small positive value
1419 let v = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
1420 assert_eq!(*v.as_ref(), f64::MIN_POSITIVE);
1421
1422 // Test creating a PositiveRealScalar instance with a large positive value
1423 let v = PositiveRealScalar::try_new(1e100).unwrap();
1424 assert_eq!(*v.as_ref(), 1e100);
1425 }
1426
1427 #[test]
1428 fn try_new_invalid_negative() {
1429 // Test creating an invalid PositiveRealScalar instance with a negative value
1430 let scalar = PositiveRealScalar::try_new(-0.1);
1431 assert!(matches!(
1432 scalar,
1433 Err(ErrorsPositiveRealScalar::NegativeValue { .. })
1434 ));
1435
1436 // Test with a negative value close to zero
1437 let scalar = PositiveRealScalar::try_new(-f64::EPSILON);
1438 assert!(matches!(
1439 scalar,
1440 Err(ErrorsPositiveRealScalar::NegativeValue { .. })
1441 ));
1442 }
1443
1444 #[test]
1445 fn try_new_invalid_zero() {
1446 // Test creating an invalid PositiveRealScalar instance with zero
1447 let scalar = PositiveRealScalar::try_new(0.0);
1448 assert!(matches!(
1449 scalar,
1450 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
1451 ));
1452 }
1453
1454 #[test]
1455 #[cfg(debug_assertions)]
1456 #[should_panic = "The input value inf is not finite!"]
1457 fn try_new_invalid_infinite() {
1458 // Test creating an invalid PositiveRealScalar instance with an infinite value
1459 let _scalar = PositiveRealScalar::try_new(f64::INFINITY);
1460 }
1461
1462 #[test]
1463 #[cfg(debug_assertions)]
1464 #[should_panic = "The input value -inf is not finite!"]
1465 fn try_new_invalid_negative_infinite() {
1466 // Test creating an invalid PositiveRealScalar instance with negative infinity
1467 let _scalar = PositiveRealScalar::try_new(f64::NEG_INFINITY);
1468 }
1469
1470 #[test]
1471 #[cfg(debug_assertions)]
1472 #[should_panic = "The input value NaN is not finite!"]
1473 fn try_new_invalid_nan() {
1474 // Test creating an invalid PositiveRealScalar instance with a NaN value
1475 let _scalar = PositiveRealScalar::try_new(f64::NAN);
1476 }
1477
1478 #[test]
1479 fn equality_operator() {
1480 // Test equality operator for PositiveRealScalar instances
1481 let a = PositiveRealScalar::try_new(1.5).unwrap();
1482 let b = PositiveRealScalar::try_new(1.5).unwrap();
1483 assert!(a == b);
1484
1485 let c = PositiveRealScalar::try_new(3.1).unwrap();
1486 let d = PositiveRealScalar::try_new(3.1).unwrap();
1487 assert!(c == d);
1488 }
1489
1490 #[test]
1491 fn inequality_operator() {
1492 // Test inequality operator for PositiveRealScalar instances
1493 let a = PositiveRealScalar::try_new(1.0).unwrap();
1494 let b = PositiveRealScalar::try_new(2.0).unwrap();
1495 assert!(a != b);
1496
1497 let c = PositiveRealScalar::try_new(0.5).unwrap();
1498 let d = PositiveRealScalar::try_new(1.5).unwrap();
1499 assert!(c != d);
1500 }
1501
1502 #[test]
1503 fn debug_trait() {
1504 // Test the Debug trait
1505 let scalar = PositiveRealScalar::try_new(2.7).unwrap();
1506 assert_eq!(format!("{:?}", scalar), "PositiveRealScalar(2.7)");
1507 }
1508
1509 #[test]
1510 fn clone_trait() {
1511 // Test the Clone trait
1512 let scalar = PositiveRealScalar::try_new(1.414).unwrap();
1513 let cloned_scalar = scalar.clone();
1514 assert_eq!(scalar, cloned_scalar);
1515 }
1516
1517 #[test]
1518 fn partial_eq_trait() {
1519 // Test the PartialEq trait
1520 let scalar1 = PositiveRealScalar::try_new(0.577).unwrap();
1521 let scalar2 = PositiveRealScalar::try_new(0.577).unwrap();
1522 assert_eq!(scalar1, scalar2);
1523 }
1524
1525 #[test]
1526 fn partial_ord_trait() {
1527 // Test the PartialOrd trait
1528 let scalar1 = PositiveRealScalar::try_new(1.0).unwrap();
1529 let scalar2 = PositiveRealScalar::try_new(2.0).unwrap();
1530 assert!(scalar1 < scalar2);
1531
1532 let scalar3 = PositiveRealScalar::try_new(3.0).unwrap();
1533 let scalar4 = PositiveRealScalar::try_new(3.0).unwrap();
1534 assert!(scalar3 <= scalar4);
1535 assert!(scalar4 >= scalar3);
1536 }
1537
1538 #[test]
1539 fn as_ref_trait() {
1540 // Test the AsRef trait
1541 let scalar = PositiveRealScalar::try_new(2.0).unwrap();
1542 let inner: &f64 = scalar.as_ref();
1543 assert_eq!(*inner, 2.0);
1544 }
1545
1546 #[test]
1547 fn edge_cases() {
1548 // Test with very small positive values
1549 let tiny_scalar = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
1550 assert_eq!(*tiny_scalar.as_ref(), f64::MIN_POSITIVE);
1551
1552 // Test with large positive values
1553 let large_scalar = PositiveRealScalar::try_new(f64::MAX).unwrap();
1554 assert_eq!(*large_scalar.as_ref(), f64::MAX);
1555
1556 // Test with epsilon
1557 let epsilon_scalar = PositiveRealScalar::try_new(f64::EPSILON).unwrap();
1558 assert_eq!(*epsilon_scalar.as_ref(), f64::EPSILON);
1559 }
1560
1561 #[test]
1562 fn generic_scalar_types() {
1563 // Test with RealNative64StrictFiniteInDebug type
1564 let scalar =
1565 PositiveRealScalar::try_new(RealNative64StrictFiniteInDebug::try_new(5.0).unwrap())
1566 .unwrap();
1567 assert_eq!(
1568 *scalar.as_ref(),
1569 RealNative64StrictFiniteInDebug::try_new(5.0).unwrap()
1570 );
1571
1572 // Test error case with generic type
1573 let zero_value = RealNative64StrictFiniteInDebug::try_new(0.0).unwrap();
1574 let scalar_result = PositiveRealScalar::try_new(zero_value);
1575 assert!(matches!(
1576 scalar_result,
1577 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
1578 ));
1579 }
1580
1581 #[test]
1582 fn mathematical_operations() {
1583 // Test that the wrapped values behave correctly in mathematical contexts
1584 let scalar1 = PositiveRealScalar::try_new(2.0).unwrap();
1585 let scalar2 = PositiveRealScalar::try_new(3.0).unwrap();
1586
1587 // Test comparison operations
1588 assert!(scalar1 < scalar2);
1589 assert!(scalar2 > scalar1);
1590 assert_eq!(
1591 scalar1.partial_cmp(&scalar2),
1592 Some(std::cmp::Ordering::Less)
1593 );
1594 }
1595
1596 #[test]
1597 fn error_messages() {
1598 // Test that error messages are descriptive
1599 let negative_result = PositiveRealScalar::try_new(-1.0);
1600 match negative_result {
1601 Err(ErrorsPositiveRealScalar::NegativeValue { value, .. }) => {
1602 assert_eq!(value, -1.0);
1603 }
1604 _ => panic!("Expected NegativeValue error"),
1605 }
1606
1607 let zero_result = PositiveRealScalar::try_new(0.0);
1608 match zero_result {
1609 Err(ErrorsPositiveRealScalar::ZeroValue { .. }) => {
1610 // Expected
1611 }
1612 _ => panic!("Expected ZeroValue error"),
1613 }
1614 }
1615 }
1616
1617 mod non_negative_real_scalar {
1618 use super::*;
1619 use crate::backends::native64::validated::RealNative64StrictFiniteInDebug;
1620
1621 #[test]
1622 fn try_new_negative_fails() {
1623 let result = NonNegativeRealScalar::try_new(-1.0_f64);
1624 assert!(
1625 matches!(result, Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) if value == -1.0)
1626 );
1627 }
1628
1629 #[test]
1630 fn display_trait() {
1631 let val = NonNegativeRealScalar::try_new(2.7_f64).unwrap();
1632 assert_eq!(format!("{}", val), "2.7");
1633
1634 let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
1635 assert_eq!(format!("{}", zero), "0");
1636 }
1637
1638 #[test]
1639 fn partial_ord() {
1640 let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
1641 let positive = NonNegativeRealScalar::try_new(1.0_f64).unwrap();
1642 assert!(zero < positive);
1643 }
1644
1645 #[test]
1646 fn serialize_deserialize() {
1647 let val = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
1648 let serialized = serde_json::to_string(&val).unwrap();
1649 let deserialized: NonNegativeRealScalar<f64> =
1650 serde_json::from_str(&serialized).unwrap();
1651 assert_eq!(val, deserialized);
1652 }
1653
1654 #[test]
1655 fn with_validated_type() {
1656 let inner = RealNative64StrictFinite::try_from_f64(0.0).unwrap();
1657 let val = NonNegativeRealScalar::try_new(inner).unwrap();
1658 assert_eq!(*val.as_ref().as_ref(), 0.0);
1659 }
1660
1661 #[test]
1662 #[cfg(debug_assertions)]
1663 #[should_panic(expected = "is not finite")]
1664 fn debug_panics_on_nan() {
1665 let _ = NonNegativeRealScalar::try_new(f64::NAN);
1666 }
1667
1668 #[test]
1669 fn try_new_positive() {
1670 // Test creating a NonNegativeRealScalar instance with positive values
1671 let v = NonNegativeRealScalar::try_new(2.5).unwrap();
1672 assert_eq!(*v.as_ref(), 2.5);
1673
1674 // Test with very small positive value
1675 let v = NonNegativeRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
1676 assert_eq!(*v.as_ref(), f64::MIN_POSITIVE);
1677
1678 // Test with large positive value
1679 let v = NonNegativeRealScalar::try_new(1e100).unwrap();
1680 assert_eq!(*v.as_ref(), 1e100);
1681 }
1682
1683 #[test]
1684 fn try_new_zero() {
1685 // Test creating a NonNegativeRealScalar instance with zero (VALID case)
1686 let v = NonNegativeRealScalar::try_new(0.0).unwrap();
1687 assert_eq!(*v.as_ref(), 0.0);
1688
1689 // This is the key difference from PositiveRealScalar:
1690 // Zero is VALID for NonNegativeRealScalar (x ≥ 0)
1691 // but INVALID for PositiveRealScalar (x > 0)
1692 }
1693
1694 #[test]
1695 fn try_new_invalid_negative() {
1696 // Test creating an invalid NonNegativeRealScalar instance with negative values
1697 let scalar = NonNegativeRealScalar::try_new(-0.1);
1698 assert!(matches!(
1699 scalar,
1700 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
1701 ));
1702
1703 // Test with negative value close to zero
1704 let scalar = NonNegativeRealScalar::try_new(-f64::EPSILON);
1705 assert!(matches!(
1706 scalar,
1707 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
1708 ));
1709
1710 // Test with large negative value
1711 let scalar = NonNegativeRealScalar::try_new(-100.0);
1712 assert!(matches!(
1713 scalar,
1714 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
1715 ));
1716 }
1717
1718 #[test]
1719 #[cfg(debug_assertions)]
1720 #[should_panic = "The input value inf is not finite!"]
1721 fn try_new_invalid_infinite() {
1722 // Test creating an invalid NonNegativeRealScalar instance with infinity
1723 let _scalar = NonNegativeRealScalar::try_new(f64::INFINITY);
1724 }
1725
1726 #[test]
1727 #[cfg(debug_assertions)]
1728 #[should_panic = "The input value -inf is not finite!"]
1729 fn try_new_invalid_negative_infinite() {
1730 // Test creating an invalid NonNegativeRealScalar instance with negative infinity
1731 let _scalar = NonNegativeRealScalar::try_new(f64::NEG_INFINITY);
1732 }
1733
1734 #[test]
1735 #[cfg(debug_assertions)]
1736 #[should_panic = "The input value NaN is not finite!"]
1737 fn try_new_invalid_nan() {
1738 // Test creating an invalid NonNegativeRealScalar instance with NaN
1739 let _scalar = NonNegativeRealScalar::try_new(f64::NAN);
1740 }
1741
1742 #[test]
1743 fn equality_operator() {
1744 // Test equality operator for NonNegativeRealScalar instances
1745 let a = NonNegativeRealScalar::try_new(1.5).unwrap();
1746 let b = NonNegativeRealScalar::try_new(1.5).unwrap();
1747 assert!(a == b);
1748
1749 let c = NonNegativeRealScalar::try_new(0.0).unwrap();
1750 let d = NonNegativeRealScalar::try_new(0.0).unwrap();
1751 assert!(c == d);
1752 }
1753
1754 #[test]
1755 fn inequality_operator() {
1756 // Test inequality operator for NonNegativeRealScalar instances
1757 let a = NonNegativeRealScalar::try_new(0.0).unwrap();
1758 let b = NonNegativeRealScalar::try_new(1.0).unwrap();
1759 assert!(a != b);
1760
1761 let c = NonNegativeRealScalar::try_new(0.5).unwrap();
1762 let d = NonNegativeRealScalar::try_new(1.5).unwrap();
1763 assert!(c != d);
1764 }
1765
1766 #[test]
1767 fn debug_trait() {
1768 // Test the Debug trait
1769 let scalar = NonNegativeRealScalar::try_new(2.7).unwrap();
1770 assert_eq!(format!("{:?}", scalar), "NonNegativeRealScalar(2.7)");
1771
1772 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
1773 assert_eq!(format!("{:?}", zero), "NonNegativeRealScalar(0.0)");
1774 }
1775
1776 #[test]
1777 fn clone_trait() {
1778 // Test the Clone trait
1779 let scalar = NonNegativeRealScalar::try_new(1.414).unwrap();
1780 let cloned_scalar = scalar.clone();
1781 assert_eq!(scalar, cloned_scalar);
1782 }
1783
1784 #[test]
1785 fn partial_eq_trait() {
1786 // Test the PartialEq trait
1787 let scalar1 = NonNegativeRealScalar::try_new(0.577).unwrap();
1788 let scalar2 = NonNegativeRealScalar::try_new(0.577).unwrap();
1789 assert_eq!(scalar1, scalar2);
1790
1791 // Test with zero
1792 let zero1 = NonNegativeRealScalar::try_new(0.0).unwrap();
1793 let zero2 = NonNegativeRealScalar::try_new(0.0).unwrap();
1794 assert_eq!(zero1, zero2);
1795 }
1796
1797 #[test]
1798 fn partial_ord_trait() {
1799 // Test the PartialOrd trait
1800 let scalar1 = NonNegativeRealScalar::try_new(0.0).unwrap();
1801 let scalar2 = NonNegativeRealScalar::try_new(1.0).unwrap();
1802 assert!(scalar1 < scalar2);
1803
1804 let scalar3 = NonNegativeRealScalar::try_new(3.0).unwrap();
1805 let scalar4 = NonNegativeRealScalar::try_new(3.0).unwrap();
1806 assert!(scalar3 <= scalar4);
1807 assert!(scalar4 >= scalar3);
1808
1809 // Test that zero is less than positive values
1810 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
1811 let positive = NonNegativeRealScalar::try_new(0.001).unwrap();
1812 assert!(zero < positive);
1813 }
1814
1815 #[test]
1816 fn as_ref_trait() {
1817 // Test the AsRef trait
1818 let scalar = NonNegativeRealScalar::try_new(2.0).unwrap();
1819 let inner: &f64 = scalar.as_ref();
1820 assert_eq!(*inner, 2.0);
1821
1822 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
1823 let inner: &f64 = zero.as_ref();
1824 assert_eq!(*inner, 0.0);
1825 }
1826
1827 #[test]
1828 fn into_inner() {
1829 // Test converting a NonNegativeRealScalar instance into its inner value
1830 let scalar = NonNegativeRealScalar::try_new(42.0).unwrap();
1831 let inner = scalar.into_inner();
1832 assert_eq!(inner, 42.0);
1833
1834 // Test with zero
1835 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
1836 let inner = zero.into_inner();
1837 assert_eq!(inner, 0.0);
1838
1839 // Test with very small positive value
1840 let scalar = NonNegativeRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
1841 let inner = scalar.into_inner();
1842 assert_eq!(inner, f64::MIN_POSITIVE);
1843 }
1844
1845 #[test]
1846 fn edge_cases() {
1847 // Test with zero (key edge case)
1848 let zero_scalar = NonNegativeRealScalar::try_new(0.0).unwrap();
1849 assert_eq!(*zero_scalar.as_ref(), 0.0);
1850
1851 // Test with very small positive values
1852 let tiny_scalar = NonNegativeRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
1853 assert_eq!(*tiny_scalar.as_ref(), f64::MIN_POSITIVE);
1854
1855 // Test with large positive values
1856 let large_scalar = NonNegativeRealScalar::try_new(f64::MAX).unwrap();
1857 assert_eq!(*large_scalar.as_ref(), f64::MAX);
1858
1859 // Test with epsilon
1860 let epsilon_scalar = NonNegativeRealScalar::try_new(f64::EPSILON).unwrap();
1861 assert_eq!(*epsilon_scalar.as_ref(), f64::EPSILON);
1862 }
1863
1864 #[test]
1865 fn generic_scalar_types() {
1866 // Test with RealNative64StrictFiniteInDebug type
1867 let scalar = NonNegativeRealScalar::try_new(
1868 RealNative64StrictFiniteInDebug::try_new(5.0).unwrap(),
1869 )
1870 .unwrap();
1871 assert_eq!(
1872 *scalar.as_ref(),
1873 RealNative64StrictFiniteInDebug::try_new(5.0).unwrap()
1874 );
1875
1876 // Test with zero (valid for NonNegativeRealScalar)
1877 let zero_value = RealNative64StrictFiniteInDebug::try_new(0.0).unwrap();
1878 let scalar_result = NonNegativeRealScalar::try_new(zero_value);
1879 assert!(scalar_result.is_ok()); // Should succeed!
1880
1881 // Test error case with generic type
1882 let negative_value = RealNative64StrictFiniteInDebug::try_new(-1.0).unwrap();
1883 let scalar_result = NonNegativeRealScalar::try_new(negative_value);
1884 assert!(matches!(
1885 scalar_result,
1886 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
1887 ));
1888 }
1889
1890 #[test]
1891 fn mathematical_operations() {
1892 // Test that the wrapped values behave correctly in mathematical contexts
1893 let scalar1 = NonNegativeRealScalar::try_new(0.0).unwrap();
1894 let scalar2 = NonNegativeRealScalar::try_new(2.0).unwrap();
1895 let scalar3 = NonNegativeRealScalar::try_new(3.0).unwrap();
1896
1897 // Test comparison operations
1898 assert!(scalar1 < scalar2);
1899 assert!(scalar2 < scalar3);
1900 assert!(scalar3 > scalar1);
1901 assert_eq!(
1902 scalar1.partial_cmp(&scalar2),
1903 Some(std::cmp::Ordering::Less)
1904 );
1905 assert_eq!(
1906 scalar2.partial_cmp(&scalar1),
1907 Some(std::cmp::Ordering::Greater)
1908 );
1909 }
1910
1911 #[test]
1912 fn error_messages() {
1913 // Test that error messages are descriptive
1914 let negative_result = NonNegativeRealScalar::try_new(-1.0);
1915 match negative_result {
1916 Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) => {
1917 assert_eq!(value, -1.0);
1918 }
1919 _ => panic!("Expected NegativeValue error"),
1920 }
1921
1922 let large_negative_result = NonNegativeRealScalar::try_new(-100.5);
1923 match large_negative_result {
1924 Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) => {
1925 assert_eq!(value, -100.5);
1926 }
1927 _ => panic!("Expected NegativeValue error"),
1928 }
1929 }
1930
1931 #[test]
1932 fn distinction_from_positive_real_scalar() {
1933 // Critical test: demonstrate the key difference between
1934 // NonNegativeRealScalar (x ≥ 0) and PositiveRealScalar (x > 0)
1935
1936 // Zero is VALID for NonNegativeRealScalar
1937 let non_neg_zero = NonNegativeRealScalar::try_new(0.0);
1938 assert!(
1939 non_neg_zero.is_ok(),
1940 "Zero should be valid for NonNegativeRealScalar (x ≥ 0)"
1941 );
1942
1943 // Zero is INVALID for PositiveRealScalar
1944 let pos_zero = PositiveRealScalar::try_new(0.0);
1945 assert!(
1946 pos_zero.is_err(),
1947 "Zero should be invalid for PositiveRealScalar (x > 0)"
1948 );
1949 assert!(matches!(
1950 pos_zero,
1951 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
1952 ));
1953
1954 // Positive values are valid for both
1955 let non_neg_positive = NonNegativeRealScalar::try_new(1.0);
1956 let pos_positive = PositiveRealScalar::try_new(1.0);
1957 assert!(non_neg_positive.is_ok());
1958 assert!(pos_positive.is_ok());
1959
1960 // Negative values are invalid for both
1961 let non_neg_negative = NonNegativeRealScalar::try_new(-1.0);
1962 let pos_negative = PositiveRealScalar::try_new(-1.0);
1963 assert!(non_neg_negative.is_err());
1964 assert!(pos_negative.is_err());
1965 }
1966
1967 #[test]
1968 fn use_case_distance() {
1969 // Real-world use case: computing distances (which can be zero)
1970 fn compute_distance(x1: f64, x2: f64) -> NonNegativeRealScalar<f64> {
1971 let diff = (x2 - x1).abs();
1972 NonNegativeRealScalar::try_new(diff).unwrap()
1973 }
1974
1975 // Distance between different points
1976 let dist1 = compute_distance(0.0, 5.0);
1977 assert_eq!(*dist1.as_ref(), 5.0);
1978
1979 // Distance from a point to itself (zero distance is valid!)
1980 let dist2 = compute_distance(3.0, 3.0);
1981 assert_eq!(*dist2.as_ref(), 0.0);
1982 }
1983
1984 #[test]
1985 fn use_case_absolute_value() {
1986 // Real-world use case: absolute values (which can be zero)
1987 fn safe_abs(x: f64) -> NonNegativeRealScalar<f64> {
1988 NonNegativeRealScalar::try_new(x.abs()).unwrap()
1989 }
1990
1991 assert_eq!(*safe_abs(-5.0).as_ref(), 5.0);
1992 assert_eq!(*safe_abs(0.0).as_ref(), 0.0); // Zero is valid!
1993 assert_eq!(*safe_abs(3.0).as_ref(), 3.0);
1994 }
1995 }
1996
1997 mod distinction_tests {
1998 use super::*;
1999
2000 #[test]
2001 fn positive_vs_non_negative_zero() {
2002 // This is the KEY difference between the two types
2003
2004 // Zero is INVALID for PositiveRealScalar (x > 0)
2005 assert!(PositiveRealScalar::try_new(0.0_f64).is_err());
2006
2007 // Zero is VALID for NonNegativeRealScalar (x ≥ 0)
2008 assert!(NonNegativeRealScalar::try_new(0.0_f64).is_ok());
2009 }
2010
2011 #[test]
2012 fn both_accept_positive() {
2013 let positive = 1.0_f64;
2014 assert!(PositiveRealScalar::try_new(positive).is_ok());
2015 assert!(NonNegativeRealScalar::try_new(positive).is_ok());
2016 }
2017
2018 #[test]
2019 fn both_reject_negative() {
2020 let negative = -1.0_f64;
2021 assert!(PositiveRealScalar::try_new(negative).is_err());
2022 assert!(NonNegativeRealScalar::try_new(negative).is_err());
2023 }
2024 }
2025}