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