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 five 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//! | [`HashableScalar<T>`] | Finite | N/A | Hash-map and hash-set keys for real or complex scalars |
21//!
22//! [`AbsoluteTolerance<T>`], [`RelativeTolerance<T>`], [`PositiveRealScalar<T>`], and
23//! [`NonNegativeRealScalar<T>`] are generic over [`RealScalar`], enabling consistent validation
24//! across different numerical backends (native `f64`, arbitrary-precision `rug`, etc.).
25//! [`HashableScalar<T>`] is generic over [`FpScalar`] and supports both real and complex scalars.
26//!
27//! ## Architectural Design
28//!
29//! - **Tolerance Types** ([`AbsoluteTolerance<T>`], [`RelativeTolerance<T>`]): Semantically specialized
30//! wrappers built on top of [`NonNegativeRealScalar<T>`]. They inherit all validation and provide
31//! tolerance-specific methods like [`RelativeTolerance::absolute_tolerance()`].
32//! - **Constrained Scalar Types** ([`PositiveRealScalar<T>`], [`NonNegativeRealScalar<T>`]): Full-featured
33//! types with algebraic operations (addition, multiplication, powers), comparison traits, and immutable
34//! access patterns. They form the foundation for general mathematical computations.
35//! - **Hashable Scalars** ([`HashableScalar<T>`]): Finite scalar wrappers that provide [`Eq`] and
36//! [`Hash`] for use as keys in [`std::collections::HashMap`] and members of
37//! [`std::collections::HashSet`].
38//! - **Comparative Utilities**: The [`approximately_equal()`] function provides precise, tolerance-aware
39//! value comparisons across all scalar types.
40//!
41//! # Design Philosophy
42//!
43//! ## Type Safety Through Distinct Types
44//!
45//! Rather than using raw primitives like `f64` directly, this module provides
46//! semantically meaningful types that encode mathematical constraints:
47//!
48//! ```rust
49//! use num_valid::{
50//! backends::native::validated::RealNative64StrictFinite, RealScalar,
51//! scalars::{AbsoluteTolerance, RelativeTolerance, PositiveRealScalar},
52//! };
53//! use try_create::TryNew;
54//!
55//! // These are different types that cannot be confused:
56//! let abs_tol = AbsoluteTolerance::try_new(
57//! RealNative64StrictFinite::try_new(1e-10_f64).unwrap()
58//! ).unwrap(); // For absolute comparisons
59//! let rel_tol = RelativeTolerance::try_new(1e-6_f64).unwrap(); // For relative comparisons
60//! let length = PositiveRealScalar::try_new(5.0_f64).unwrap(); // Must be > 0
61//!
62//! // Type system prevents mixing them up:
63//! // let wrong: AbsoluteTolerance<f64> = rel_tol; // ← Compilation error!
64//! ```
65//!
66//! ## Validation at Construction Time
67//!
68//! All types validate their input at construction time, failing fast on invalid values:
69//!
70//! ```rust
71//! use num_valid::{*, scalars::*};
72//! use try_create::TryNew;
73//! use std::assert_matches;
74//!
75//! // Negative tolerances are rejected
76//! let invalid_tol = AbsoluteTolerance::try_new(
77//! RealNative64StrictFinite::try_new(-1e-6_f64).unwrap()
78//! ).unwrap_err();
79//! assert_matches!(invalid_tol, ErrorsNonNegativeRealScalar::NegativeValue { .. });
80//!
81//! // Zero is not positive
82//! let invalid_pos = PositiveRealScalar::try_new(0.0_f64).unwrap_err();
83//! assert_matches!(invalid_pos, ErrorsPositiveRealScalar::ZeroValue { .. });
84//! ```
85//!
86//! # Error Handling
87//!
88//! Both [`AbsoluteTolerance<T>`] and [`RelativeTolerance<T>`] use [`ErrorsNonNegativeRealScalar`]
89//! for error reporting, since they are wrappers around [`NonNegativeRealScalar<T>`]. This provides
90//! consistent error semantics:
91//!
92//! - **No negative values:** Both types reject negative inputs
93//! - **Debug validation:** NaN and infinity are panicked on in debug builds
94//! - **Backtrace support:** When the `backtrace` feature is enabled
95//!
96//! # Tolerance Types
97//!
98//! ## [`AbsoluteTolerance<T>`]
99//!
100//! Represents an absolute error bound for numerical comparisons. The tolerance value
101//! must be non-negative (≥ 0).
102//!
103//! ```rust
104//! use num_valid::{*, scalars::*};
105//! use try_create::TryNew;
106//!
107//! // Create tolerances
108//! let tight = AbsoluteTolerance::try_new(1e-12_f64).unwrap();
109//! let loose = AbsoluteTolerance::try_new(1e-6_f64).unwrap();
110//! let zero = AbsoluteTolerance::<f64>::zero(); // Exact comparison
111//! let epsilon = AbsoluteTolerance::<f64>::epsilon(); // Machine epsilon
112//!
113//! // Use in approximate comparison
114//! assert!(approximately_equal(1.0, 1.0 + 1e-13, &tight));
115//! assert!(!approximately_equal(1.0, 1.0 + 1e-11, &tight));
116//! ```
117//!
118//! ## [`RelativeTolerance<T>`]
119//!
120//! Represents a relative (proportional) error bound. Can be converted to an absolute
121//! tolerance based on a reference value.
122//!
123//! ```rust
124//! use num_valid::{*, scalars::RelativeTolerance};
125//! use try_create::TryNew;
126//!
127//! let rel_tol = RelativeTolerance::try_new(0.01_f64).unwrap(); // 1% tolerance
128//!
129//! // Convert to absolute tolerance based on reference value
130//! let reference = 1000.0_f64;
131//! let abs_tol = rel_tol.absolute_tolerance(reference);
132//! assert_eq!(abs_tol.as_raw_scalar_ref(), &10.0); // 1% of 1000 = 10
133//! ```
134//!
135//! # Constrained Real Number Types
136//!
137//! ## [`PositiveRealScalar<T>`]
138//!
139//! Wraps a real scalar that must be strictly positive (> 0). Zero is **not** valid.
140//!
141//! ```rust
142//! use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
143//! use try_create::TryNew;
144//!
145//! // Valid positive values
146//! let length = PositiveRealScalar::try_new(2.5_f64).unwrap();
147//! let tiny = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
148//!
149//! // Zero is NOT positive (x > 0 required)
150//! let zero_result = PositiveRealScalar::try_new(0.0_f64);
151//! assert!(matches!(zero_result, Err(ErrorsPositiveRealScalar::ZeroValue { .. })));
152//!
153//! // Negative values rejected
154//! let neg_result = PositiveRealScalar::try_new(-1.0_f64);
155//! assert!(matches!(neg_result, Err(ErrorsPositiveRealScalar::NegativeValue { .. })));
156//! ```
157//!
158//! ## [`NonNegativeRealScalar<T>`]
159//!
160//! Wraps a real scalar that must be non-negative (≥ 0). Zero **is** valid.
161//!
162//! ```rust
163//! use num_valid::{*, scalars::*};
164//! use try_create::TryNew;
165//!
166//! // Zero is valid for NonNegativeRealScalar
167//! let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
168//! assert_eq!(*zero.as_raw_scalar_ref(), 0.0);
169//!
170//! // But NOT for PositiveRealScalar
171//! assert!(PositiveRealScalar::try_new(0.0_f64).is_err());
172//!
173//! // Use case: computing distances (can be zero)
174//! fn distance(a: f64, b: f64) -> NonNegativeRealScalar<f64> {
175//! NonNegativeRealScalar::try_new((a - b).abs()).unwrap()
176//! }
177//!
178//! let d = distance(5.0, 5.0); // Zero distance is valid
179//! assert_eq!(d.as_raw_scalar_ref(), &0.0);
180//! ```
181//!
182//! # Generic Programming with [`RealScalar`]
183//!
184//! All types work seamlessly with any scalar type implementing [`RealScalar`]:
185//!
186//! ```rust
187//! use num_valid::{
188//! backends::native::validated::{RealNative64StrictFinite, RealNative64StrictFiniteInDebug},
189//! RealScalar,
190//! scalars::AbsoluteTolerance
191//! };
192//! use try_create::TryNew;
193//!
194//! // Same tolerance type works with different backends
195//! type FastTol = AbsoluteTolerance<f64>;
196//! type SafeTol = AbsoluteTolerance<RealNative64StrictFinite>;
197//! type DebugTol = AbsoluteTolerance<RealNative64StrictFiniteInDebug>;
198//!
199//! let fast = FastTol::try_new(1e-10).unwrap();
200//! let safe = SafeTol::try_new(RealNative64StrictFinite::try_from_f64(1e-10).unwrap()).unwrap();
201//! ```
202//!
203//! # Performance Characteristics
204//!
205//! All wrapper types are designed as zero-cost abstractions:
206//!
207//! | Type | Memory Layout | Runtime Overhead |
208//! |------|---------------|------------------|
209//! | [`AbsoluteTolerance<T>`] | Same as `T` | Zero (validation at construction only) |
210//! | [`RelativeTolerance<T>`] | Same as `T` | Zero (validation at construction only) |
211//! | [`PositiveRealScalar<T>`] | Same as `T` | Zero (validation at construction only) |
212//! | [`NonNegativeRealScalar<T>`] | Same as `T` | Zero (validation at construction only) |
213//!
214//! The `#[repr(transparent)]` attribute ensures that each wrapper has the exact same
215//! memory layout as its underlying type.
216//!
217//! # Relationship to `approx` Crate
218//!
219//! These tolerance types complement the [`approx`] crate's comparison traits. While `approx`
220//! uses raw `f64` for epsilon values (which can be negative, causing silent failures),
221//! these wrappers guarantee non-negativity at construction time:
222//!
223//! ```rust
224//! use num_valid::{*, scalars::AbsoluteTolerance, approx::assert_abs_diff_eq};
225//!
226//! // Create a validated tolerance
227//! let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
228//!
229//! // Use with approx (extract the inner value)
230//! let a = 1.0_f64;
231//! let b = 1.0 + 1e-11;
232//! assert_abs_diff_eq!(a, b, epsilon = *tol.as_raw_scalar_ref());
233//! ```
234
235use crate::{
236 FpScalar, Pow, RealScalar, ScalarCore,
237 algorithms::accumulators::{NeumaierAddable, neumaier_sum_and_compensation_real},
238 core::{errors::capture_backtrace, traits::raw::RawScalarTrait},
239 functions::{Max, Min, PowIntExponentErrors},
240};
241use derive_more::{AsRef, Display, LowerExp};
242use duplicate::duplicate_item;
243use into_inner::IntoInner;
244use num::{One, Zero};
245use serde::{Deserialize, Serialize};
246use std::{
247 backtrace::Backtrace,
248 hash::{Hash, Hasher},
249 ops::{Add, AddAssign, Mul, MulAssign},
250};
251use thiserror::Error;
252use try_create::TryNew;
253
254//------------------------------------------------------------------------------------------------------------
255// Error Types
256//------------------------------------------------------------------------------------------------------------
257
258/// Error type for [`PositiveRealScalar<T>`] validation failures.
259///
260/// This enum provides detailed error information when attempting to construct
261/// a [`PositiveRealScalar<T>`] with invalid input values.
262///
263/// # Error Variants
264///
265/// - [`ErrorsPositiveRealScalar::NegativeValue`]: The input was negative (< 0).
266/// - [`ErrorsPositiveRealScalar::ZeroValue`]: The input was zero (not strictly positive).
267///
268/// # Mathematical Distinction
269///
270/// Positive real numbers are defined as `ℝ⁺ = {x ∈ ℝ : x > 0}`, which **excludes** zero.
271/// This is distinct from non-negative reals `ℝ₀⁺ = {x ∈ ℝ : x ≥ 0}`.
272///
273/// # Examples
274///
275/// ```rust
276/// use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
277/// use try_create::TryNew;
278///
279/// // Zero is NOT positive
280/// match PositiveRealScalar::try_new(0.0_f64) {
281/// Err(ErrorsPositiveRealScalar::ZeroValue { .. }) => {
282/// println!("Zero is not strictly positive (x > 0 required)");
283/// }
284/// _ => unreachable!(),
285/// }
286///
287/// // Negative values rejected with the value included in the error
288/// match PositiveRealScalar::try_new(-2.5_f64) {
289/// Err(ErrorsPositiveRealScalar::NegativeValue { value, .. }) => {
290/// assert_eq!(value, -2.5);
291/// }
292/// _ => unreachable!(),
293/// }
294/// ```
295#[derive(Debug, Error)]
296pub enum ErrorsPositiveRealScalar<RealType: RealScalar> {
297 /// The input value was negative (< 0).
298 #[error("Negative value detected: {value} (valid value must be strictly positive, i.e., > 0)")]
299 NegativeValue {
300 /// The specific negative value that was rejected.
301 value: RealType,
302 /// Stack trace for debugging.
303 backtrace: Backtrace,
304 },
305
306 /// The input value was exactly zero (not strictly positive).
307 ///
308 /// Zero is NOT positive in mathematical terms. The positive real numbers
309 /// are defined as `ℝ⁺ = {x ∈ ℝ : x > 0}`, which excludes zero.
310 #[error("Zero value detected (valid value must be strictly positive, i.e., > 0)")]
311 ZeroValue {
312 /// Stack trace for debugging.
313 backtrace: Backtrace,
314 },
315}
316
317/// Error type for [`NonNegativeRealScalar<T>`] validation failures.
318///
319/// This enum provides error information when attempting to construct a
320/// [`NonNegativeRealScalar<T>`] with a negative value.
321///
322/// # Error Variants
323///
324/// - [`ErrorsNonNegativeRealScalar::NegativeValue`]: The input was negative (< 0).
325///
326/// Note that zero is **valid** for [`NonNegativeRealScalar`], unlike [`PositiveRealScalar`].
327///
328/// # Examples
329///
330/// ```rust
331/// use num_valid::scalars::{NonNegativeRealScalar, ErrorsNonNegativeRealScalar};
332/// use try_create::TryNew;
333///
334/// // Zero is valid for NonNegativeRealScalar
335/// let zero = NonNegativeRealScalar::try_new(0.0_f64);
336/// assert!(zero.is_ok());
337///
338/// // Negative values are rejected
339/// match NonNegativeRealScalar::try_new(-1.0_f64) {
340/// Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) => {
341/// assert_eq!(value, -1.0);
342/// }
343/// _ => unreachable!(),
344/// }
345/// ```
346#[derive(Debug, Error)]
347pub enum ErrorsNonNegativeRealScalar<RealType: RealScalar> {
348 /// The input value was negative (must be ≥ 0).
349 #[error("Negative value detected: {value} (valid value must be non-negative, i.e., ≥ 0)")]
350 NegativeValue {
351 /// The negative value that was rejected.
352 value: RealType,
353 /// Stack trace for debugging.
354 backtrace: Backtrace,
355 },
356}
357
358//------------------------------------------------------------------------------------------------------------
359// AbsoluteTolerance
360//------------------------------------------------------------------------------------------------------------
361
362/// Type-safe wrapper for absolute tolerance values.
363///
364/// [`AbsoluteTolerance<T>`] represents a non-negative (≥ 0) tolerance value used for
365/// absolute error comparisons. It ensures that the tolerance is always valid by
366/// rejecting negative values at construction time.
367///
368/// # Implementation
369///
370/// This type is a `#[repr(transparent)]` wrapper around [`NonNegativeRealScalar<T>`].
371/// All validation is performed by the underlying `NonNegativeRealScalar`, and errors
372/// are reported as [`ErrorsNonNegativeRealScalar`].
373///
374/// # Mathematical Definition
375///
376/// An absolute tolerance `ε` is used in comparisons like:
377/// ```text
378/// |a - b| ≤ ε
379/// ```
380///
381/// Since `ε` represents a distance or error bound, it must be non-negative.
382///
383/// # Type Parameters
384///
385/// - `RealType`: The underlying real scalar type (e.g., `f64`, [`RealNative64StrictFinite`](crate::RealNative64StrictFinite))
386///
387/// # Examples
388///
389/// ## Basic Usage
390///
391/// ```rust
392/// use num_valid::{*, scalars::AbsoluteTolerance};
393///
394/// // Create from f64
395/// let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
396/// assert_eq!(tol.as_raw_scalar_ref(), &1e-10);
397///
398/// // Use convenience constructors
399/// let zero = AbsoluteTolerance::<f64>::zero();
400/// let eps = AbsoluteTolerance::<f64>::epsilon();
401/// ```
402///
403/// ## In Numerical Comparisons
404///
405/// ```rust
406/// use num_valid::{*, scalars::*};
407/// use try_create::{IntoInner, TryNew};
408///
409/// let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
410/// assert!(approximately_equal(1.0, 1.0 + 1e-11, &tol));
411/// assert!(!approximately_equal(1.0, 1.0 + 1e-9, &tol));
412/// ```
413///
414/// # Zero-Cost Abstraction
415///
416/// This type uses `#[repr(transparent)]` and has zero runtime overhead beyond
417/// the initial validation at construction time.
418// `AsRef`/`IntoInner` expose the wrapped `NonNegativeRealScalar<RealType>` as-is; use
419// `as_raw_scalar_ref()`/`into_raw_scalar()` to unwrap all the way down to the raw primitive.
420#[derive(
421 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, LowerExp, Serialize, Deserialize,
422)]
423#[serde(bound(deserialize = "RealType: for<'a> Deserialize<'a>"))]
424#[repr(transparent)]
425pub struct AbsoluteTolerance<RealType: RealScalar>(NonNegativeRealScalar<RealType>);
426
427impl<RealType: RealScalar> AbsoluteTolerance<RealType> {
428 /// Creates an absolute tolerance of zero.
429 ///
430 /// A zero tolerance represents an exact comparison (no error allowed).
431 ///
432 /// # Examples
433 ///
434 /// ```rust
435 /// use num_valid::{*, scalars::AbsoluteTolerance};
436 ///
437 /// let zero_tol = AbsoluteTolerance::<f64>::zero();
438 /// assert_eq!(zero_tol.as_raw_scalar_ref(), &0.0);
439 /// ```
440 #[inline(always)]
441 pub fn zero() -> Self {
442 AbsoluteTolerance(NonNegativeRealScalar::zero())
443 }
444
445 /// Creates an absolute tolerance equal to machine epsilon.
446 ///
447 /// Machine epsilon is the smallest value such that `1.0 + epsilon != 1.0`.
448 /// This is often a good default tolerance for floating-point comparisons.
449 ///
450 /// # Examples
451 ///
452 /// ```rust
453 /// use num_valid::{*, scalars::AbsoluteTolerance};
454 ///
455 /// let eps_tol = AbsoluteTolerance::<f64>::epsilon();
456 /// assert_eq!(eps_tol.as_raw_scalar_ref(), &f64::EPSILON);
457 /// ```
458 #[inline(always)]
459 pub fn epsilon() -> Self {
460 AbsoluteTolerance(NonNegativeRealScalar(RealType::epsilon()))
461 }
462
463 /// Wraps a [`NonNegativeRealScalar<T>`] as an [`AbsoluteTolerance<T>`].
464 ///
465 /// This constructor allows explicit conversion from a validated non-negative scalar
466 /// to a tolerance type, providing semantic clarity.
467 ///
468 /// # Examples
469 ///
470 /// ```rust
471 /// use num_valid::{*, scalars::*};
472 /// use try_create::TryNew;
473 ///
474 /// let non_neg = NonNegativeRealScalar::try_new(1e-10_f64).unwrap();
475 /// let tol = AbsoluteTolerance::new(non_neg);
476 /// assert_eq!(tol.as_raw_scalar_ref(), &1e-10);
477 /// ```
478 pub fn new(value: NonNegativeRealScalar<RealType>) -> Self {
479 AbsoluteTolerance(value)
480 }
481
482 /// Attempts to create a [`AbsoluteTolerance`] from a value.
483 ///
484 /// # Errors
485 ///
486 /// Returns [`ErrorsNonNegativeRealScalar::NegativeValue`] if the input is negative.
487 ///
488 /// # Panics (Debug Mode Only)
489 ///
490 /// In debug builds, panics if the input is not finite (NaN or infinity).
491 pub fn try_new(value: RealType) -> Result<Self, ErrorsNonNegativeRealScalar<RealType>> {
492 NonNegativeRealScalar::try_new(value).map(AbsoluteTolerance)
493 }
494}
495
496impl<RealType: RealScalar> ScalarCore for AbsoluteTolerance<RealType> {
497 type RawScalarType = RealType::RawScalarType;
498
499 #[inline(always)]
500 fn as_raw_scalar_ref(&self) -> &Self::RawScalarType {
501 self.0.as_raw_scalar_ref()
502 }
503
504 fn into_raw_scalar(self) -> Self::RawScalarType {
505 self.0.into_raw_scalar()
506 }
507}
508
509/// Compares two scalar values within an absolute tolerance.
510///
511/// Tests whether the absolute difference between two values is less than or equal to
512/// a specified tolerance. This is the standard approach for approximate floating-point
513/// comparisons when the expected magnitude of values is known.
514///
515/// # Mathematical Definition
516///
517/// Returns `true` if and only if:
518/// ```text
519/// |a - b| ≤ tolerance
520/// ```
521///
522/// # Symmetry
523///
524/// The comparison is symmetric: `approximately_equal(a, b, tol) == approximately_equal(b, a, tol)`
525///
526/// # Type Parameters
527///
528/// - `T`: Any scalar type implementing [`RealScalar`]
529///
530/// # Arguments
531///
532/// - `a`: First value
533/// - `b`: Second value to compare with
534/// - `tolerance`: The maximum allowed absolute difference (must be non-negative)
535///
536/// # Examples
537///
538/// ## Basic Floating-Point Comparison
539///
540/// ```rust
541/// use num_valid::scalars::{AbsoluteTolerance, approximately_equal};
542/// use try_create::TryNew;
543///
544/// let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
545///
546/// // Values within tolerance
547/// assert!(approximately_equal(1.0, 1.0 + 1e-11, &tol));
548///
549/// // Values outside tolerance
550/// assert!(!approximately_equal(1.0, 1.0 + 1e-9, &tol));
551///
552/// // Exact match
553/// assert!(approximately_equal(1.0, 1.0, &tol));
554/// ```
555///
556/// ## Zero Tolerance (Exact Comparison)
557///
558/// ```rust
559/// use num_valid::scalars::{AbsoluteTolerance, approximately_equal};
560///
561/// let exact_tol = AbsoluteTolerance::<f64>::zero();
562///
563/// // Only exact matches pass
564/// assert!(approximately_equal(1.0, 1.0, &exact_tol));
565/// assert!(!approximately_equal(1.0, 1.0 + 1e-15, &exact_tol));
566/// ```
567///
568/// ## Negative Values
569///
570/// ```rust
571/// use num_valid::scalars::{AbsoluteTolerance, approximately_equal};
572/// use try_create::TryNew;
573///
574/// let tol = AbsoluteTolerance::try_new(0.1_f64).unwrap();
575///
576/// // Works with negative values
577/// assert!(approximately_equal(-5.0, -5.05, &tol));
578/// assert!(!approximately_equal(-5.0, -5.2, &tol));
579/// ```
580///
581/// # When to Use
582///
583/// - **Use `approximately_equal()`** when you have a fixed maximum allowable error (absolute tolerance)
584/// - **Use [`RelativeTolerance::absolute_tolerance()`]** when the tolerance is proportional to values
585///
586/// # See Also
587///
588/// - [`AbsoluteTolerance`] for creating and managing absolute tolerances
589/// - [`RelativeTolerance`] for proportional error bounds
590pub fn approximately_equal<T: RealScalar>(a: T, b: T, tolerance: &AbsoluteTolerance<T>) -> bool {
591 let diff = (a - b).abs();
592 &diff <= tolerance.as_ref()
593}
594
595//------------------------------------------------------------------------------------------------------------
596// RelativeTolerance
597//------------------------------------------------------------------------------------------------------------
598
599/// Type-safe wrapper for relative tolerance values.
600///
601/// [`RelativeTolerance<T>`] represents a non-negative (≥ 0) tolerance value used for
602/// relative (proportional) error comparisons. It can be converted to an absolute
603/// tolerance based on a reference value.
604///
605/// # Implementation
606///
607/// This type is a `#[repr(transparent)]` wrapper around [`NonNegativeRealScalar<T>`].
608/// All validation is performed by the underlying `NonNegativeRealScalar`, and errors
609/// are reported as [`ErrorsNonNegativeRealScalar`].
610///
611/// # Mathematical Definition
612///
613/// A relative tolerance `ε_rel` is used in comparisons like:
614/// ```text
615/// |a - b| ≤ ε_rel × |reference|
616/// ```
617///
618/// Common relative tolerances:
619/// - `0.01` = 1% tolerance
620/// - `0.001` = 0.1% tolerance
621/// - `1e-6` = one part per million
622///
623/// # Examples
624///
625/// ## Basic Usage
626///
627/// ```rust
628/// use num_valid::{*, scalars::RelativeTolerance};
629/// use try_create::TryNew;
630///
631/// let one_percent = RelativeTolerance::try_new(0.01_f64).unwrap();
632/// assert_eq!(*one_percent.as_raw_scalar_ref(), 0.01);
633/// ```
634///
635/// ## Converting to Absolute Tolerance
636///
637/// ```rust
638/// use num_valid::{*, scalars::RelativeTolerance};
639/// use try_create::TryNew;
640///
641/// let rel_tol = RelativeTolerance::try_new(0.01_f64).unwrap(); // 1%
642///
643/// // Convert based on reference value
644/// let abs_tol = rel_tol.absolute_tolerance(1000.0);
645/// assert_eq!(*abs_tol.as_raw_scalar_ref(), 10.0); // 1% of 1000 = 10
646///
647/// // Works with negative references (uses absolute value)
648/// let abs_tol_neg = rel_tol.absolute_tolerance(-500.0);
649/// assert_eq!(*abs_tol_neg.as_raw_scalar_ref(), 5.0); // 1% of |-500| = 5
650/// ```
651// `AsRef`/`IntoInner` expose the wrapped `NonNegativeRealScalar<RealType>` as-is; use
652// `as_raw_scalar_ref()`/`into_raw_scalar()` to unwrap all the way down to the raw primitive.
653#[derive(
654 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, LowerExp, Serialize, Deserialize,
655)]
656#[serde(bound(deserialize = "RealType: for<'a> Deserialize<'a>"))]
657#[repr(transparent)]
658pub struct RelativeTolerance<RealType: RealScalar>(NonNegativeRealScalar<RealType>);
659
660impl<RealType: RealScalar> RelativeTolerance<RealType> {
661 /// Creates a relative tolerance of zero.
662 ///
663 /// A zero relative tolerance represents an exact comparison.
664 ///
665 /// # Examples
666 ///
667 /// ```rust
668 /// use num_valid::{*, scalars::RelativeTolerance};
669 ///
670 /// let zero_tol = RelativeTolerance::<f64>::zero();
671 /// assert_eq!(zero_tol.as_raw_scalar_ref(), &0.0);
672 /// ```
673 #[inline(always)]
674 pub fn zero() -> Self {
675 RelativeTolerance(NonNegativeRealScalar::zero())
676 }
677
678 /// Creates a relative tolerance equal to machine epsilon.
679 ///
680 /// # Examples
681 ///
682 /// ```rust
683 /// use num_valid::{*, scalars::RelativeTolerance};
684 ///
685 /// let eps_tol = RelativeTolerance::<f64>::epsilon();
686 /// assert_eq!(eps_tol.as_raw_scalar_ref(), &f64::EPSILON);
687 /// ```
688 #[inline(always)]
689 pub fn epsilon() -> Self {
690 RelativeTolerance(NonNegativeRealScalar(RealType::epsilon()))
691 }
692
693 /// Converts this relative tolerance to an absolute tolerance based on a reference value.
694 ///
695 /// The absolute tolerance is computed as:
696 /// ```text
697 /// absolute_tolerance = relative_tolerance × |reference|
698 /// ```
699 ///
700 /// # Parameters
701 ///
702 /// - `reference`: The reference value to scale by. The absolute value is used.
703 ///
704 /// # Examples
705 ///
706 /// ```rust
707 /// use num_valid::{*, scalars::RelativeTolerance};
708 /// use try_create::TryNew;
709 ///
710 /// let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap(); // 10%
711 ///
712 /// // 10% of 100 = 10
713 /// let abs_tol = rel_tol.absolute_tolerance(100.0);
714 /// assert_eq!(*abs_tol.as_raw_scalar_ref(), 10.0);
715 ///
716 /// // 10% of |-50| = 5
717 /// let abs_tol_neg = rel_tol.absolute_tolerance(-50.0);
718 /// assert_eq!(*abs_tol_neg.as_raw_scalar_ref(), 5.0);
719 ///
720 /// // 10% of 0 = 0
721 /// let abs_tol_zero = rel_tol.absolute_tolerance(0.0);
722 /// assert_eq!(*abs_tol_zero.as_raw_scalar_ref(), 0.0);
723 /// ```
724 #[inline(always)]
725 pub fn absolute_tolerance(&self, reference: RealType) -> AbsoluteTolerance<RealType> {
726 let abs_tol = reference.abs() * &self.0;
727 AbsoluteTolerance(abs_tol)
728 }
729
730 /// Wraps a [`NonNegativeRealScalar<T>`] as a [`RelativeTolerance<T>`].
731 ///
732 /// This constructor allows explicit conversion from a validated non-negative scalar
733 /// to a tolerance type, providing semantic clarity.
734 ///
735 /// # Examples
736 ///
737 /// ```rust
738 /// use num_valid::{*, scalars::*};
739 /// use try_create::TryNew;
740 ///
741 /// let non_neg = NonNegativeRealScalar::try_new(0.01_f64).unwrap(); // 1%
742 /// let tol = RelativeTolerance::new(non_neg);
743 /// assert_eq!(tol.as_raw_scalar_ref(), &0.01);
744 /// ```
745 pub fn new(value: NonNegativeRealScalar<RealType>) -> Self {
746 RelativeTolerance(value)
747 }
748
749 /// Attempts to create a [`RelativeTolerance`] from a value.
750 ///
751 /// # Errors
752 ///
753 /// Returns [`ErrorsNonNegativeRealScalar::NegativeValue`] if the input is negative.
754 ///
755 /// # Panics (Debug Mode Only)
756 ///
757 /// In debug builds, panics if the input is not finite (NaN or infinity).
758 pub fn try_new(value: RealType) -> Result<Self, ErrorsNonNegativeRealScalar<RealType>> {
759 NonNegativeRealScalar::try_new(value).map(RelativeTolerance)
760 }
761}
762
763impl<RealType: RealScalar> ScalarCore for RelativeTolerance<RealType> {
764 type RawScalarType = RealType::RawScalarType;
765
766 #[inline(always)]
767 fn as_raw_scalar_ref(&self) -> &Self::RawScalarType {
768 self.0.as_raw_scalar_ref()
769 }
770
771 fn into_raw_scalar(self) -> Self::RawScalarType {
772 self.0.into_raw_scalar()
773 }
774}
775
776//------------------------------------------------------------------------------------------------------------
777// PositiveRealScalar
778//------------------------------------------------------------------------------------------------------------
779
780/// Type-safe wrapper for strictly positive real scalar values.
781///
782/// [`PositiveRealScalar<T>`] guarantees that wrapped values are strictly greater than zero.
783/// Zero is **not** valid for this type. For values that can be zero, use [`NonNegativeRealScalar`].
784///
785/// # Mathematical Definition
786///
787/// ```text
788/// PositiveRealScalar<T> = { x ∈ T : x > 0 }
789/// ```
790///
791/// This is the set of positive real numbers `ℝ⁺`, which **excludes** zero.
792///
793/// # Use Cases
794///
795/// - Lengths and distances that cannot be zero
796/// - Positive tolerances
797/// - Scaling factors that must be positive
798/// - Any quantity that is mathematically required to be > 0
799///
800/// # Examples
801///
802/// ## Basic Usage
803///
804/// ```rust
805/// use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
806/// use try_create::TryNew;
807///
808/// // Valid positive values
809/// let length = PositiveRealScalar::try_new(2.5_f64).unwrap();
810/// let tiny = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
811///
812/// // Zero is NOT positive
813/// assert!(matches!(
814/// PositiveRealScalar::try_new(0.0_f64),
815/// Err(ErrorsPositiveRealScalar::ZeroValue { .. })
816/// ));
817///
818/// // Negative values rejected
819/// assert!(matches!(
820/// PositiveRealScalar::try_new(-1.0_f64),
821/// Err(ErrorsPositiveRealScalar::NegativeValue { .. })
822/// ));
823/// ```
824///
825/// ## Difference from [`NonNegativeRealScalar`]
826///
827/// ```rust
828/// use num_valid::scalars::{PositiveRealScalar, NonNegativeRealScalar};
829/// use try_create::TryNew;
830///
831/// // Zero is INVALID for PositiveRealScalar (x > 0)
832/// assert!(PositiveRealScalar::try_new(0.0_f64).is_err());
833///
834/// // Zero is VALID for NonNegativeRealScalar (x ≥ 0)
835/// assert!(NonNegativeRealScalar::try_new(0.0_f64).is_ok());
836/// ```
837///
838/// ## Algebraic and Ordering Operations
839///
840/// [`PositiveRealScalar`] supports:
841/// - **Addition**: Sum of positive values is positive
842/// - **Multiplication**: Product of positive values is positive
843/// - **Integer powers**: Raising positive to any integer exponent yields positive
844/// - **Square root**: Preserves positivity (√x > 0 if x > 0)
845/// - **Multiplicative identity** ([`One`]): For neutral multiplication operations
846/// - **Comparison** ([`Max`], [`Min`]): Ordering and selection helpers
847///
848/// ```rust
849/// use num::One;
850/// use num_valid::{
851/// *,
852/// functions::{Max, Min, Pow},
853/// scalars::PositiveRealScalar,
854/// };
855/// use try_create::{IntoInner, TryNew};
856///
857/// let a = PositiveRealScalar::try_new(2.0_f64).unwrap();
858/// let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
859///
860/// // Add preserves positivity for valid operands.
861/// let sum = a.clone() + b.clone();
862/// assert_eq!(*sum.as_raw_scalar_ref(), 5.0);
863///
864/// // Multiply preserves positivity
865/// let product = a.clone() * b.clone();
866/// assert_eq!(*product.as_raw_scalar_ref(), 6.0);
867///
868/// // Integer powers preserve positivity
869/// let squared = a.clone().pow(2u32);
870/// assert_eq!(*squared.as_raw_scalar_ref(), 4.0);
871///
872/// // Square root preserves positivity
873/// let sqrt = PositiveRealScalar::try_new(4.0_f64).unwrap().sqrt();
874/// assert_eq!(*sqrt.as_raw_scalar_ref(), 2.0);
875///
876/// // One is the multiplicative identity.
877/// let one = PositiveRealScalar::<f64>::one();
878/// let result = a.clone() * one;
879/// assert_eq!(*result.as_raw_scalar_ref(), 2.0);
880///
881/// let x = PositiveRealScalar::try_new(1.0_f64).unwrap();
882/// let y = PositiveRealScalar::try_new(3.0_f64).unwrap();
883/// assert_eq!(*Max::max_by_ref(&x, &y).as_raw_scalar_ref(), 3.0);
884/// assert_eq!(*Min::min_by_value(x, y).as_raw_scalar_ref(), 1.0);
885/// ```
886// `AsRef`/`IntoInner` expose the wrapped `RealType` (still a validated scalar); use
887// `as_raw_scalar_ref()`/`into_raw_scalar()` to unwrap all the way down to the raw primitive.
888#[derive(
889 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, Serialize, Deserialize,
890)]
891#[repr(transparent)]
892#[serde(bound(deserialize = "RealType: for<'a> Deserialize<'a>"))]
893pub struct PositiveRealScalar<RealType: RealScalar>(RealType);
894
895impl<RealType: RealScalar> PositiveRealScalar<RealType> {
896 /// Computes the square root of this positive scalar.
897 ///
898 /// Since the input is guaranteed to be positive, the result is always defined
899 /// and remains positive.
900 ///
901 /// # Examples
902 ///
903 /// ```rust
904 /// use num_valid::{*, scalars::PositiveRealScalar};
905 /// use try_create::TryNew;
906 ///
907 /// let value = PositiveRealScalar::try_new(4.0_f64).unwrap();
908 /// let sqrt = value.sqrt();
909 /// assert_eq!(sqrt.as_raw_scalar_ref(), &2.0);
910 /// ```
911 pub fn sqrt(self) -> Self {
912 PositiveRealScalar(self.0.sqrt())
913 }
914
915 #[inline(always)]
916 pub fn new(value: RealType) -> Self {
917 #[cfg(debug_assertions)]
918 {
919 Self::try_new(value)
920 .expect("Error in the creation of the positive real scalar in debug mode")
921 }
922 #[cfg(not(debug_assertions))]
923 {
924 Self(value)
925 }
926 }
927}
928
929impl<RealType: RealScalar> ScalarCore for PositiveRealScalar<RealType> {
930 type RawScalarType = RealType::RawScalarType;
931
932 #[inline(always)]
933 fn as_raw_scalar_ref(&self) -> &Self::RawScalarType {
934 self.0.as_raw_scalar_ref()
935 }
936
937 fn into_raw_scalar(self) -> Self::RawScalarType {
938 self.0.into_raw_scalar()
939 }
940}
941
942impl<RealType: RealScalar> TryNew for PositiveRealScalar<RealType> {
943 type Error = ErrorsPositiveRealScalar<RealType>;
944
945 /// Attempts to create a [`PositiveRealScalar`] from a value.
946 ///
947 /// # Errors
948 ///
949 /// - [`ErrorsPositiveRealScalar::NegativeValue`]: If the input is negative (< 0).
950 /// - [`ErrorsPositiveRealScalar::ZeroValue`]: If the input is zero.
951 ///
952 /// # Panics (Debug Mode Only)
953 ///
954 /// In debug builds, panics if the input is not finite (NaN or infinity).
955 ///
956 /// # Examples
957 ///
958 /// ```rust
959 /// use num_valid::scalars::{PositiveRealScalar, ErrorsPositiveRealScalar};
960 /// use try_create::TryNew;
961 ///
962 /// // Positive value succeeds
963 /// assert!(PositiveRealScalar::try_new(1.0_f64).is_ok());
964 ///
965 /// // Zero fails
966 /// assert!(matches!(
967 /// PositiveRealScalar::try_new(0.0_f64),
968 /// Err(ErrorsPositiveRealScalar::ZeroValue { .. })
969 /// ));
970 ///
971 /// // Negative fails
972 /// assert!(matches!(
973 /// PositiveRealScalar::try_new(-1.0_f64),
974 /// Err(ErrorsPositiveRealScalar::NegativeValue { value: v, .. }) if v == -1.0
975 /// ));
976 /// ```
977 fn try_new(value: RealType) -> Result<Self, Self::Error> {
978 debug_assert!(value.is_finite(), "The input value {value} is not finite!");
979 if value.kernel_is_sign_negative() {
980 Err(ErrorsPositiveRealScalar::NegativeValue {
981 value,
982 backtrace: capture_backtrace(),
983 })
984 } else if value.is_zero() {
985 Err(ErrorsPositiveRealScalar::ZeroValue {
986 backtrace: capture_backtrace(),
987 })
988 } else {
989 Ok(Self(value))
990 }
991 }
992}
993
994/// Implements additive composition for [`PositiveRealScalar`].
995///
996/// Since both operands are guaranteed to be positive, their sum is also positive.
997impl<RealType: RealScalar> Add for PositiveRealScalar<RealType> {
998 type Output = Self;
999
1000 fn add(self, other: Self) -> Self {
1001 PositiveRealScalar(self.0 + other.0)
1002 }
1003}
1004
1005/// Implements multiplicative composition for [`PositiveRealScalar`].
1006///
1007/// Since both operands are guaranteed to be positive, their product is also positive.
1008impl<RealType: RealScalar> Mul for PositiveRealScalar<RealType> {
1009 type Output = Self;
1010
1011 fn mul(self, other: Self) -> Self {
1012 PositiveRealScalar(self.0 * other.0)
1013 }
1014}
1015
1016/// Implements reference-based addition for [`PositiveRealScalar`].
1017///
1018/// This allows adding a reference to a positive scalar without consuming it.
1019/// Since both operands are guaranteed to be positive, their sum is also positive.
1020impl<'a, RealType: RealScalar> Add<&'a PositiveRealScalar<RealType>>
1021 for PositiveRealScalar<RealType>
1022{
1023 type Output = PositiveRealScalar<RealType>;
1024
1025 fn add(self, other: &'a PositiveRealScalar<RealType>) -> PositiveRealScalar<RealType> {
1026 PositiveRealScalar(self.0 + other.0.clone())
1027 }
1028}
1029
1030/// Implements reference-based multiplication for [`PositiveRealScalar`].
1031///
1032/// This allows multiplying a positive scalar by a reference to another positive scalar
1033/// without consuming it. Since both operands are guaranteed to be positive, their product is also positive.
1034impl<'a, RealType: RealScalar> Mul<&'a PositiveRealScalar<RealType>>
1035 for PositiveRealScalar<RealType>
1036{
1037 type Output = PositiveRealScalar<RealType>;
1038
1039 fn mul(self, other: &'a PositiveRealScalar<RealType>) -> PositiveRealScalar<RealType> {
1040 PositiveRealScalar(self.0 * other.0.clone())
1041 }
1042}
1043
1044/// Implements in-place reference-based addition for [`PositiveRealScalar`].
1045///
1046/// This allows adding a reference to a positive scalar to `self` without consuming `self`.
1047impl<'a, RealType: RealScalar> AddAssign<&'a PositiveRealScalar<RealType>>
1048 for PositiveRealScalar<RealType>
1049{
1050 fn add_assign(&mut self, other: &'a PositiveRealScalar<RealType>) {
1051 self.0 += &other.0;
1052 }
1053}
1054
1055/// Implements in-place addition for [`PositiveRealScalar`] by value.
1056///
1057/// This allows adding a positive scalar value to `self`.
1058impl<RealType: RealScalar> AddAssign<PositiveRealScalar<RealType>>
1059 for PositiveRealScalar<RealType>
1060{
1061 fn add_assign(&mut self, other: PositiveRealScalar<RealType>) {
1062 self.0 += other.0;
1063 }
1064}
1065
1066/// Implements in-place reference-based multiplication for [`PositiveRealScalar`].
1067///
1068/// This allows multiplying `self` by a reference to another positive scalar without consuming `self`.
1069impl<'a, RealType: RealScalar> MulAssign<&'a PositiveRealScalar<RealType>>
1070 for PositiveRealScalar<RealType>
1071{
1072 fn mul_assign(&mut self, other: &'a PositiveRealScalar<RealType>) {
1073 self.0 *= &other.0;
1074 }
1075}
1076
1077/// Implements in-place multiplication for [`PositiveRealScalar`] by value.
1078///
1079/// This allows multiplying `self` by a positive scalar value.
1080impl<RealType: RealScalar> MulAssign<PositiveRealScalar<RealType>>
1081 for PositiveRealScalar<RealType>
1082{
1083 fn mul_assign(&mut self, other: PositiveRealScalar<RealType>) {
1084 self.0 *= other.0;
1085 }
1086}
1087
1088impl<RealType: RealScalar> One for PositiveRealScalar<RealType> {
1089 fn one() -> Self {
1090 PositiveRealScalar(RealType::one())
1091 }
1092}
1093
1094#[duplicate_item(
1095 exponent_type;
1096 [u8];
1097 [i8];
1098 [u16];
1099 [i16];
1100 [u32];
1101 [i32];
1102 [u64];
1103 [i64];
1104 [u128];
1105 [i128];
1106 [usize];
1107 [isize];
1108)]
1109impl<RealType: RealScalar> Pow<exponent_type> for PositiveRealScalar<RealType> {
1110 type Error = PowIntExponentErrors<<RealType as IntoInner>::InnerType, exponent_type>;
1111
1112 fn try_pow(self, exponent: exponent_type) -> Result<Self, Self::Error> {
1113 self.0.try_pow(exponent).map(PositiveRealScalar)
1114 }
1115
1116 #[inline(always)]
1117 /// Raises the number `self` to the power `exponent`.
1118 fn pow(self, exponent: exponent_type) -> Self {
1119 #[cfg(debug_assertions)]
1120 {
1121 self.try_pow(exponent).unwrap()
1122 }
1123 #[cfg(not(debug_assertions))]
1124 {
1125 PositiveRealScalar(self.0.pow(exponent))
1126 }
1127 }
1128}
1129
1130/// Implements [`Max`] for [`PositiveRealScalar`].
1131///
1132/// Maximum selection is delegated to the default trait behavior based on [`PartialOrd`].
1133impl<RealType: RealScalar> Max for PositiveRealScalar<RealType> {}
1134/// Implements [`Min`] for [`PositiveRealScalar`].
1135///
1136/// Minimum selection is delegated to the default trait behavior based on [`PartialOrd`].
1137impl<RealType: RealScalar> Min for PositiveRealScalar<RealType> {}
1138
1139/// Implements Neumaier compensated summation for [`PositiveRealScalar`].
1140///
1141/// This implementation delegates to the underlying real scalar type's Neumaier
1142/// compensated sum operation, preserving numerical stability for sums of positive values.
1143impl<RealType: RealScalar> NeumaierAddable for PositiveRealScalar<RealType> {
1144 #[inline(always)]
1145 fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
1146 neumaier_sum_and_compensation_real(value.into_inner(), &mut sum.0, &mut compensation.0)
1147 }
1148}
1149
1150//------------------------------------------------------------------------------------------------------------
1151// NonNegativeRealScalar
1152//------------------------------------------------------------------------------------------------------------
1153
1154/// Type-safe wrapper for non-negative real scalar values.
1155///
1156/// [`NonNegativeRealScalar<T>`] guarantees that wrapped values are greater than or equal to zero.
1157/// Zero **is** valid for this type. For values that must be strictly positive, use [`PositiveRealScalar`].
1158///
1159/// # Mathematical Definition
1160///
1161/// ```text
1162/// NonNegativeRealScalar<T> = { x ∈ T : x ≥ 0 }
1163/// ```
1164///
1165/// This is the set of non-negative real numbers `ℝ₀⁺`, which **includes** zero.
1166///
1167/// # Use Cases
1168///
1169/// - Distances (can be zero when comparing a value to itself)
1170/// - Absolute values
1171/// - Magnitudes
1172/// - Any quantity that is mathematically required to be ≥ 0
1173///
1174/// # Examples
1175///
1176/// ## Basic Usage
1177///
1178/// ```rust
1179/// use num_valid::{*, scalars::*};
1180/// use try_create::TryNew;
1181///
1182/// // Zero is valid
1183/// let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
1184/// assert_eq!(zero.as_raw_scalar_ref(), &0.0);
1185///
1186/// // Positive values are valid
1187/// let positive = NonNegativeRealScalar::try_new(5.0_f64).unwrap();
1188///
1189/// // Negative values are rejected
1190/// assert!(matches!(
1191/// NonNegativeRealScalar::try_new(-1.0_f64),
1192/// Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
1193/// ));
1194/// ```
1195///
1196/// ## Use Case: Computing Distances
1197///
1198/// ```rust
1199/// use num_valid::{*, scalars::NonNegativeRealScalar};
1200/// use try_create::TryNew;
1201///
1202/// fn distance(a: f64, b: f64) -> NonNegativeRealScalar<f64> {
1203/// NonNegativeRealScalar::try_new((a - b).abs()).unwrap()
1204/// }
1205///
1206/// // Different points
1207/// let d1 = distance(0.0, 5.0);
1208/// assert_eq!(*d1.as_raw_scalar_ref(), 5.0);
1209///
1210/// // Same point (zero distance is valid!)
1211/// let d2 = distance(3.0, 3.0);
1212/// assert_eq!(d2.as_raw_scalar_ref(), &0.0);
1213/// ```
1214///
1215/// ## Algebraic and Ordering Operations
1216///
1217/// [`NonNegativeRealScalar`] supports:
1218/// - **Addition**: Sum of non-negative values is non-negative
1219/// - **Multiplication**: Product of non-negative values is non-negative
1220/// - **Integer powers**: Raising non-negative to any integer exponent yields non-negative
1221/// - **Additive identity** ([`Zero`]): For neutral addition operations
1222/// - **Multiplicative identity** ([`One`]): For neutral multiplication operations
1223/// - **Comparison** ([`Max`], [`Min`]): Ordering and selection helpers
1224///
1225/// ```rust
1226/// use num::{One, Zero};
1227/// use num_valid::{
1228/// *,
1229/// functions::{Max, Min, Pow},
1230/// scalars::NonNegativeRealScalar,
1231/// };
1232/// use try_create::TryNew;
1233///
1234/// let a = NonNegativeRealScalar::try_new(1.25_f64).unwrap();
1235/// let b = NonNegativeRealScalar::try_new(2.75_f64).unwrap();
1236///
1237/// // Add preserves non-negativity for valid operands.
1238/// let sum = a.clone() + &b;
1239/// assert_eq!(*sum.as_raw_scalar_ref(), 4.0);
1240///
1241/// // Multiply preserves non-negativity
1242/// let product = a * b;
1243/// assert!(*product.as_raw_scalar_ref() > 0.0);
1244///
1245/// // Integer powers preserve non-negativity
1246/// let base = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
1247/// let squared = base.clone().pow(2u32);
1248/// assert_eq!(*squared.as_raw_scalar_ref(), 4.0);
1249///
1250/// // Zero is the additive identity.
1251/// let z = NonNegativeRealScalar::<f64>::zero();
1252/// assert!(z.is_zero());
1253/// assert_eq!(*(sum + z).as_raw_scalar_ref(), 4.0);
1254///
1255/// // One is the multiplicative identity.
1256/// let one = NonNegativeRealScalar::<f64>::one();
1257/// let result = base.clone() * one;
1258/// assert_eq!(*result.as_raw_scalar_ref(), 2.0);
1259///
1260/// let x = NonNegativeRealScalar::try_new(1.0_f64).unwrap();
1261/// let y = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
1262/// assert_eq!(*Max::max_by_ref(&x, &y).as_raw_scalar_ref(), 3.0);
1263/// assert_eq!(*Min::min_by_value(x, y).as_raw_scalar_ref(), 1.0);
1264/// ```
1265// `AsRef`/`IntoInner` expose the wrapped `RealType` (still a validated scalar); use
1266// `as_raw_scalar_ref()`/`into_raw_scalar()` to unwrap all the way down to the raw primitive.
1267#[derive(
1268 Debug, Clone, PartialEq, PartialOrd, AsRef, IntoInner, Display, Serialize, Deserialize,
1269)]
1270#[repr(transparent)]
1271#[serde(bound(deserialize = "RealType: for<'a> Deserialize<'a>"))]
1272pub struct NonNegativeRealScalar<RealType: RealScalar>(RealType);
1273
1274impl<RealType: RealScalar> NonNegativeRealScalar<RealType> {
1275 /// Computes the square root of this non-negative scalar.
1276 ///
1277 /// Since the input is guaranteed to be non-negative, the result is always defined
1278 /// and remains non-negative.
1279 ///
1280 /// # Examples
1281 ///
1282 /// ```rust
1283 /// use num_valid::{*, scalars::NonNegativeRealScalar};
1284 /// use try_create::TryNew;
1285 ///
1286 /// let value = NonNegativeRealScalar::try_new(4.0_f64).unwrap();
1287 /// let sqrt = value.sqrt();
1288 /// assert_eq!(sqrt.as_raw_scalar_ref(), &2.0);
1289 /// ```
1290 pub fn sqrt(self) -> Self {
1291 NonNegativeRealScalar(self.0.sqrt())
1292 }
1293
1294 #[inline(always)]
1295 pub fn new(value: RealType) -> Self {
1296 #[cfg(debug_assertions)]
1297 {
1298 Self::try_new(value)
1299 .expect("Error in the creation of the non-negative real scalar in debug mode")
1300 }
1301 #[cfg(not(debug_assertions))]
1302 {
1303 Self(value)
1304 }
1305 }
1306}
1307
1308impl<RealType: RealScalar> ScalarCore for NonNegativeRealScalar<RealType> {
1309 type RawScalarType = RealType::RawScalarType;
1310
1311 #[inline(always)]
1312 fn as_raw_scalar_ref(&self) -> &Self::RawScalarType {
1313 self.0.as_raw_scalar_ref()
1314 }
1315
1316 fn into_raw_scalar(self) -> Self::RawScalarType {
1317 self.0.into_raw_scalar()
1318 }
1319}
1320
1321impl<RealType: RealScalar> TryNew for NonNegativeRealScalar<RealType> {
1322 type Error = ErrorsNonNegativeRealScalar<RealType>;
1323
1324 /// Attempts to create a [`NonNegativeRealScalar`] from a value.
1325 ///
1326 /// # Errors
1327 ///
1328 /// Returns [`ErrorsNonNegativeRealScalar::NegativeValue`] if the input is negative.
1329 ///
1330 /// # Panics (Debug Mode Only)
1331 ///
1332 /// In debug builds, panics if the input is not finite (NaN or infinity).
1333 ///
1334 /// # Examples
1335 ///
1336 /// ```rust
1337 /// use num_valid::scalars::{NonNegativeRealScalar, ErrorsNonNegativeRealScalar};
1338 /// use try_create::TryNew;
1339 ///
1340 /// // Zero is valid
1341 /// assert!(NonNegativeRealScalar::try_new(0.0_f64).is_ok());
1342 ///
1343 /// // Positive is valid
1344 /// assert!(NonNegativeRealScalar::try_new(1.0_f64).is_ok());
1345 ///
1346 /// // Negative is rejected
1347 /// assert!(matches!(
1348 /// NonNegativeRealScalar::try_new(-1.0_f64),
1349 /// Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
1350 /// ));
1351 /// ```
1352 fn try_new(value: RealType) -> Result<Self, Self::Error> {
1353 debug_assert!(value.is_finite(), "The input value {value} is not finite!");
1354 if value.kernel_is_sign_negative() {
1355 Err(ErrorsNonNegativeRealScalar::NegativeValue {
1356 value,
1357 backtrace: capture_backtrace(),
1358 })
1359 } else {
1360 Ok(Self(value))
1361 }
1362 }
1363}
1364
1365/// Implements additive composition for [`NonNegativeRealScalar`].
1366///
1367/// Since both operands are guaranteed to be non-negative, their sum is also non-negative.
1368impl<RealType: RealScalar> Add for NonNegativeRealScalar<RealType> {
1369 type Output = Self;
1370
1371 fn add(self, other: Self) -> Self {
1372 NonNegativeRealScalar(self.0 + other.0)
1373 }
1374}
1375
1376/// Implements multiplicative composition for [`NonNegativeRealScalar`].
1377///
1378/// Since both operands are guaranteed to be non-negative, their product is also non-negative.
1379impl<RealType: RealScalar> Mul for NonNegativeRealScalar<RealType> {
1380 type Output = Self;
1381
1382 fn mul(self, other: Self) -> Self {
1383 NonNegativeRealScalar(self.0 * other.0)
1384 }
1385}
1386
1387/// Implements reference-based addition for [`NonNegativeRealScalar`].
1388///
1389/// This allows adding a reference to a non-negative scalar without consuming it.
1390/// Since both operands are guaranteed to be non-negative, their sum is also non-negative.
1391impl<'a, RealType: RealScalar> Add<&'a NonNegativeRealScalar<RealType>>
1392 for NonNegativeRealScalar<RealType>
1393{
1394 type Output = NonNegativeRealScalar<RealType>;
1395
1396 fn add(self, other: &'a NonNegativeRealScalar<RealType>) -> NonNegativeRealScalar<RealType> {
1397 NonNegativeRealScalar(self.0 + other.0.clone())
1398 }
1399}
1400
1401/// Implements reference-based multiplication for [`NonNegativeRealScalar`].
1402///
1403/// This allows multiplying a non-negative scalar by a reference to another non-negative scalar
1404/// without consuming it. Since both operands are guaranteed to be non-negative, their product is also non-negative.
1405impl<'a, RealType: RealScalar> Mul<&'a NonNegativeRealScalar<RealType>>
1406 for NonNegativeRealScalar<RealType>
1407{
1408 type Output = NonNegativeRealScalar<RealType>;
1409
1410 fn mul(self, other: &'a NonNegativeRealScalar<RealType>) -> NonNegativeRealScalar<RealType> {
1411 NonNegativeRealScalar(self.0 * other.0.clone())
1412 }
1413}
1414
1415/// Implements in-place reference-based addition for [`NonNegativeRealScalar`].
1416///
1417/// This allows adding a reference to a non-negative scalar to `self` without consuming `self`.
1418impl<'a, RealType: RealScalar> AddAssign<&'a NonNegativeRealScalar<RealType>>
1419 for NonNegativeRealScalar<RealType>
1420{
1421 fn add_assign(&mut self, other: &'a NonNegativeRealScalar<RealType>) {
1422 self.0 += &other.0;
1423 }
1424}
1425
1426/// Implements in-place addition for [`NonNegativeRealScalar`] by value.
1427///
1428/// This allows adding a non-negative scalar value to `self`.
1429impl<RealType: RealScalar> AddAssign<NonNegativeRealScalar<RealType>>
1430 for NonNegativeRealScalar<RealType>
1431{
1432 fn add_assign(&mut self, other: NonNegativeRealScalar<RealType>) {
1433 self.0 += other.0;
1434 }
1435}
1436
1437/// Implements in-place reference-based multiplication for [`NonNegativeRealScalar`].
1438///
1439/// This allows multiplying `self` by a reference to another non-negative scalar without consuming `self`.
1440impl<'a, RealType: RealScalar> MulAssign<&'a NonNegativeRealScalar<RealType>>
1441 for NonNegativeRealScalar<RealType>
1442{
1443 fn mul_assign(&mut self, other: &'a NonNegativeRealScalar<RealType>) {
1444 self.0 *= &other.0;
1445 }
1446}
1447
1448/// Implements in-place multiplication for [`NonNegativeRealScalar`] by value.
1449///
1450/// This allows multiplying `self` by a non-negative scalar value.
1451impl<RealType: RealScalar> MulAssign<NonNegativeRealScalar<RealType>>
1452 for NonNegativeRealScalar<RealType>
1453{
1454 fn mul_assign(&mut self, other: NonNegativeRealScalar<RealType>) {
1455 self.0 *= other.0;
1456 }
1457}
1458
1459/// Implements [`Zero`] for [`NonNegativeRealScalar`].
1460///
1461/// The zero value is always valid, and acts as the additive identity.
1462impl<RealType: RealScalar> Zero for NonNegativeRealScalar<RealType> {
1463 fn is_zero(&self) -> bool {
1464 self.0.is_zero()
1465 }
1466
1467 fn zero() -> Self {
1468 NonNegativeRealScalar(RealType::zero())
1469 }
1470}
1471
1472impl<RealType: RealScalar> One for NonNegativeRealScalar<RealType> {
1473 fn one() -> Self {
1474 NonNegativeRealScalar(RealType::one())
1475 }
1476}
1477
1478#[duplicate_item(
1479 exponent_type;
1480 [u8];
1481 [i8];
1482 [u16];
1483 [i16];
1484 [u32];
1485 [i32];
1486 [u64];
1487 [i64];
1488 [u128];
1489 [i128];
1490 [usize];
1491 [isize];
1492)]
1493impl<RealType: RealScalar> Pow<exponent_type> for NonNegativeRealScalar<RealType> {
1494 type Error = PowIntExponentErrors<<RealType as IntoInner>::InnerType, exponent_type>;
1495
1496 fn try_pow(self, exponent: exponent_type) -> Result<Self, Self::Error> {
1497 self.0.try_pow(exponent).map(NonNegativeRealScalar)
1498 }
1499
1500 #[inline(always)]
1501 /// Raises the number `self` to the power `exponent`.
1502 fn pow(self, exponent: exponent_type) -> Self {
1503 #[cfg(debug_assertions)]
1504 {
1505 self.try_pow(exponent).unwrap()
1506 }
1507 #[cfg(not(debug_assertions))]
1508 {
1509 NonNegativeRealScalar(self.0.pow(exponent))
1510 }
1511 }
1512}
1513
1514/// Implements [`Max`] for [`NonNegativeRealScalar`].
1515///
1516/// Maximum selection is delegated to the default trait behavior based on [`PartialOrd`].
1517impl<RealType: RealScalar> Max for NonNegativeRealScalar<RealType> {}
1518/// Implements [`Min`] for [`NonNegativeRealScalar`].
1519///
1520/// Minimum selection is delegated to the default trait behavior based on [`PartialOrd`].
1521impl<RealType: RealScalar> Min for NonNegativeRealScalar<RealType> {}
1522
1523/// Implements Neumaier compensated summation for [`NonNegativeRealScalar`].
1524///
1525/// This implementation delegates to the underlying real scalar type's Neumaier
1526/// compensated sum operation, preserving numerical stability for sums of non-negative values.
1527impl<RealType: RealScalar> NeumaierAddable for NonNegativeRealScalar<RealType> {
1528 #[inline(always)]
1529 fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
1530 neumaier_sum_and_compensation_real(value.into_inner(), &mut sum.0, &mut compensation.0)
1531 }
1532}
1533
1534//------------------------------------------------------------------------------------------------------------
1535// Conversions
1536//------------------------------------------------------------------------------------------------------------
1537
1538/// Converts a [`PositiveRealScalar<T>`] to a [`NonNegativeRealScalar<T>`].
1539///
1540/// This conversion is mathematically valid because every positive number (x > 0) is
1541/// also non-negative (x ≥ 0). The conversion is infallible and zero-cost.
1542///
1543/// # Examples
1544///
1545/// ```rust
1546/// use num_valid::{*, scalars::*};
1547/// use try_create::TryNew;
1548///
1549/// let positive = PositiveRealScalar::try_new(5.0_f64).unwrap();
1550/// let non_negative: NonNegativeRealScalar<f64> = positive.into();
1551///
1552/// assert_eq!(non_negative.as_raw_scalar_ref(), &5.0);
1553/// ```
1554impl<RealType: RealScalar> From<PositiveRealScalar<RealType>> for NonNegativeRealScalar<RealType> {
1555 fn from(positive: PositiveRealScalar<RealType>) -> Self {
1556 NonNegativeRealScalar(positive.into_inner())
1557 }
1558}
1559
1560//------------------------------------------------------------------------------------------------------------
1561// Cross-type arithmetic operations
1562//------------------------------------------------------------------------------------------------------------
1563
1564/// Adds a [`NonNegativeRealScalar`] to a [`PositiveRealScalar`], yielding a [`PositiveRealScalar`].
1565///
1566/// Since `self` is strictly positive (x > 0) and `other` is non-negative (y ≥ 0),
1567/// their sum is strictly positive: x + y > 0.
1568///
1569/// # Examples
1570///
1571/// ```rust
1572/// use num_valid::{*, scalars::*};
1573/// use try_create::TryNew;
1574///
1575/// let pos = PositiveRealScalar::try_new(2.0_f64).unwrap();
1576/// let non_neg = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
1577///
1578/// let result: PositiveRealScalar<f64> = pos + non_neg;
1579/// assert_eq!(result.as_raw_scalar_ref(), &5.0);
1580/// ```
1581impl<RealType: RealScalar> Add<NonNegativeRealScalar<RealType>> for PositiveRealScalar<RealType> {
1582 type Output = PositiveRealScalar<RealType>;
1583
1584 fn add(self, other: NonNegativeRealScalar<RealType>) -> PositiveRealScalar<RealType> {
1585 PositiveRealScalar(self.0 + other.0)
1586 }
1587}
1588
1589/// Adds a [`NonNegativeRealScalar`] by reference to a [`PositiveRealScalar`], yielding a [`PositiveRealScalar`].
1590///
1591/// Since `self` is strictly positive (x > 0) and `other` is non-negative (y ≥ 0),
1592/// their sum is strictly positive: x + y > 0.
1593impl<'a, RealType: RealScalar> Add<&'a NonNegativeRealScalar<RealType>>
1594 for PositiveRealScalar<RealType>
1595{
1596 type Output = PositiveRealScalar<RealType>;
1597
1598 fn add(self, other: &'a NonNegativeRealScalar<RealType>) -> PositiveRealScalar<RealType> {
1599 PositiveRealScalar(self.0 + &other.0)
1600 }
1601}
1602
1603/// Adds a [`PositiveRealScalar`] to a [`NonNegativeRealScalar`], yielding a [`PositiveRealScalar`].
1604///
1605/// Since `other` is strictly positive (x > 0) and `self` is non-negative (y ≥ 0),
1606/// their sum is strictly positive: x + y > 0.
1607///
1608/// # Examples
1609///
1610/// ```rust
1611/// use num_valid::{*, scalars::*};
1612/// use try_create::TryNew;
1613///
1614/// let non_neg = NonNegativeRealScalar::try_new(0.0_f64).unwrap(); // zero!
1615/// let pos = PositiveRealScalar::try_new(5.0_f64).unwrap();
1616///
1617/// let result: PositiveRealScalar<f64> = non_neg + pos;
1618/// assert_eq!(result.as_raw_scalar_ref(), &5.0);
1619/// ```
1620impl<RealType: RealScalar> Add<PositiveRealScalar<RealType>> for NonNegativeRealScalar<RealType> {
1621 type Output = PositiveRealScalar<RealType>;
1622
1623 fn add(self, other: PositiveRealScalar<RealType>) -> PositiveRealScalar<RealType> {
1624 PositiveRealScalar(self.0 + other.0)
1625 }
1626}
1627
1628/// Adds a [`PositiveRealScalar`] by reference to a [`NonNegativeRealScalar`], yielding a [`PositiveRealScalar`].
1629///
1630/// Since `other` is strictly positive (x > 0) and `self` is non-negative (y ≥ 0),
1631/// their sum is strictly positive: x + y > 0.
1632impl<'a, RealType: RealScalar> Add<&'a PositiveRealScalar<RealType>>
1633 for NonNegativeRealScalar<RealType>
1634{
1635 type Output = PositiveRealScalar<RealType>;
1636
1637 fn add(self, other: &'a PositiveRealScalar<RealType>) -> PositiveRealScalar<RealType> {
1638 PositiveRealScalar(self.0 + &other.0)
1639 }
1640}
1641
1642//-----------------------------------------------------------------------------------
1643
1644/// Error returned when constructing a [`HashableScalar`] from an invalid value.
1645///
1646/// Hash-based collections require keys to implement [`Eq`] and [`Hash`]. Floating-point
1647/// NaN values do not satisfy the reflexive equality requirement because `NaN != NaN`,
1648/// so they cannot safely be used as hash keys. This error preserves that invariant by
1649/// rejecting non-finite values during [`HashableScalar::try_new`].
1650///
1651/// # Examples
1652///
1653/// ```rust
1654/// use num_valid::scalars::{ErrorsHashableScalar, HashableScalar};
1655///
1656/// let result = HashableScalar::try_new(f64::NAN);
1657/// assert!(matches!(
1658/// result,
1659/// Err(ErrorsHashableScalar::NonFiniteValue { .. })
1660/// ));
1661/// ```
1662///
1663/// # See Also
1664///
1665/// - [`HashableScalar`] for the validated hash-key wrapper.
1666/// - [`HashableScalar::try_new`] for fallible construction.
1667#[derive(Debug, Error)]
1668pub enum ErrorsHashableScalar {
1669 /// The supplied scalar is NaN or infinite.
1670 ///
1671 /// Non-finite values cannot satisfy the equality contract required by hash-based
1672 /// collections, so [`HashableScalar::try_new`] rejects them.
1673 #[error("HashableScalar can only be constructed from finite scalar values")]
1674 NonFiniteValue {
1675 /// Stack backtrace captured at the invalid construction site.
1676 backtrace: Backtrace,
1677 },
1678}
1679
1680/// Wrapper type that makes floating-point scalars hashable for use in hash-based collections.
1681///
1682/// # Purpose
1683///
1684/// Real or complex floating-point types (`f32`, `f64`, `Complex<f32>`, `Complex<f64>` and validated wrappers like
1685/// [`RealNative64StrictFinite`](crate::RealNative64StrictFinite),
1686/// [`ComplexNative64StrictFinite`](crate::ComplexNative64StrictFinite), `RealRugStrictFinite<PRECISION>`,
1687/// `ComplexRugStrictFinite<PRECISION>`, etc.) are not hashable by default
1688/// in Rust because of the presence of `NaN` values, which violate the requirements for hashing. Specifically, IEEE 754 defines that `NaN != NaN`, which means that two identical `NaN` values would not be considered equal, leading to inconsistent hashing behavior.
1689/// do not implement [`Hash`] by default because IEEE 754 defines `NaN != NaN`, violating the
1690/// requirement that `a == b` implies `hash(a) == hash(b)`. This type enforces that only
1691/// **finite** floating-point values can be hashed, making them safe for use in:
1692///
1693/// - [`HashMap`](std::collections::HashMap) and [`HashSet`](std::collections::HashSet)
1694/// - [`DashMap`](https://docs.rs/dashmap) for concurrent caching
1695/// - Any algorithm requiring consistent hashing of numerical values
1696///
1697/// # Invariants
1698///
1699/// - **Only finite values allowed**: [`HashableScalar::try_new()`] rejects values
1700/// that are not finite (not `NaN` or `±∞`).
1701/// - **Hash consistency**: Values are hashed via their raw bit representation
1702/// ([`RawScalarTrait::compute_hash_raw_scalar`]), ensuring bitwise-identical values produce identical hashes.
1703/// - **Equality semantics**: Implements [`Eq`] by delegating to the underlying scalar's
1704/// [`PartialEq`], which is safe because we exclude `NaN`.
1705///
1706/// # Type Parameters
1707///
1708/// - `ScalarType`: Any real or complex floating-point type implementing [`FpScalar`] (e.g., `f64`, `Complex<f64>`,
1709/// [`RealNative64StrictFinite`](crate::RealNative64StrictFinite),
1710/// [`ComplexNative64StrictFinite`](crate::ComplexNative64StrictFinite),
1711/// `RealRugStrictFinite<PRECISION>`, `ComplexRugStrictFinite<PRECISION>`, etc.).
1712///
1713/// # Examples
1714///
1715/// ## Basic Usage with `f64` and `Complex<f64>`
1716///
1717/// ```rust
1718/// use num_valid::scalars::HashableScalar;
1719/// use num::Complex;
1720/// use std::collections::HashSet;
1721///
1722/// let x = HashableScalar::new(3.14);
1723/// let y = HashableScalar::new(2.71);
1724/// let z = HashableScalar::new(3.14);
1725///
1726/// let mut set_real = HashSet::new();
1727/// set_real.insert(x);
1728/// set_real.insert(y);
1729/// set_real.insert(z); // Duplicate, won't be added
1730///
1731/// assert_eq!(set_real.len(), 2);
1732///
1733/// let a = HashableScalar::new(Complex::new(3.14, 2.0));
1734/// let b = HashableScalar::new(Complex::new(2.71, 1.0));
1735/// let c = HashableScalar::new(Complex::new(3.14, 2.0));
1736///
1737/// let mut set_complex = HashSet::new();
1738/// set_complex.insert(a);
1739/// set_complex.insert(b);
1740/// set_complex.insert(c); // Duplicate, won't be added
1741///
1742/// assert_eq!(set_complex.len(), 2);
1743/// ```
1744///
1745/// ## Use in Caching Structures
1746///
1747/// ```rust
1748/// use num_valid::scalars::HashableScalar;
1749/// use std::collections::HashMap;
1750///
1751/// // Cache computed values indexed by floating-point parameters
1752/// let mut cache: HashMap<HashableScalar<f64>, String> = HashMap::new();
1753///
1754/// let key = HashableScalar::new(1.5);
1755/// cache.insert(key, "result_for_1.5".to_string());
1756///
1757/// let retrieved = cache.get(&HashableScalar::new(1.5));
1758/// assert_eq!(retrieved, Some(&"result_for_1.5".to_string()));
1759/// ```
1760///
1761/// ## With Validated Scalar Types
1762///
1763/// The wrapper works with any type implementing [`FpScalar`], including validated types
1764/// from the `num_valid` crate:
1765///
1766/// ```rust
1767/// use num_valid::scalars::HashableScalar;
1768/// use std::collections::HashSet;
1769///
1770/// // Works with any FpScalar type
1771/// let mut set = HashSet::new();
1772/// set.insert(HashableScalar::new(1.0_f64));
1773/// set.insert(HashableScalar::new(2.0_f64));
1774/// set.insert(HashableScalar::new(1.0_f64)); // Duplicate
1775///
1776/// assert_eq!(set.len(), 2);
1777/// ```
1778///
1779/// # Invalid Values
1780///
1781/// Fallible construction rejects non-finite values:
1782///
1783/// ```rust
1784/// use num_valid::scalars::HashableScalar;
1785///
1786/// let invalid = HashableScalar::try_new(f64::NAN);
1787/// assert!(invalid.is_err());
1788/// ```
1789///
1790/// # Implementation Notes
1791///
1792/// - **Serialization**: Supports `serde` serialization/deserialization with bounds to ensure
1793/// the wrapped type can be serialized.
1794/// - **Extraction**: Implements [`IntoInner`] for consuming the wrapper and extracting the
1795/// underlying scalar via `.into_inner()`.
1796/// - **Zero-cost abstraction**: The wrapper is `#[repr(transparent)]` equivalent (single field),
1797/// so there is no runtime overhead.
1798///
1799/// # See Also
1800///
1801/// - [`FpScalar`] for the trait bounds on wrapped types
1802/// - [`RawScalarTrait::compute_hash_raw_scalar`] for the hashing mechanism
1803/// - Caching examples in `numerics-core/src/math/chebyshev/cache.rs`
1804#[derive(Debug, Clone, PartialEq, PartialOrd, AsRef, Serialize, Deserialize)]
1805#[serde(bound(deserialize = "ScalarType: for<'a> Deserialize<'a>"))]
1806pub struct HashableScalar<ScalarType: FpScalar> {
1807 value: ScalarType,
1808}
1809
1810impl<ScalarType: FpScalar> Eq for HashableScalar<ScalarType> {}
1811
1812impl<ScalarType: FpScalar> HashableScalar<ScalarType> {
1813 /// Creates a new hashable scalar from a finite value.
1814 ///
1815 /// # Panics
1816 ///
1817 /// Panics if the value is not finite (NaN or infinite). Use [`Self::try_new`]
1818 /// when the input is not already trusted.
1819 #[must_use]
1820 pub fn new(value: ScalarType) -> Self {
1821 Self::try_new(value)
1822 .expect("HashableScalar can only be constructed from finite scalar values")
1823 }
1824
1825 /// Attempts to create a hashable scalar from a finite value.
1826 ///
1827 /// Returns [`ErrorsHashableScalar::NonFiniteValue`] when `value` is NaN or infinite.
1828 pub fn try_new(value: ScalarType) -> Result<Self, ErrorsHashableScalar> {
1829 if value.is_finite() {
1830 Ok(Self { value })
1831 } else {
1832 Err(ErrorsHashableScalar::NonFiniteValue {
1833 backtrace: capture_backtrace(),
1834 })
1835 }
1836 }
1837}
1838
1839impl<ScalarType: FpScalar> Hash for HashableScalar<ScalarType> {
1840 fn hash<H: Hasher>(&self, state: &mut H) {
1841 self.value
1842 .as_raw_scalar_ref()
1843 .compute_hash_raw_scalar(state);
1844 }
1845}
1846
1847impl<ScalarType: FpScalar> IntoInner for HashableScalar<ScalarType> {
1848 type InnerType = ScalarType;
1849
1850 #[inline(always)]
1851 fn into_inner(self) -> ScalarType {
1852 self.value
1853 }
1854}
1855//-----------------------------------------------------------------------------------
1856
1857//------------------------------------------------------------------------------------------------------------
1858// Tests
1859//------------------------------------------------------------------------------------------------------------
1860
1861#[cfg(test)]
1862mod tests {
1863 use super::*;
1864 use crate::backends::native::validated::RealNative64StrictFinite;
1865
1866 mod hashable_scalar {
1867 use super::*;
1868
1869 #[test]
1870 fn try_new_rejects_non_finite_values() {
1871 assert!(matches!(
1872 HashableScalar::try_new(f64::NAN),
1873 Err(ErrorsHashableScalar::NonFiniteValue { .. })
1874 ));
1875 assert!(matches!(
1876 HashableScalar::try_new(f64::INFINITY),
1877 Err(ErrorsHashableScalar::NonFiniteValue { .. })
1878 ));
1879 }
1880 }
1881
1882 mod absolute_tolerance {
1883 use super::*;
1884
1885 #[test]
1886 fn try_new_valid() {
1887 let tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1e-10_f64).unwrap());
1888 assert_eq!(*tol.as_raw_scalar_ref(), 1e-10);
1889 }
1890
1891 #[test]
1892 fn try_new_zero() {
1893 let tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.0_f64).unwrap());
1894 assert_eq!(*tol.as_raw_scalar_ref(), 0.0);
1895 }
1896
1897 #[test]
1898 fn zero_constructor() {
1899 let tol = AbsoluteTolerance::<f64>::zero();
1900 assert_eq!(*tol.as_raw_scalar_ref(), 0.0);
1901 }
1902
1903 #[test]
1904 fn epsilon_constructor() {
1905 let tol = AbsoluteTolerance::<f64>::epsilon();
1906 assert_eq!(*tol.as_raw_scalar_ref(), f64::EPSILON);
1907 }
1908
1909 #[test]
1910 fn display_trait() {
1911 let tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5_f64).unwrap());
1912 assert_eq!(format!("{}", tol), "0.5");
1913 }
1914
1915 #[test]
1916 fn lower_exp_trait() {
1917 let tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.00001_f64).unwrap());
1918 assert_eq!(format!("{:e}", tol.into_inner().into_inner()), "1e-5");
1919 }
1920
1921 #[test]
1922 fn into_inner() {
1923 let tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5_f64).unwrap());
1924 let inner = tol.into_inner().into_inner();
1925 assert_eq!(inner, 0.5);
1926 }
1927
1928 #[test]
1929 fn clone_and_partial_eq() {
1930 let tol1 = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1e-10_f64).unwrap());
1931 let tol2 = tol1.clone();
1932 assert_eq!(tol1, tol2);
1933 }
1934
1935 #[test]
1936 fn partial_ord() {
1937 let tol1 = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1e-10_f64).unwrap());
1938 let tol2 = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1e-9_f64).unwrap());
1939 assert!(tol1 < tol2);
1940 }
1941
1942 #[test]
1943 fn serialize_deserialize() {
1944 let tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5_f64).unwrap());
1945 let serialized = serde_json::to_string(&tol).unwrap();
1946 let deserialized: AbsoluteTolerance<f64> = serde_json::from_str(&serialized).unwrap();
1947 assert_eq!(tol, deserialized);
1948 }
1949
1950 #[test]
1951 fn with_validated_type() {
1952 let val = RealNative64StrictFinite::try_from_f64(1e-10).unwrap();
1953 let tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(val).unwrap());
1954 assert_eq!(tol.as_raw_scalar_ref(), &1e-10);
1955 }
1956
1957 #[test]
1958 #[cfg(debug_assertions)]
1959 #[should_panic(expected = "is not finite")]
1960 fn debug_panics_on_nan() {
1961 let _ = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(f64::NAN).unwrap());
1962 }
1963
1964 #[test]
1965 #[cfg(debug_assertions)]
1966 #[should_panic(expected = "is not finite")]
1967 fn debug_panics_on_infinity() {
1968 let _ = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(f64::INFINITY).unwrap());
1969 }
1970
1971 #[test]
1972 fn try_new() {
1973 // Test creating an AbsoluteTolerance instance with a value of 0.0
1974 let v = AbsoluteTolerance::<f64>::zero();
1975 assert_eq!(*v.as_raw_scalar_ref(), 0.0);
1976
1977 // Test creating an AbsoluteTolerance instance with a positive value
1978 let v = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(2.0).unwrap());
1979 assert_eq!(*v.as_raw_scalar_ref(), 2.0);
1980 }
1981
1982 #[test]
1983 #[cfg(debug_assertions)]
1984 #[should_panic = "The input value inf is not finite!"]
1985 fn try_new_invalid_infinite() {
1986 // Test creating an invalid AbsoluteTolerance instance with an infinite value
1987 let _tol =
1988 AbsoluteTolerance::new(NonNegativeRealScalar::try_new(f64::INFINITY).unwrap());
1989 }
1990
1991 #[test]
1992 #[cfg(debug_assertions)]
1993 #[should_panic = "The input value NaN is not finite!"]
1994 fn try_new_invalid_nan() {
1995 // Test creating an invalid AbsoluteTolerance instance with a NaN value
1996 let _tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(f64::NAN).unwrap());
1997 }
1998
1999 #[test]
2000 fn epsilon() {
2001 // Test creating an AbsoluteTolerance instance with epsilon value
2002 let epsilon_tol = AbsoluteTolerance::<f64>::epsilon();
2003 assert_eq!(*epsilon_tol.as_raw_scalar_ref(), f64::EPSILON);
2004 }
2005
2006 #[test]
2007 fn equality_operator() {
2008 // Test equality operator for AbsoluteTolerance instances
2009 let a = AbsoluteTolerance::<f64>::zero();
2010 let b = AbsoluteTolerance::<f64>::zero();
2011 assert!(a == b);
2012
2013 let c = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1.0).unwrap());
2014 let d = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1.0).unwrap());
2015 assert!(c == d);
2016 }
2017
2018 #[test]
2019 fn inequality_operator() {
2020 // Test inequality operator for AbsoluteTolerance instances
2021 let a = AbsoluteTolerance::<f64>::zero();
2022 let b = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1.0).unwrap());
2023 assert!(a != b);
2024
2025 let c = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1.0).unwrap());
2026 let d = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(2.0).unwrap());
2027 assert!(c != d);
2028 }
2029
2030 #[test]
2031 fn debug_trait() {
2032 // Test the Debug trait
2033 let tolerance = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5).unwrap());
2034 assert_eq!(
2035 format!("{:?}", tolerance),
2036 "AbsoluteTolerance(NonNegativeRealScalar(0.5))"
2037 );
2038 }
2039
2040 #[test]
2041 fn clone_trait() {
2042 // Test the Clone trait
2043 let tolerance = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5).unwrap());
2044 let cloned_tolerance = tolerance.clone();
2045 assert_eq!(tolerance, cloned_tolerance);
2046 }
2047
2048 #[test]
2049 fn partial_eq_trait() {
2050 // Test the PartialEq trait
2051 let tolerance1 = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5).unwrap());
2052 let tolerance2 = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5).unwrap());
2053 assert_eq!(tolerance1, tolerance2);
2054 }
2055
2056 #[test]
2057 fn partial_ord_trait() {
2058 // Test the PartialOrd trait
2059 let tolerance1 = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5).unwrap());
2060 let tolerance2 = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1.0).unwrap());
2061 assert!(tolerance1 < tolerance2);
2062 }
2063
2064 #[test]
2065 fn as_ref_trait() {
2066 // Test the AsRef trait
2067 let tolerance = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(0.5).unwrap());
2068 let inner: &f64 = tolerance.as_raw_scalar_ref();
2069 assert_eq!(*inner, 0.5);
2070 }
2071
2072 #[test]
2073 fn zero() {
2074 // Test the zero constructor
2075 let zero_tol = AbsoluteTolerance::<f64>::zero();
2076 assert_eq!(*zero_tol.as_raw_scalar_ref(), 0.0);
2077 }
2078
2079 #[test]
2080 fn edge_cases() {
2081 // Test with very small positive values
2082 let tiny_tol =
2083 AbsoluteTolerance::new(NonNegativeRealScalar::try_new(f64::MIN_POSITIVE).unwrap());
2084 assert_eq!(*tiny_tol.as_raw_scalar_ref(), f64::MIN_POSITIVE);
2085
2086 // Test with large positive values
2087 let large_tol = AbsoluteTolerance::new(NonNegativeRealScalar::try_new(1e100).unwrap());
2088 assert_eq!(*large_tol.as_raw_scalar_ref(), 1e100);
2089 }
2090
2091 #[test]
2092 fn approximately_equal_exact_match() {
2093 let a = 1.0_f64;
2094 let b = 1.0_f64;
2095 let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
2096 assert!(approximately_equal(a, b, &tol));
2097 }
2098
2099 #[test]
2100 fn approximately_equal_within_tolerance() {
2101 let a = 1.0_f64;
2102 let b = 1.0 + 1e-11_f64;
2103 let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
2104 assert!(approximately_equal(a, b, &tol));
2105 }
2106
2107 #[test]
2108 fn approximately_equal_outside_tolerance() {
2109 let a = 1.0_f64;
2110 let b = 1.0 + 1e-9_f64;
2111 let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
2112 assert!(!approximately_equal(a, b, &tol));
2113 }
2114
2115 #[test]
2116 fn approximately_equal_negative_values() {
2117 let a = -5.0_f64;
2118 let b = -5.0 + 1e-12_f64;
2119 let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
2120 assert!(approximately_equal(a, b, &tol));
2121 }
2122
2123 #[test]
2124 fn approximately_equal_zero_tolerance() {
2125 let a = 1.0_f64;
2126 let b = 1.0_f64;
2127 let tol = AbsoluteTolerance::<f64>::zero();
2128 assert!(approximately_equal(a, b, &tol));
2129
2130 let c = 1.0_f64 + 1e-15;
2131 assert!(!approximately_equal(a, c, &tol)); // Even tiny difference fails with zero tolerance
2132 }
2133
2134 #[test]
2135 fn approximately_equal_large_difference_outside_tolerance() {
2136 let a = 1.0_f64;
2137 let b = 2.0_f64;
2138 let tol = AbsoluteTolerance::try_new(0.1_f64).unwrap();
2139 assert!(!approximately_equal(a, b, &tol));
2140 }
2141
2142 #[test]
2143 fn approximately_equal_symmetric() {
2144 let a = 1.0_f64;
2145 let b = 1.0 + 1e-12_f64;
2146 let tol = AbsoluteTolerance::try_new(1e-10_f64).unwrap();
2147 assert_eq!(
2148 approximately_equal(a, b, &tol),
2149 approximately_equal(b, a, &tol)
2150 );
2151 }
2152
2153 #[test]
2154 fn approximately_equal_with_validated_type() {
2155 let a = RealNative64StrictFinite::try_from_f64(1.0).unwrap();
2156 let b = RealNative64StrictFinite::try_from_f64(1.0 + 1e-11).unwrap();
2157 let tol = AbsoluteTolerance::new(
2158 NonNegativeRealScalar::try_new(
2159 RealNative64StrictFinite::try_from_f64(1e-10).unwrap(),
2160 )
2161 .unwrap(),
2162 );
2163 assert!(approximately_equal(a, b, &tol));
2164 }
2165 }
2166
2167 mod relative_tolerance {
2168 use super::*;
2169
2170 #[test]
2171 fn try_new_valid() {
2172 let tol = RelativeTolerance::try_new(0.01_f64).unwrap();
2173 assert_eq!(*tol.as_raw_scalar_ref(), 0.01);
2174 }
2175
2176 #[test]
2177 fn try_new_zero() {
2178 let tol = RelativeTolerance::try_new(0.0_f64).unwrap();
2179 assert_eq!(*tol.as_raw_scalar_ref(), 0.0);
2180 }
2181
2182 #[test]
2183 fn try_new_negative() {
2184 let result = RelativeTolerance::try_new(-0.01_f64);
2185 assert!(matches!(
2186 result,
2187 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
2188 ));
2189 }
2190
2191 #[test]
2192 fn zero_constructor() {
2193 let tol = RelativeTolerance::<f64>::zero();
2194 assert_eq!(*tol.as_raw_scalar_ref(), 0.0);
2195 }
2196
2197 #[test]
2198 fn epsilon_constructor() {
2199 let tol = RelativeTolerance::<f64>::epsilon();
2200 assert_eq!(*tol.as_raw_scalar_ref(), f64::EPSILON);
2201 }
2202
2203 #[test]
2204 fn absolute_tolerance_positive_reference() {
2205 let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap();
2206 let abs_tol = rel_tol.absolute_tolerance(100.0);
2207 assert_eq!(*abs_tol.as_raw_scalar_ref(), 10.0);
2208 }
2209
2210 #[test]
2211 fn absolute_tolerance_negative_reference() {
2212 let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap();
2213 let abs_tol = rel_tol.absolute_tolerance(-50.0);
2214 assert_eq!(*abs_tol.as_raw_scalar_ref(), 5.0);
2215 }
2216
2217 #[test]
2218 fn absolute_tolerance_zero_reference() {
2219 let rel_tol = RelativeTolerance::try_new(0.1_f64).unwrap();
2220 let abs_tol = rel_tol.absolute_tolerance(0.0);
2221 assert_eq!(*abs_tol.as_raw_scalar_ref(), 0.0);
2222 }
2223
2224 #[test]
2225 fn display_trait() {
2226 let tol = RelativeTolerance::try_new(0.5_f64).unwrap();
2227 assert_eq!(format!("{}", tol), "0.5");
2228 }
2229
2230 #[test]
2231 fn into_inner() {
2232 let tol = RelativeTolerance::try_new(0.01_f64).unwrap();
2233 let inner = tol.into_inner();
2234 assert_eq!(inner.as_raw_scalar_ref(), &0.01);
2235 }
2236
2237 #[test]
2238 fn serialize_deserialize() {
2239 let tol = RelativeTolerance::try_new(0.01_f64).unwrap();
2240 let serialized = serde_json::to_string(&tol).unwrap();
2241 let deserialized: RelativeTolerance<f64> = serde_json::from_str(&serialized).unwrap();
2242 assert_eq!(tol, deserialized);
2243 }
2244
2245 #[test]
2246 fn try_new() {
2247 // Test creating a RelativeTolerance instance with a value of 0.0
2248 let v = RelativeTolerance::<f64>::zero();
2249 assert_eq!(*v.as_raw_scalar_ref(), 0.0);
2250
2251 // Test creating a RelativeTolerance instance with a positive value
2252 let v = RelativeTolerance::try_new(2.0).unwrap();
2253 assert_eq!(*v.as_raw_scalar_ref(), 2.0);
2254 }
2255
2256 #[test]
2257 fn try_new_invalid_negative() {
2258 // Test creating an invalid RelativeTolerance instance with a negative value
2259 let tol = RelativeTolerance::try_new(-0.1);
2260 assert!(matches!(
2261 tol,
2262 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
2263 ));
2264 }
2265
2266 #[test]
2267 #[cfg(debug_assertions)]
2268 #[should_panic = "The input value inf is not finite!"]
2269 fn try_new_invalid_infinite() {
2270 // Test creating an invalid RelativeTolerance instance with an infinite value
2271 let _tol = RelativeTolerance::try_new(f64::INFINITY);
2272 }
2273
2274 #[test]
2275 #[cfg(debug_assertions)]
2276 #[should_panic = "The input value NaN is not finite!"]
2277 fn try_new_invalid_nan() {
2278 // Test creating an invalid RelativeTolerance instance with a NaN value
2279 let _tol = RelativeTolerance::try_new(f64::NAN);
2280 }
2281
2282 #[test]
2283 fn epsilon() {
2284 // Test creating a RelativeTolerance instance with epsilon value
2285 let epsilon_tol = RelativeTolerance::<f64>::epsilon();
2286 assert_eq!(*epsilon_tol.as_raw_scalar_ref(), f64::EPSILON);
2287 }
2288
2289 #[test]
2290 fn absolute_tolerance() {
2291 // Test converting relative tolerance to absolute tolerance
2292 let rel_tol = RelativeTolerance::try_new(0.1).unwrap();
2293 let reference = 10.0;
2294 let abs_tol = rel_tol.absolute_tolerance(reference);
2295 assert_eq!(*abs_tol.as_raw_scalar_ref(), 1.0); // 0.1 * |10.0| = 1.0
2296
2297 // Test with negative reference value
2298 let reference = -5.0;
2299 let abs_tol = rel_tol.absolute_tolerance(reference);
2300 assert_eq!(*abs_tol.as_raw_scalar_ref(), 0.5); // 0.1 * |-5.0| = 0.5
2301
2302 // Test with zero reference value
2303 let reference = 0.0;
2304 let abs_tol = rel_tol.absolute_tolerance(reference);
2305 assert_eq!(*abs_tol.as_raw_scalar_ref(), 0.0); // 0.1 * |0.0| = 0.0
2306 }
2307
2308 #[test]
2309 fn equality_operator() {
2310 // Test equality operator for RelativeTolerance instances
2311 let a = RelativeTolerance::<f64>::zero();
2312 let b = RelativeTolerance::<f64>::zero();
2313 assert!(a == b);
2314
2315 let c = RelativeTolerance::try_new(1.0).unwrap();
2316 let d = RelativeTolerance::try_new(1.0).unwrap();
2317 assert!(c == d);
2318 }
2319
2320 #[test]
2321 fn inequality_operator() {
2322 // Test inequality operator for RelativeTolerance instances
2323 let a = RelativeTolerance::<f64>::zero();
2324 let b = RelativeTolerance::try_new(1.0).unwrap();
2325 assert!(a != b);
2326
2327 let c = RelativeTolerance::try_new(1.0).unwrap();
2328 let d = RelativeTolerance::try_new(2.0).unwrap();
2329 assert!(c != d);
2330 }
2331
2332 #[test]
2333 fn debug_trait() {
2334 // Test the Debug trait
2335 let tolerance = RelativeTolerance::try_new(0.5).unwrap();
2336 assert_eq!(
2337 format!("{:?}", tolerance),
2338 "RelativeTolerance(NonNegativeRealScalar(0.5))"
2339 );
2340 }
2341
2342 #[test]
2343 fn clone_trait() {
2344 // Test the Clone trait
2345 let tolerance = RelativeTolerance::try_new(0.5).unwrap();
2346 let cloned_tolerance = tolerance.clone();
2347 assert_eq!(tolerance, cloned_tolerance);
2348 }
2349
2350 #[test]
2351 fn partial_eq_trait() {
2352 // Test the PartialEq trait
2353 let tolerance1 = RelativeTolerance::try_new(0.5).unwrap();
2354 let tolerance2 = RelativeTolerance::try_new(0.5).unwrap();
2355 assert_eq!(tolerance1, tolerance2);
2356 }
2357
2358 #[test]
2359 fn partial_ord_trait() {
2360 // Test the PartialOrd trait
2361 let tolerance1 = RelativeTolerance::try_new(0.5).unwrap();
2362 let tolerance2 = RelativeTolerance::try_new(1.0).unwrap();
2363 assert!(tolerance1 < tolerance2);
2364 }
2365
2366 #[test]
2367 fn as_ref_trait() {
2368 // Test the AsRef trait
2369 let tolerance = RelativeTolerance::try_new(0.5).unwrap();
2370 let inner: &f64 = tolerance.as_raw_scalar_ref();
2371 assert_eq!(*inner, 0.5);
2372 }
2373
2374 #[test]
2375 fn lower_exp_trait() {
2376 // Test the LowerExp trait (scientific notation formatting)
2377 let tolerance = RelativeTolerance::try_new(0.00001).unwrap();
2378 assert_eq!(format!("{:e}", tolerance.as_raw_scalar_ref()), "1e-5");
2379 }
2380 }
2381
2382 mod positive_real_scalar {
2383 use super::*;
2384 use crate::backends::native::validated::RealNative64StrictFiniteInDebug;
2385
2386 #[test]
2387 fn try_new_positive() {
2388 let val = PositiveRealScalar::try_new(2.5_f64).unwrap();
2389 assert_eq!(*val.as_raw_scalar_ref(), 2.5);
2390 }
2391
2392 #[test]
2393 fn try_new_min_positive() {
2394 let val = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
2395 assert_eq!(*val.as_raw_scalar_ref(), f64::MIN_POSITIVE);
2396 }
2397
2398 #[test]
2399 fn try_new_zero_fails() {
2400 let result = PositiveRealScalar::try_new(0.0_f64);
2401 assert!(matches!(
2402 result,
2403 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
2404 ));
2405 }
2406
2407 #[test]
2408 fn try_new_negative_fails() {
2409 let result = PositiveRealScalar::try_new(-1.0_f64);
2410 assert!(
2411 matches!(result, Err(ErrorsPositiveRealScalar::NegativeValue { value, .. }) if value == -1.0)
2412 );
2413 }
2414
2415 #[test]
2416 fn display_trait() {
2417 let val = PositiveRealScalar::try_new(1.618_f64).unwrap();
2418 assert_eq!(format!("{}", val), "1.618");
2419 }
2420
2421 #[test]
2422 fn into_inner() {
2423 let val = PositiveRealScalar::try_new(42.0_f64).unwrap();
2424 let inner = val.into_inner();
2425 assert_eq!(inner, 42.0);
2426 }
2427
2428 #[test]
2429 fn partial_ord() {
2430 let a = PositiveRealScalar::try_new(1.0_f64).unwrap();
2431 let b = PositiveRealScalar::try_new(2.0_f64).unwrap();
2432 assert!(a < b);
2433 }
2434
2435 #[test]
2436 fn serialize_deserialize() {
2437 let val = PositiveRealScalar::try_new(3.0_f64).unwrap();
2438 let serialized = serde_json::to_string(&val).unwrap();
2439 let deserialized: PositiveRealScalar<f64> = serde_json::from_str(&serialized).unwrap();
2440 assert_eq!(val, deserialized);
2441 }
2442
2443 #[test]
2444 fn with_validated_type() {
2445 let inner = RealNative64StrictFinite::try_from_f64(5.0).unwrap();
2446 let val = PositiveRealScalar::try_new(inner).unwrap();
2447 assert_eq!(*val.as_raw_scalar_ref(), 5.0);
2448 }
2449
2450 #[test]
2451 #[cfg(debug_assertions)]
2452 #[should_panic(expected = "is not finite")]
2453 fn debug_panics_on_nan() {
2454 let _ = PositiveRealScalar::try_new(f64::NAN);
2455 }
2456
2457 #[test]
2458 fn try_new() {
2459 // Test creating a PositiveRealScalar instance with a positive value
2460 let v = PositiveRealScalar::try_new(2.5).unwrap();
2461 assert_eq!(*v.as_raw_scalar_ref(), 2.5);
2462
2463 // Test creating a PositiveRealScalar instance with a very small positive value
2464 let v = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
2465 assert_eq!(*v.as_raw_scalar_ref(), f64::MIN_POSITIVE);
2466
2467 // Test creating a PositiveRealScalar instance with a large positive value
2468 let v = PositiveRealScalar::try_new(1e100).unwrap();
2469 assert_eq!(*v.as_raw_scalar_ref(), 1e100);
2470 }
2471
2472 #[test]
2473 fn try_new_invalid_negative() {
2474 // Test creating an invalid PositiveRealScalar instance with a negative value
2475 let scalar = PositiveRealScalar::try_new(-0.1);
2476 assert!(matches!(
2477 scalar,
2478 Err(ErrorsPositiveRealScalar::NegativeValue { .. })
2479 ));
2480
2481 // Test with a negative value close to zero
2482 let scalar = PositiveRealScalar::try_new(-f64::EPSILON);
2483 assert!(matches!(
2484 scalar,
2485 Err(ErrorsPositiveRealScalar::NegativeValue { .. })
2486 ));
2487 }
2488
2489 #[test]
2490 fn try_new_invalid_zero() {
2491 // Test creating an invalid PositiveRealScalar instance with zero
2492 let scalar = PositiveRealScalar::try_new(0.0);
2493 assert!(matches!(
2494 scalar,
2495 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
2496 ));
2497 }
2498
2499 #[test]
2500 #[cfg(debug_assertions)]
2501 #[should_panic = "The input value inf is not finite!"]
2502 fn try_new_invalid_infinite() {
2503 // Test creating an invalid PositiveRealScalar instance with an infinite value
2504 let _scalar = PositiveRealScalar::try_new(f64::INFINITY);
2505 }
2506
2507 #[test]
2508 #[cfg(debug_assertions)]
2509 #[should_panic = "The input value -inf is not finite!"]
2510 fn try_new_invalid_negative_infinite() {
2511 // Test creating an invalid PositiveRealScalar instance with negative infinity
2512 let _scalar = PositiveRealScalar::try_new(f64::NEG_INFINITY);
2513 }
2514
2515 #[test]
2516 #[cfg(debug_assertions)]
2517 #[should_panic = "The input value NaN is not finite!"]
2518 fn try_new_invalid_nan() {
2519 // Test creating an invalid PositiveRealScalar instance with a NaN value
2520 let _scalar = PositiveRealScalar::try_new(f64::NAN);
2521 }
2522
2523 #[test]
2524 fn equality_operator() {
2525 // Test equality operator for PositiveRealScalar instances
2526 let a = PositiveRealScalar::try_new(1.5).unwrap();
2527 let b = PositiveRealScalar::try_new(1.5).unwrap();
2528 assert!(a == b);
2529
2530 let c = PositiveRealScalar::try_new(3.1).unwrap();
2531 let d = PositiveRealScalar::try_new(3.1).unwrap();
2532 assert!(c == d);
2533 }
2534
2535 #[test]
2536 fn inequality_operator() {
2537 // Test inequality operator for PositiveRealScalar instances
2538 let a = PositiveRealScalar::try_new(1.0).unwrap();
2539 let b = PositiveRealScalar::try_new(2.0).unwrap();
2540 assert!(a != b);
2541
2542 let c = PositiveRealScalar::try_new(0.5).unwrap();
2543 let d = PositiveRealScalar::try_new(1.5).unwrap();
2544 assert!(c != d);
2545 }
2546
2547 #[test]
2548 fn debug_trait() {
2549 // Test the Debug trait
2550 let scalar = PositiveRealScalar::try_new(2.7).unwrap();
2551 assert_eq!(format!("{:?}", scalar), "PositiveRealScalar(2.7)");
2552 }
2553
2554 #[test]
2555 fn clone_trait() {
2556 // Test the Clone trait
2557 let scalar = PositiveRealScalar::try_new(1.414).unwrap();
2558 let cloned_scalar = scalar.clone();
2559 assert_eq!(scalar, cloned_scalar);
2560 }
2561
2562 #[test]
2563 fn partial_eq_trait() {
2564 // Test the PartialEq trait
2565 let scalar1 = PositiveRealScalar::try_new(0.577).unwrap();
2566 let scalar2 = PositiveRealScalar::try_new(0.577).unwrap();
2567 assert_eq!(scalar1, scalar2);
2568 }
2569
2570 #[test]
2571 fn partial_ord_trait() {
2572 // Test the PartialOrd trait
2573 let scalar1 = PositiveRealScalar::try_new(1.0).unwrap();
2574 let scalar2 = PositiveRealScalar::try_new(2.0).unwrap();
2575 assert!(scalar1 < scalar2);
2576
2577 let scalar3 = PositiveRealScalar::try_new(3.0).unwrap();
2578 let scalar4 = PositiveRealScalar::try_new(3.0).unwrap();
2579 assert!(scalar3 <= scalar4);
2580 assert!(scalar4 >= scalar3);
2581 }
2582
2583 #[test]
2584 fn as_ref_trait() {
2585 // Test the AsRef trait
2586 let scalar = PositiveRealScalar::try_new(2.0).unwrap();
2587 let inner: &f64 = scalar.as_raw_scalar_ref();
2588 assert_eq!(*inner, 2.0);
2589 }
2590
2591 #[test]
2592 fn edge_cases() {
2593 // Test with very small positive values
2594 let tiny_scalar = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
2595 assert_eq!(*tiny_scalar.as_raw_scalar_ref(), f64::MIN_POSITIVE);
2596
2597 // Test with large positive values
2598 let large_scalar = PositiveRealScalar::try_new(f64::MAX).unwrap();
2599 assert_eq!(*large_scalar.as_raw_scalar_ref(), f64::MAX);
2600
2601 // Test with epsilon
2602 let epsilon_scalar = PositiveRealScalar::try_new(f64::EPSILON).unwrap();
2603 assert_eq!(*epsilon_scalar.as_raw_scalar_ref(), f64::EPSILON);
2604 }
2605
2606 #[test]
2607 fn generic_scalar_types() {
2608 // Test with RealNative64StrictFiniteInDebug type
2609 let scalar =
2610 PositiveRealScalar::try_new(RealNative64StrictFiniteInDebug::try_new(5.0).unwrap())
2611 .unwrap();
2612 assert_eq!(scalar.as_raw_scalar_ref(), &5.0);
2613
2614 // Test error case with generic type
2615 let zero_value = RealNative64StrictFiniteInDebug::try_new(0.0).unwrap();
2616 let scalar_result = PositiveRealScalar::try_new(zero_value);
2617 assert!(matches!(
2618 scalar_result,
2619 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
2620 ));
2621 }
2622
2623 #[test]
2624 fn mathematical_operations() {
2625 // Test that the wrapped values behave correctly in mathematical contexts
2626 let scalar1 = PositiveRealScalar::try_new(2.0).unwrap();
2627 let scalar2 = PositiveRealScalar::try_new(3.0).unwrap();
2628
2629 // Test comparison operations
2630 assert!(scalar1 < scalar2);
2631 assert!(scalar2 > scalar1);
2632 assert_eq!(
2633 scalar1.partial_cmp(&scalar2),
2634 Some(std::cmp::Ordering::Less)
2635 );
2636 }
2637
2638 #[test]
2639 fn error_messages() {
2640 // Test that error messages are descriptive
2641 let negative_result = PositiveRealScalar::try_new(-1.0);
2642 match negative_result {
2643 Err(ErrorsPositiveRealScalar::NegativeValue { value, .. }) => {
2644 assert_eq!(value, -1.0);
2645 }
2646 _ => panic!("Expected NegativeValue error"),
2647 }
2648
2649 let zero_result = PositiveRealScalar::try_new(0.0);
2650 match zero_result {
2651 Err(ErrorsPositiveRealScalar::ZeroValue { .. }) => {
2652 // Expected
2653 }
2654 _ => panic!("Expected ZeroValue error"),
2655 }
2656 }
2657
2658 #[test]
2659 fn sqrt_preserves_positivity() {
2660 // Test that sqrt of a positive value is also positive
2661 let base = PositiveRealScalar::try_new(4.0_f64).unwrap();
2662 let result = base.sqrt();
2663 assert_eq!(*result.as_raw_scalar_ref(), 2.0);
2664
2665 // Test with other values
2666 let base = PositiveRealScalar::try_new(9.0_f64).unwrap();
2667 let result = base.sqrt();
2668 assert_eq!(*result.as_raw_scalar_ref(), 3.0);
2669 }
2670
2671 #[test]
2672 fn sqrt_with_small_base() {
2673 // Test sqrt with very small positive value
2674 let base = PositiveRealScalar::try_new(0.01_f64).unwrap();
2675 let result = base.sqrt();
2676 assert_eq!(*result.as_raw_scalar_ref(), 0.1);
2677 }
2678
2679 #[test]
2680 fn sqrt_with_generic_types() {
2681 // Test sqrt with validated types
2682 let base_val = RealNative64StrictFiniteInDebug::try_new(4.0).unwrap();
2683 let base = PositiveRealScalar::try_new(base_val).unwrap();
2684
2685 let result = base.sqrt();
2686 assert_eq!(*result.as_raw_scalar_ref(), 2.0);
2687 }
2688
2689 #[test]
2690 fn new_constructor_debug_validates() {
2691 // Test the new() constructor (validates in debug, skips in release)
2692 // In debug mode, new() should behave like try_new().unwrap()
2693 #[cfg(debug_assertions)]
2694 {
2695 let val = PositiveRealScalar::new(2.5_f64);
2696 assert_eq!(*val.as_raw_scalar_ref(), 2.5);
2697 }
2698
2699 #[cfg(not(debug_assertions))]
2700 {
2701 // In release mode, new() trusts the input
2702 let val = PositiveRealScalar::new(2.5_f64);
2703 assert_eq!(*val.as_raw_scalar_ref(), 2.5);
2704 }
2705 }
2706
2707 #[test]
2708 #[cfg(debug_assertions)]
2709 #[should_panic(expected = "Error in the creation")]
2710 fn new_constructor_panics_on_zero_debug() {
2711 // In debug mode, new() should panic on zero
2712 let _ = PositiveRealScalar::new(0.0_f64);
2713 }
2714
2715 #[test]
2716 #[cfg(debug_assertions)]
2717 #[should_panic(expected = "Error in the creation")]
2718 fn new_constructor_panics_on_negative_debug() {
2719 // In debug mode, new() should panic on negative
2720 let _ = PositiveRealScalar::new(-1.0_f64);
2721 }
2722
2723 #[test]
2724 fn one_trait_identity() {
2725 // Test the One trait: one() should return PositiveRealScalar(1.0)
2726 let one = PositiveRealScalar::<f64>::one();
2727 assert_eq!(*one.as_raw_scalar_ref(), 1.0);
2728 }
2729
2730 #[test]
2731 fn one_trait_multiplicative_identity() {
2732 // Test that one acts as multiplicative identity
2733 let value = PositiveRealScalar::try_new(5.5_f64).unwrap();
2734 let one = PositiveRealScalar::<f64>::one();
2735
2736 let result = value.clone() * one.clone();
2737 assert_eq!(*result.as_raw_scalar_ref(), 5.5);
2738
2739 let result = one * value;
2740 assert_eq!(*result.as_raw_scalar_ref(), 5.5);
2741 }
2742
2743 #[test]
2744 fn one_trait_with_generic_types() {
2745 // Test One trait with validated types
2746 let one = PositiveRealScalar::<RealNative64StrictFiniteInDebug>::one();
2747 assert_eq!(*one.as_raw_scalar_ref(), 1.0);
2748 }
2749
2750 #[test]
2751 fn pow_integer_exponents() {
2752 // Test Pow trait with different integer exponent types
2753 let base = PositiveRealScalar::try_new(2.0_f64).unwrap();
2754
2755 // Test with u32
2756 let result = base.clone().pow(3u32);
2757 assert_eq!(*result.as_raw_scalar_ref(), 8.0);
2758
2759 // Test with u8
2760 let result = base.clone().pow(2u8);
2761 assert_eq!(*result.as_raw_scalar_ref(), 4.0);
2762
2763 // Test with i32 (positive)
2764 let result = base.clone().pow(2i32);
2765 assert_eq!(*result.as_raw_scalar_ref(), 4.0);
2766 }
2767
2768 #[test]
2769 fn pow_zero_exponent() {
2770 // Test that any number raised to power 0 equals 1
2771 let base = PositiveRealScalar::try_new(5.5_f64).unwrap();
2772
2773 let result = base.pow(0u32);
2774 assert_eq!(*result.as_raw_scalar_ref(), 1.0);
2775 }
2776
2777 #[test]
2778 fn pow_one_exponent() {
2779 // Test that any number raised to power 1 equals itself
2780 let base = PositiveRealScalar::try_new(3.7_f64).unwrap();
2781
2782 let result = base.clone().pow(1u32);
2783 assert_eq!(*result.as_raw_scalar_ref(), 3.7);
2784 }
2785
2786 #[test]
2787 fn pow_preserves_positivity() {
2788 // Test that raising positive to any integer power preserves positivity
2789 let base = PositiveRealScalar::try_new(2.5_f64).unwrap();
2790
2791 let result = base.clone().pow(5u32);
2792 assert!(*result.as_raw_scalar_ref() > 0.0);
2793 assert_eq!(*result.as_raw_scalar_ref(), 2.5_f64.powi(5));
2794 }
2795
2796 #[test]
2797 fn pow_with_try_pow() {
2798 // Test the try_pow method
2799 let base = PositiveRealScalar::try_new(3.0_f64).unwrap();
2800
2801 let result = base.clone().try_pow(2u32);
2802 assert!(result.is_ok());
2803 assert_eq!(*result.unwrap().as_raw_scalar_ref(), 9.0);
2804
2805 // Test with different exponent types
2806 let result = base.try_pow(3i32);
2807 assert!(result.is_ok());
2808 assert_eq!(*result.unwrap().as_raw_scalar_ref(), 27.0);
2809 }
2810
2811 #[test]
2812 fn pow_large_exponents() {
2813 // Test with large exponents
2814 let base = PositiveRealScalar::try_new(1.1_f64).unwrap();
2815 let result = base.pow(10u32);
2816 let expected = 1.1_f64.powi(10);
2817 assert!((result.as_raw_scalar_ref() - expected).abs() < 1e-10);
2818 }
2819
2820 #[test]
2821 fn pow_small_base() {
2822 // Test with small base values
2823 let base = PositiveRealScalar::try_new(0.5_f64).unwrap();
2824
2825 let result = base.clone().pow(2u32);
2826 assert_eq!(*result.as_raw_scalar_ref(), 0.25);
2827
2828 let result = base.pow(3u32);
2829 assert_eq!(*result.as_raw_scalar_ref(), 0.125);
2830 }
2831
2832 #[test]
2833 fn pow_with_generic_types() {
2834 // Test Pow with validated types
2835 let base_val = RealNative64StrictFiniteInDebug::try_new(2.0).unwrap();
2836 let base = PositiveRealScalar::try_new(base_val).unwrap();
2837
2838 let result = base.pow(3u32);
2839 assert_eq!(*result.as_raw_scalar_ref(), 8.0);
2840 }
2841
2842 #[test]
2843 fn pow_chain_operations() {
2844 // Test chaining pow operations
2845 let base = PositiveRealScalar::try_new(2.0_f64).unwrap();
2846
2847 // (2^3)^2 = 2^6 = 64
2848 let result = base.pow(3u32).pow(2u32);
2849 assert_eq!(*result.as_raw_scalar_ref(), 64.0);
2850 }
2851
2852 #[test]
2853 fn add_trait_value_semantics() {
2854 // Test Add trait for by-value operands
2855 let a = PositiveRealScalar::try_new(2.0_f64).unwrap();
2856 let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
2857
2858 let sum = a + b;
2859 assert_eq!(*sum.as_raw_scalar_ref(), 5.0);
2860 assert!(*sum.as_raw_scalar_ref() > 0.0);
2861 }
2862
2863 #[test]
2864 fn add_trait_reference_semantics() {
2865 // Test Add trait with reference operand
2866 let a = PositiveRealScalar::try_new(2.0_f64).unwrap();
2867 let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
2868
2869 let sum = a + &b;
2870 assert_eq!(*sum.as_raw_scalar_ref(), 5.0);
2871 }
2872
2873 #[test]
2874 fn mul_trait_value_semantics() {
2875 // Test Mul trait for by-value operands
2876 let a = PositiveRealScalar::try_new(2.5_f64).unwrap();
2877 let b = PositiveRealScalar::try_new(4.0_f64).unwrap();
2878
2879 let product = a * b;
2880 assert_eq!(*product.as_raw_scalar_ref(), 10.0);
2881 assert!(*product.as_raw_scalar_ref() > 0.0);
2882 }
2883
2884 #[test]
2885 fn mul_trait_reference_semantics() {
2886 // Test Mul trait with reference operand
2887 let a = PositiveRealScalar::try_new(2.5_f64).unwrap();
2888 let b = PositiveRealScalar::try_new(4.0_f64).unwrap();
2889
2890 let product = a * &b;
2891 assert_eq!(*product.as_raw_scalar_ref(), 10.0);
2892 }
2893
2894 #[test]
2895 fn add_assign_value_semantics() {
2896 // Test AddAssign trait for by-value operand
2897 let mut a = PositiveRealScalar::try_new(2.0_f64).unwrap();
2898 let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
2899
2900 a += b;
2901 assert_eq!(*a.as_raw_scalar_ref(), 5.0);
2902 }
2903
2904 #[test]
2905 fn add_assign_reference_semantics() {
2906 // Test AddAssign trait with reference operand
2907 let mut a = PositiveRealScalar::try_new(2.0_f64).unwrap();
2908 let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
2909
2910 a += &b;
2911 assert_eq!(*a.as_raw_scalar_ref(), 5.0);
2912 }
2913
2914 #[test]
2915 fn mul_assign_value_semantics() {
2916 // Test MulAssign trait for by-value operand
2917 let mut a = PositiveRealScalar::try_new(2.0_f64).unwrap();
2918 let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
2919
2920 a *= b;
2921 assert_eq!(*a.as_raw_scalar_ref(), 6.0);
2922 }
2923
2924 #[test]
2925 fn mul_assign_reference_semantics() {
2926 // Test MulAssign trait with reference operand
2927 let mut a = PositiveRealScalar::try_new(2.0_f64).unwrap();
2928 let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
2929
2930 a *= &b;
2931 assert_eq!(*a.as_raw_scalar_ref(), 6.0);
2932 }
2933
2934 #[test]
2935 fn max_min_traits() {
2936 // Test Max/Min traits (delegated to default behavior)
2937 let a = PositiveRealScalar::try_new(1.0_f64).unwrap();
2938 let b = PositiveRealScalar::try_new(3.0_f64).unwrap();
2939
2940 let max_val = Max::max_by_ref(&a, &b);
2941 assert_eq!(*max_val.as_raw_scalar_ref(), 3.0);
2942
2943 let min_val = Min::min_by_ref(&a, &b);
2944 assert_eq!(*min_val.as_raw_scalar_ref(), 1.0);
2945 }
2946 }
2947
2948 mod non_negative_real_scalar {
2949 use super::*;
2950 use crate::backends::native::validated::RealNative64StrictFiniteInDebug;
2951 use crate::functions::{Max, Min};
2952 use num::Zero;
2953
2954 #[test]
2955 fn add_trait_preserves_non_negativity() {
2956 let a = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
2957 let b = NonNegativeRealScalar::try_new(3.5_f64).unwrap();
2958
2959 let sum = a + b;
2960 assert_eq!(*sum.as_raw_scalar_ref(), 5.5);
2961 assert!(*sum.as_raw_scalar_ref() >= 0.0);
2962 }
2963
2964 #[test]
2965 fn add_trait_zero_is_identity() {
2966 let value = NonNegativeRealScalar::try_new(4.25_f64).unwrap();
2967 let zero = NonNegativeRealScalar::<f64>::zero();
2968
2969 assert_eq!(*(value.clone() + zero.clone()).as_raw_scalar_ref(), 4.25);
2970 assert_eq!(*(zero + value).as_raw_scalar_ref(), 4.25);
2971 }
2972
2973 #[test]
2974 fn zero_trait_methods() {
2975 let zero = NonNegativeRealScalar::<f64>::zero();
2976 let non_zero = NonNegativeRealScalar::try_new(0.5_f64).unwrap();
2977
2978 assert!(zero.is_zero());
2979 assert!(!non_zero.is_zero());
2980 assert_eq!(*zero.as_raw_scalar_ref(), 0.0);
2981 }
2982
2983 #[test]
2984 fn max_trait_by_ref_and_by_value() {
2985 let small = NonNegativeRealScalar::try_new(1.0_f64).unwrap();
2986 let large = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
2987
2988 let by_ref = Max::max_by_ref(&small, &large);
2989 assert_eq!(*by_ref.as_raw_scalar_ref(), 3.0);
2990
2991 let by_value = Max::max_by_value(small, large);
2992 assert_eq!(*by_value.as_raw_scalar_ref(), 3.0);
2993 }
2994
2995 #[test]
2996 fn min_trait_by_ref_and_by_value() {
2997 let small = NonNegativeRealScalar::try_new(1.0_f64).unwrap();
2998 let large = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
2999
3000 let by_ref = Min::min_by_ref(&small, &large);
3001 assert_eq!(*by_ref.as_raw_scalar_ref(), 1.0);
3002
3003 let by_value = Min::min_by_value(small, large);
3004 assert_eq!(*by_value.as_raw_scalar_ref(), 1.0);
3005 }
3006
3007 #[test]
3008 fn max_min_by_ref_equal_values_returns_left_operand() {
3009 let left_max = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
3010 let right_max = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
3011 let selected_max = Max::max_by_ref(&left_max, &right_max);
3012 assert!(std::ptr::eq(selected_max, &left_max));
3013
3014 let left_min = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
3015 let right_min = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
3016 let selected_min = Min::min_by_ref(&left_min, &right_min);
3017 assert!(std::ptr::eq(selected_min, &left_min));
3018 }
3019
3020 #[test]
3021 fn try_new_negative_fails() {
3022 let result = NonNegativeRealScalar::try_new(-1.0_f64);
3023 assert!(
3024 matches!(result, Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) if value == -1.0)
3025 );
3026 }
3027
3028 #[test]
3029 fn display_trait() {
3030 let val = NonNegativeRealScalar::try_new(2.7_f64).unwrap();
3031 assert_eq!(format!("{}", val), "2.7");
3032
3033 let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
3034 assert_eq!(format!("{}", zero), "0");
3035 }
3036
3037 #[test]
3038 fn partial_ord() {
3039 let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
3040 let positive = NonNegativeRealScalar::try_new(1.0_f64).unwrap();
3041 assert!(zero < positive);
3042 }
3043
3044 #[test]
3045 fn serialize_deserialize() {
3046 let val = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
3047 let serialized = serde_json::to_string(&val).unwrap();
3048 let deserialized: NonNegativeRealScalar<f64> =
3049 serde_json::from_str(&serialized).unwrap();
3050 assert_eq!(val, deserialized);
3051 }
3052
3053 #[test]
3054 fn with_validated_type() {
3055 let inner = RealNative64StrictFinite::try_from_f64(0.0).unwrap();
3056 let val = NonNegativeRealScalar::try_new(inner).unwrap();
3057 assert_eq!(*val.as_raw_scalar_ref(), 0.0);
3058 }
3059
3060 #[test]
3061 #[cfg(debug_assertions)]
3062 #[should_panic(expected = "is not finite")]
3063 fn debug_panics_on_nan() {
3064 let _ = NonNegativeRealScalar::try_new(f64::NAN);
3065 }
3066
3067 #[test]
3068 fn try_new_positive() {
3069 // Test creating a NonNegativeRealScalar instance with positive values
3070 let v = NonNegativeRealScalar::try_new(2.5).unwrap();
3071 assert_eq!(*v.as_raw_scalar_ref(), 2.5);
3072
3073 // Test with very small positive value
3074 let v = NonNegativeRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
3075 assert_eq!(*v.as_raw_scalar_ref(), f64::MIN_POSITIVE);
3076
3077 // Test with large positive value
3078 let v = NonNegativeRealScalar::try_new(1e100).unwrap();
3079 assert_eq!(*v.as_raw_scalar_ref(), 1e100);
3080 }
3081
3082 #[test]
3083 fn try_new_zero() {
3084 // Test creating a NonNegativeRealScalar instance with zero (VALID case)
3085 let v = NonNegativeRealScalar::try_new(0.0).unwrap();
3086 assert_eq!(*v.as_raw_scalar_ref(), 0.0);
3087
3088 // This is the key difference from PositiveRealScalar:
3089 // Zero is VALID for NonNegativeRealScalar (x ≥ 0)
3090 // but INVALID for PositiveRealScalar (x > 0)
3091 }
3092
3093 #[test]
3094 fn try_new_invalid_negative() {
3095 // Test creating an invalid NonNegativeRealScalar instance with negative values
3096 let scalar = NonNegativeRealScalar::try_new(-0.1);
3097 assert!(matches!(
3098 scalar,
3099 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
3100 ));
3101
3102 // Test with negative value close to zero
3103 let scalar = NonNegativeRealScalar::try_new(-f64::EPSILON);
3104 assert!(matches!(
3105 scalar,
3106 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
3107 ));
3108
3109 // Test with large negative value
3110 let scalar = NonNegativeRealScalar::try_new(-100.0);
3111 assert!(matches!(
3112 scalar,
3113 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
3114 ));
3115 }
3116
3117 #[test]
3118 #[cfg(debug_assertions)]
3119 #[should_panic = "The input value inf is not finite!"]
3120 fn try_new_invalid_infinite() {
3121 // Test creating an invalid NonNegativeRealScalar instance with infinity
3122 let _scalar = NonNegativeRealScalar::try_new(f64::INFINITY);
3123 }
3124
3125 #[test]
3126 #[cfg(debug_assertions)]
3127 #[should_panic = "The input value -inf is not finite!"]
3128 fn try_new_invalid_negative_infinite() {
3129 // Test creating an invalid NonNegativeRealScalar instance with negative infinity
3130 let _scalar = NonNegativeRealScalar::try_new(f64::NEG_INFINITY);
3131 }
3132
3133 #[test]
3134 #[cfg(debug_assertions)]
3135 #[should_panic = "The input value NaN is not finite!"]
3136 fn try_new_invalid_nan() {
3137 // Test creating an invalid NonNegativeRealScalar instance with NaN
3138 let _scalar = NonNegativeRealScalar::try_new(f64::NAN);
3139 }
3140
3141 #[test]
3142 fn equality_operator() {
3143 // Test equality operator for NonNegativeRealScalar instances
3144 let a = NonNegativeRealScalar::try_new(1.5).unwrap();
3145 let b = NonNegativeRealScalar::try_new(1.5).unwrap();
3146 assert!(a == b);
3147
3148 let c = NonNegativeRealScalar::try_new(0.0).unwrap();
3149 let d = NonNegativeRealScalar::try_new(0.0).unwrap();
3150 assert!(c == d);
3151 }
3152
3153 #[test]
3154 fn inequality_operator() {
3155 // Test inequality operator for NonNegativeRealScalar instances
3156 let a = NonNegativeRealScalar::try_new(0.0).unwrap();
3157 let b = NonNegativeRealScalar::try_new(1.0).unwrap();
3158 assert!(a != b);
3159
3160 let c = NonNegativeRealScalar::try_new(0.5).unwrap();
3161 let d = NonNegativeRealScalar::try_new(1.5).unwrap();
3162 assert!(c != d);
3163 }
3164
3165 #[test]
3166 fn debug_trait() {
3167 // Test the Debug trait
3168 let scalar = NonNegativeRealScalar::try_new(2.7).unwrap();
3169 assert_eq!(format!("{:?}", scalar), "NonNegativeRealScalar(2.7)");
3170
3171 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
3172 assert_eq!(format!("{:?}", zero), "NonNegativeRealScalar(0.0)");
3173 }
3174
3175 #[test]
3176 fn clone_trait() {
3177 // Test the Clone trait
3178 let scalar = NonNegativeRealScalar::try_new(1.414).unwrap();
3179 let cloned_scalar = scalar.clone();
3180 assert_eq!(scalar, cloned_scalar);
3181 }
3182
3183 #[test]
3184 fn partial_eq_trait() {
3185 // Test the PartialEq trait
3186 let scalar1 = NonNegativeRealScalar::try_new(0.577).unwrap();
3187 let scalar2 = NonNegativeRealScalar::try_new(0.577).unwrap();
3188 assert_eq!(scalar1, scalar2);
3189
3190 // Test with zero
3191 let zero1 = NonNegativeRealScalar::try_new(0.0).unwrap();
3192 let zero2 = NonNegativeRealScalar::try_new(0.0).unwrap();
3193 assert_eq!(zero1, zero2);
3194 }
3195
3196 #[test]
3197 fn partial_ord_trait() {
3198 // Test the PartialOrd trait
3199 let scalar1 = NonNegativeRealScalar::try_new(0.0).unwrap();
3200 let scalar2 = NonNegativeRealScalar::try_new(1.0).unwrap();
3201 assert!(scalar1 < scalar2);
3202
3203 let scalar3 = NonNegativeRealScalar::try_new(3.0).unwrap();
3204 let scalar4 = NonNegativeRealScalar::try_new(3.0).unwrap();
3205 assert!(scalar3 <= scalar4);
3206 assert!(scalar4 >= scalar3);
3207
3208 // Test that zero is less than positive values
3209 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
3210 let positive = NonNegativeRealScalar::try_new(0.001).unwrap();
3211 assert!(zero < positive);
3212 }
3213
3214 #[test]
3215 fn as_ref_trait() {
3216 // Test the AsRef trait
3217 let scalar = NonNegativeRealScalar::try_new(2.0).unwrap();
3218 let inner: &f64 = scalar.as_raw_scalar_ref();
3219 assert_eq!(*inner, 2.0);
3220
3221 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
3222 let inner: &f64 = zero.as_raw_scalar_ref();
3223 assert_eq!(*inner, 0.0);
3224 }
3225
3226 #[test]
3227 fn into_inner() {
3228 // Test converting a NonNegativeRealScalar instance into its inner value
3229 let scalar = NonNegativeRealScalar::try_new(42.0).unwrap();
3230 let inner = scalar.into_inner();
3231 assert_eq!(inner, 42.0);
3232
3233 // Test with zero
3234 let zero = NonNegativeRealScalar::try_new(0.0).unwrap();
3235 let inner = zero.into_inner();
3236 assert_eq!(inner, 0.0);
3237
3238 // Test with very small positive value
3239 let scalar = NonNegativeRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
3240 let inner = scalar.into_inner();
3241 assert_eq!(inner, f64::MIN_POSITIVE);
3242 }
3243
3244 #[test]
3245 fn edge_cases() {
3246 // Test with zero (key edge case)
3247 let zero_scalar = NonNegativeRealScalar::try_new(0.0).unwrap();
3248 assert_eq!(*zero_scalar.as_raw_scalar_ref(), 0.0);
3249
3250 // Test with very small positive values
3251 let tiny_scalar = NonNegativeRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
3252 assert_eq!(*tiny_scalar.as_raw_scalar_ref(), f64::MIN_POSITIVE);
3253
3254 // Test with large positive values
3255 let large_scalar = NonNegativeRealScalar::try_new(f64::MAX).unwrap();
3256 assert_eq!(*large_scalar.as_raw_scalar_ref(), f64::MAX);
3257
3258 // Test with epsilon
3259 let epsilon_scalar = NonNegativeRealScalar::try_new(f64::EPSILON).unwrap();
3260 assert_eq!(*epsilon_scalar.as_raw_scalar_ref(), f64::EPSILON);
3261 }
3262
3263 #[test]
3264 fn generic_scalar_types() {
3265 // Test with RealNative64StrictFiniteInDebug type
3266 let scalar = NonNegativeRealScalar::try_new(
3267 RealNative64StrictFiniteInDebug::try_new(5.0).unwrap(),
3268 )
3269 .unwrap();
3270 assert_eq!(scalar.as_raw_scalar_ref(), &5.0);
3271
3272 // Test with zero (valid for NonNegativeRealScalar)
3273 let zero_value = RealNative64StrictFiniteInDebug::try_new(0.0).unwrap();
3274 let scalar_result = NonNegativeRealScalar::try_new(zero_value);
3275 assert!(scalar_result.is_ok()); // Should succeed!
3276
3277 // Test error case with generic type
3278 let negative_value = RealNative64StrictFiniteInDebug::try_new(-1.0).unwrap();
3279 let scalar_result = NonNegativeRealScalar::try_new(negative_value);
3280 assert!(matches!(
3281 scalar_result,
3282 Err(ErrorsNonNegativeRealScalar::NegativeValue { .. })
3283 ));
3284 }
3285
3286 #[test]
3287 fn mathematical_operations() {
3288 // Test that the wrapped values behave correctly in mathematical contexts
3289 let scalar1 = NonNegativeRealScalar::try_new(0.0).unwrap();
3290 let scalar2 = NonNegativeRealScalar::try_new(2.0).unwrap();
3291 let scalar3 = NonNegativeRealScalar::try_new(3.0).unwrap();
3292
3293 // Test comparison operations
3294 assert!(scalar1 < scalar2);
3295 assert!(scalar2 < scalar3);
3296 assert!(scalar3 > scalar1);
3297 assert_eq!(
3298 scalar1.partial_cmp(&scalar2),
3299 Some(std::cmp::Ordering::Less)
3300 );
3301 assert_eq!(
3302 scalar2.partial_cmp(&scalar1),
3303 Some(std::cmp::Ordering::Greater)
3304 );
3305 }
3306
3307 #[test]
3308 fn error_messages() {
3309 // Test that error messages are descriptive
3310 let negative_result = NonNegativeRealScalar::try_new(-1.0);
3311 match negative_result {
3312 Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) => {
3313 assert_eq!(value, -1.0);
3314 }
3315 _ => panic!("Expected NegativeValue error"),
3316 }
3317
3318 let large_negative_result = NonNegativeRealScalar::try_new(-100.5);
3319 match large_negative_result {
3320 Err(ErrorsNonNegativeRealScalar::NegativeValue { value, .. }) => {
3321 assert_eq!(value, -100.5);
3322 }
3323 _ => panic!("Expected NegativeValue error"),
3324 }
3325 }
3326
3327 #[test]
3328 fn distinction_from_positive_real_scalar() {
3329 // Critical test: demonstrate the key difference between
3330 // NonNegativeRealScalar (x ≥ 0) and PositiveRealScalar (x > 0)
3331
3332 // Zero is VALID for NonNegativeRealScalar
3333 let non_neg_zero = NonNegativeRealScalar::try_new(0.0);
3334 assert!(
3335 non_neg_zero.is_ok(),
3336 "Zero should be valid for NonNegativeRealScalar (x ≥ 0)"
3337 );
3338
3339 // Zero is INVALID for PositiveRealScalar
3340 let pos_zero = PositiveRealScalar::try_new(0.0);
3341 assert!(
3342 pos_zero.is_err(),
3343 "Zero should be invalid for PositiveRealScalar (x > 0)"
3344 );
3345 assert!(matches!(
3346 pos_zero,
3347 Err(ErrorsPositiveRealScalar::ZeroValue { .. })
3348 ));
3349
3350 // Positive values are valid for both
3351 let non_neg_positive = NonNegativeRealScalar::try_new(1.0);
3352 let pos_positive = PositiveRealScalar::try_new(1.0);
3353 assert!(non_neg_positive.is_ok());
3354 assert!(pos_positive.is_ok());
3355
3356 // Negative values are invalid for both
3357 let non_neg_negative = NonNegativeRealScalar::try_new(-1.0);
3358 let pos_negative = PositiveRealScalar::try_new(-1.0);
3359 assert!(non_neg_negative.is_err());
3360 assert!(pos_negative.is_err());
3361 }
3362
3363 #[test]
3364 fn use_case_distance() {
3365 // Real-world use case: computing distances (which can be zero)
3366 fn compute_distance(x1: f64, x2: f64) -> NonNegativeRealScalar<f64> {
3367 let diff = (x2 - x1).abs();
3368 NonNegativeRealScalar::try_new(diff).unwrap()
3369 }
3370
3371 // Distance between different points
3372 let dist1 = compute_distance(0.0, 5.0);
3373 assert_eq!(*dist1.as_raw_scalar_ref(), 5.0);
3374
3375 // Distance from a point to itself (zero distance is valid!)
3376 let dist2 = compute_distance(3.0, 3.0);
3377 assert_eq!(*dist2.as_raw_scalar_ref(), 0.0);
3378 }
3379
3380 #[test]
3381 fn use_case_absolute_value() {
3382 // Real-world use case: absolute values (which can be zero)
3383 fn safe_abs(x: f64) -> NonNegativeRealScalar<f64> {
3384 NonNegativeRealScalar::try_new(x.abs()).unwrap()
3385 }
3386
3387 assert_eq!(*safe_abs(-5.0).as_raw_scalar_ref(), 5.0);
3388 assert_eq!(*safe_abs(0.0).as_raw_scalar_ref(), 0.0); // Zero is valid!
3389 assert_eq!(*safe_abs(3.0).as_raw_scalar_ref(), 3.0);
3390 }
3391
3392 #[test]
3393 fn one_trait_identity() {
3394 // Test the One trait: one() should return NonNegativeRealScalar(1.0)
3395 let one = NonNegativeRealScalar::<f64>::one();
3396 assert_eq!(*one.as_raw_scalar_ref(), 1.0);
3397 }
3398
3399 #[test]
3400 fn one_trait_multiplicative_identity() {
3401 // Test that one acts as multiplicative identity
3402 let value = NonNegativeRealScalar::try_new(5.5_f64).unwrap();
3403 let one = NonNegativeRealScalar::<f64>::one();
3404
3405 let result = value.clone() * one.clone();
3406 assert_eq!(*result.as_raw_scalar_ref(), 5.5);
3407
3408 let result = one * value;
3409 assert_eq!(*result.as_raw_scalar_ref(), 5.5);
3410 }
3411
3412 #[test]
3413 fn one_trait_with_generic_types() {
3414 // Test One trait with validated types
3415 let one = NonNegativeRealScalar::<RealNative64StrictFiniteInDebug>::one();
3416 assert_eq!(*one.as_raw_scalar_ref(), 1.0);
3417 }
3418
3419 #[test]
3420 fn pow_integer_exponents() {
3421 // Test Pow trait with different integer exponent types
3422 let base = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
3423
3424 // Test with u32
3425 let result = base.clone().pow(3u32);
3426 assert_eq!(*result.as_raw_scalar_ref(), 8.0);
3427
3428 // Test with u8
3429 let result = base.clone().pow(2u8);
3430 assert_eq!(*result.as_raw_scalar_ref(), 4.0);
3431
3432 // Test with i32 (positive)
3433 let result = base.clone().pow(2i32);
3434 assert_eq!(*result.as_raw_scalar_ref(), 4.0);
3435 }
3436
3437 #[test]
3438 fn pow_zero_exponent() {
3439 // Test that any number raised to power 0 equals 1
3440 let base = NonNegativeRealScalar::try_new(5.5_f64).unwrap();
3441
3442 let result = base.clone().pow(0u32);
3443 assert_eq!(*result.as_raw_scalar_ref(), 1.0);
3444
3445 // Even 0 raised to power 0 should be 1
3446 let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
3447 let result = zero.pow(0u32);
3448 assert_eq!(*result.as_raw_scalar_ref(), 1.0);
3449 }
3450
3451 #[test]
3452 fn pow_one_exponent() {
3453 // Test that any number raised to power 1 equals itself
3454 let base = NonNegativeRealScalar::try_new(3.7_f64).unwrap();
3455
3456 let result = base.clone().pow(1u32);
3457 assert_eq!(*result.as_raw_scalar_ref(), 3.7);
3458 }
3459
3460 #[test]
3461 fn pow_preserves_non_negativity() {
3462 // Test that raising non-negative to any integer power preserves non-negativity
3463 let base = NonNegativeRealScalar::try_new(2.5_f64).unwrap();
3464
3465 let result = base.clone().pow(5u32);
3466 assert!(*result.as_raw_scalar_ref() >= 0.0);
3467 assert_eq!(*result.as_raw_scalar_ref(), 2.5_f64.powi(5));
3468
3469 // Even 0 raised to positive power stays non-negative
3470 let zero = NonNegativeRealScalar::try_new(0.0_f64).unwrap();
3471 let result = zero.pow(10u32);
3472 assert_eq!(*result.as_raw_scalar_ref(), 0.0);
3473 }
3474
3475 #[test]
3476 fn pow_with_try_pow() {
3477 // Test the try_pow method
3478 let base = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
3479
3480 let result = base.clone().try_pow(2u32);
3481 assert!(result.is_ok());
3482 assert_eq!(*result.unwrap().as_raw_scalar_ref(), 9.0);
3483
3484 // Test with different exponent types
3485 let result = base.clone().try_pow(3i32);
3486 assert!(result.is_ok());
3487 assert_eq!(*result.unwrap().as_raw_scalar_ref(), 27.0);
3488 }
3489
3490 #[test]
3491 fn pow_large_exponents() {
3492 // Test with large exponents
3493 let base = NonNegativeRealScalar::try_new(1.1_f64).unwrap();
3494 let result = base.pow(10u32);
3495 let expected = 1.1_f64.powi(10);
3496 assert!((result.as_raw_scalar_ref() - expected).abs() < 1e-10);
3497 }
3498
3499 #[test]
3500 fn pow_small_base() {
3501 // Test with small base values
3502 let base = NonNegativeRealScalar::try_new(0.5_f64).unwrap();
3503
3504 let result = base.clone().pow(2u32);
3505 assert_eq!(*result.as_raw_scalar_ref(), 0.25);
3506
3507 let result = base.clone().pow(3u32);
3508 assert_eq!(*result.as_raw_scalar_ref(), 0.125);
3509 }
3510
3511 #[test]
3512 fn pow_with_generic_types() {
3513 // Test Pow with validated types
3514 let base_val = RealNative64StrictFiniteInDebug::try_new(2.0).unwrap();
3515 let base = NonNegativeRealScalar::try_new(base_val).unwrap();
3516
3517 let result = base.pow(3u32);
3518 assert_eq!(*result.as_raw_scalar_ref(), 8.0);
3519 }
3520
3521 #[test]
3522 fn pow_chain_operations() {
3523 // Test chaining pow operations
3524 let base = NonNegativeRealScalar::try_new(2.0_f64).unwrap();
3525
3526 // (2^3)^2 = 2^6 = 64
3527 let result = base.clone().pow(3u32).pow(2u32);
3528 assert_eq!(*result.as_raw_scalar_ref(), 64.0);
3529 }
3530 }
3531
3532 mod distinction_tests {
3533 use super::*;
3534
3535 #[test]
3536 fn positive_vs_non_negative_zero() {
3537 // This is the KEY difference between the two types
3538
3539 // Zero is INVALID for PositiveRealScalar (x > 0)
3540 assert!(PositiveRealScalar::try_new(0.0_f64).is_err());
3541
3542 // Zero is VALID for NonNegativeRealScalar (x ≥ 0)
3543 assert!(NonNegativeRealScalar::try_new(0.0_f64).is_ok());
3544 }
3545
3546 #[test]
3547 fn both_accept_positive() {
3548 let positive = 1.0_f64;
3549 assert!(PositiveRealScalar::try_new(positive).is_ok());
3550 assert!(NonNegativeRealScalar::try_new(positive).is_ok());
3551 }
3552
3553 #[test]
3554 fn both_reject_negative() {
3555 let negative = -1.0_f64;
3556 assert!(PositiveRealScalar::try_new(negative).is_err());
3557 assert!(NonNegativeRealScalar::try_new(negative).is_err());
3558 }
3559 }
3560
3561 mod conversion_tests {
3562 use super::*;
3563 use crate::backends::native::validated::RealNative64StrictFiniteInDebug;
3564
3565 #[test]
3566 fn from_positive_to_non_negative() {
3567 // Test basic conversion from PositiveRealScalar to NonNegativeRealScalar
3568 let positive = PositiveRealScalar::try_new(5.0_f64).unwrap();
3569 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3570
3571 assert_eq!(*non_negative.as_raw_scalar_ref(), 5.0);
3572 }
3573
3574 #[test]
3575 fn from_positive_to_non_negative_min_positive() {
3576 // Test conversion with f64::MIN_POSITIVE (smallest positive value)
3577 let positive = PositiveRealScalar::try_new(f64::MIN_POSITIVE).unwrap();
3578 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3579
3580 assert_eq!(*non_negative.as_raw_scalar_ref(), f64::MIN_POSITIVE);
3581 }
3582
3583 #[test]
3584 fn from_positive_to_non_negative_large_value() {
3585 // Test conversion with large value
3586 let positive = PositiveRealScalar::try_new(f64::MAX).unwrap();
3587 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3588
3589 assert_eq!(*non_negative.as_raw_scalar_ref(), f64::MAX);
3590 }
3591
3592 #[test]
3593 fn from_positive_to_non_negative_one() {
3594 // Test conversion with the multiplicative identity
3595 let positive = PositiveRealScalar::try_new(1.0_f64).unwrap();
3596 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3597
3598 assert_eq!(*non_negative.as_raw_scalar_ref(), 1.0);
3599 }
3600
3601 #[test]
3602 fn from_positive_to_non_negative_preserves_value() {
3603 // Test that the conversion preserves the exact value
3604 let test_values = vec![0.5, 1.0, 2.5, 10.0, 99.9, 1e10, 1e-10];
3605
3606 for value in test_values {
3607 let positive = PositiveRealScalar::try_new(value).unwrap();
3608 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3609
3610 assert_eq!(*non_negative.as_raw_scalar_ref(), value);
3611 }
3612 }
3613
3614 #[test]
3615 fn from_positive_to_non_negative_with_generic_type() {
3616 // Test conversion with validated types
3617 let inner = RealNative64StrictFiniteInDebug::try_new(5.0).unwrap();
3618 let positive = PositiveRealScalar::try_new(inner).unwrap();
3619 let non_negative: NonNegativeRealScalar<RealNative64StrictFiniteInDebug> =
3620 positive.into();
3621
3622 assert_eq!(*non_negative.as_raw_scalar_ref(), 5.0);
3623 }
3624
3625 #[test]
3626 fn from_positive_consumed_by_conversion() {
3627 // Test that the conversion consumes the PositiveRealScalar
3628 let positive = PositiveRealScalar::try_new(5.0_f64).unwrap();
3629 let _non_negative: NonNegativeRealScalar<f64> = positive.into();
3630
3631 // If we try to use `positive` here, it would be a compile error
3632 // because it was consumed by the conversion.
3633 // This test just documents the behavior.
3634 }
3635
3636 #[test]
3637 fn from_positive_creates_valid_non_negative() {
3638 // Test that the converted value is indeed non-negative
3639 let positive = PositiveRealScalar::try_new(3.5_f64).unwrap();
3640 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3641
3642 // The converted value should pass all non-negative validation
3643 assert!(*non_negative.as_raw_scalar_ref() >= 0.0);
3644 assert!(!non_negative.is_zero()); // Since it came from a positive value
3645 }
3646
3647 #[test]
3648 fn from_positive_mathematical_properties() {
3649 // Test that mathematical properties are preserved after conversion
3650 let positive_a = PositiveRealScalar::try_new(2.0_f64).unwrap();
3651 let positive_b = PositiveRealScalar::try_new(3.0_f64).unwrap();
3652
3653 let non_neg_a: NonNegativeRealScalar<f64> = positive_a.into();
3654 let non_neg_b: NonNegativeRealScalar<f64> = positive_b.into();
3655
3656 // Arithmetic should work the same way after conversion
3657 let sum = non_neg_a + non_neg_b;
3658 assert_eq!(*sum.as_raw_scalar_ref(), 5.0);
3659 }
3660
3661 #[test]
3662 fn from_positive_with_chained_operations() {
3663 // Test conversion followed by operations
3664 let positive = PositiveRealScalar::try_new(2.0_f64).unwrap();
3665 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3666
3667 // Test that we can use trait methods on the converted value
3668 let one = NonNegativeRealScalar::<f64>::one();
3669 let result = non_negative * one;
3670
3671 assert_eq!(*result.as_raw_scalar_ref(), 2.0);
3672 }
3673
3674 #[test]
3675 fn from_positive_comparison_after_conversion() {
3676 // Test comparison operations work correctly after conversion
3677 let positive_small = PositiveRealScalar::try_new(1.0_f64).unwrap();
3678 let positive_large = PositiveRealScalar::try_new(3.0_f64).unwrap();
3679
3680 let non_neg_small: NonNegativeRealScalar<f64> = positive_small.into();
3681 let non_neg_large: NonNegativeRealScalar<f64> = positive_large.into();
3682
3683 assert!(non_neg_small < non_neg_large);
3684 assert!(non_neg_large > non_neg_small);
3685 }
3686
3687 #[test]
3688 fn from_positive_pow_after_conversion() {
3689 // Test that Pow trait works correctly after conversion
3690 let positive = PositiveRealScalar::try_new(2.0_f64).unwrap();
3691 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3692
3693 let squared = non_negative.pow(2u32);
3694 assert_eq!(*squared.as_raw_scalar_ref(), 4.0);
3695 }
3696
3697 #[test]
3698 fn from_positive_sqrt_after_conversion() {
3699 // Test that sqrt works correctly after conversion
3700 let positive = PositiveRealScalar::try_new(4.0_f64).unwrap();
3701 let non_negative: NonNegativeRealScalar<f64> = positive.into();
3702
3703 let sqrt = non_negative.sqrt();
3704 assert_eq!(*sqrt.as_raw_scalar_ref(), 2.0);
3705 }
3706 }
3707
3708 mod cross_type_arithmetic_tests {
3709 use super::*;
3710 use crate::backends::native::validated::RealNative64StrictFiniteInDebug;
3711
3712 // --- Pos + NonNeg → Pos ---
3713
3714 #[test]
3715 fn pos_add_non_neg_value() {
3716 // PositiveRealScalar + NonNegativeRealScalar (by value) → PositiveRealScalar
3717 let pos = PositiveRealScalar::try_new(2.0_f64).unwrap();
3718 let non_neg = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
3719
3720 let result: PositiveRealScalar<f64> = pos + non_neg;
3721 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3722 }
3723
3724 #[test]
3725 fn pos_add_non_neg_ref() {
3726 // PositiveRealScalar + &NonNegativeRealScalar (by reference) → PositiveRealScalar
3727 let pos = PositiveRealScalar::try_new(2.0_f64).unwrap();
3728 let non_neg = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
3729
3730 let result: PositiveRealScalar<f64> = pos + &non_neg;
3731 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3732 // non_neg is still usable here (not consumed)
3733 assert_eq!(*non_neg.as_raw_scalar_ref(), 3.0);
3734 }
3735
3736 #[test]
3737 fn pos_add_non_neg_zero_preserves_positivity() {
3738 // Adding zero (NonNeg) to a positive value should remain positive
3739 let pos = PositiveRealScalar::try_new(5.0_f64).unwrap();
3740 let zero = NonNegativeRealScalar::<f64>::zero();
3741
3742 let result: PositiveRealScalar<f64> = pos + zero;
3743 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3744 assert!(*result.as_raw_scalar_ref() > 0.0);
3745 }
3746
3747 #[test]
3748 fn pos_add_non_neg_zero_ref_preserves_positivity() {
3749 // Adding zero (NonNeg ref) to a positive value should remain positive
3750 let pos = PositiveRealScalar::try_new(5.0_f64).unwrap();
3751 let zero = NonNegativeRealScalar::<f64>::zero();
3752
3753 let result: PositiveRealScalar<f64> = pos + &zero;
3754 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3755 }
3756
3757 // --- NonNeg + Pos → Pos ---
3758
3759 #[test]
3760 fn non_neg_add_pos_value() {
3761 // NonNegativeRealScalar + PositiveRealScalar (by value) → PositiveRealScalar
3762 let non_neg = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
3763 let pos = PositiveRealScalar::try_new(2.0_f64).unwrap();
3764
3765 let result: PositiveRealScalar<f64> = non_neg + pos;
3766 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3767 }
3768
3769 #[test]
3770 fn non_neg_add_pos_ref() {
3771 // NonNegativeRealScalar + &PositiveRealScalar (by reference) → PositiveRealScalar
3772 let non_neg = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
3773 let pos = PositiveRealScalar::try_new(2.0_f64).unwrap();
3774
3775 let result: PositiveRealScalar<f64> = non_neg + &pos;
3776 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3777 // pos is still usable here (not consumed)
3778 assert_eq!(*pos.as_raw_scalar_ref(), 2.0);
3779 }
3780
3781 #[test]
3782 fn zero_add_pos_value() {
3783 // Zero (NonNeg) + PositiveRealScalar → PositiveRealScalar
3784 // Key test: zero + positive should give the positive value
3785 let zero = NonNegativeRealScalar::<f64>::zero();
3786 let pos = PositiveRealScalar::try_new(5.0_f64).unwrap();
3787
3788 let result: PositiveRealScalar<f64> = zero + pos;
3789 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3790 assert!(*result.as_raw_scalar_ref() > 0.0);
3791 }
3792
3793 #[test]
3794 fn zero_add_pos_ref() {
3795 // Zero (NonNeg) + &PositiveRealScalar → PositiveRealScalar
3796 let zero = NonNegativeRealScalar::<f64>::zero();
3797 let pos = PositiveRealScalar::try_new(5.0_f64).unwrap();
3798
3799 let result: PositiveRealScalar<f64> = zero + &pos;
3800 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3801 }
3802
3803 // --- Commutativity ---
3804
3805 #[test]
3806 fn commutativity() {
3807 // Pos + NonNeg and NonNeg + Pos should give the same numeric value
3808 let pos = PositiveRealScalar::try_new(2.0_f64).unwrap();
3809 let non_neg = NonNegativeRealScalar::try_new(3.0_f64).unwrap();
3810
3811 let left: PositiveRealScalar<f64> = pos.clone() + non_neg.clone();
3812 let right: PositiveRealScalar<f64> = non_neg + pos;
3813
3814 assert_eq!(*left.as_raw_scalar_ref(), *right.as_raw_scalar_ref());
3815 }
3816
3817 // --- Generic types ---
3818
3819 #[test]
3820 fn cross_type_add_with_generic_type() {
3821 let pos_inner = RealNative64StrictFiniteInDebug::try_new(2.0).unwrap();
3822 let non_neg_inner = RealNative64StrictFiniteInDebug::try_new(3.0).unwrap();
3823
3824 let pos = PositiveRealScalar::try_new(pos_inner).unwrap();
3825 let non_neg = NonNegativeRealScalar::try_new(non_neg_inner).unwrap();
3826
3827 let result: PositiveRealScalar<RealNative64StrictFiniteInDebug> = pos + non_neg;
3828 assert_eq!(*result.as_raw_scalar_ref(), 5.0);
3829 }
3830 }
3831}