num_valid/validation.rs
1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! # Numerical Value Validation Module
4//!
5//! This module provides a framework for validating numerical scalar values,
6//! particularly floating-point and complex numbers, including those with arbitrary precision
7//! using the [`rug`](https://crates.io/crates/rug) library (when the "rug" feature is enabled).
8//!
9//! It defines:
10//! - **Error Types**: Such as [`ErrorsValidationRawReal`], [`ErrorsValidationRawComplex`],
11//! and [`ErrorsTryFromf64`].
12//! These errors detail various validation failures like non-finite values,
13//! subnormal numbers (for `f64` and [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html)), and precision mismatches for `rug` types.
14//! - **Validation Policies**: Implemented via the [`ValidationPolicy`] trait from the
15//! [`try_create`](https://crates.io/crates/try_create) crate.
16//! The primary policy provided is [`StrictFinitePolicy`], which ensures that
17//! numbers are finite (not NaN or Infinity) and, for `f64` and [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html),
18//! not subnormal.
19//! - **Floating-Point Checks Trait**: The [`FpChecks`] trait, defined within this module,
20//! provides common classification methods like `is_finite`, `is_nan`, `is_infinite`,
21//! and `is_normal`. This trait is implemented for standard and `rug`-based scalar types.
22//! - **Enhanced Type Safety for HashMap Keys**: The [`GuaranteesFiniteValues`] marker trait enables
23//! full equality ([`Eq`]) and hashing ([`Hash`]) for validated types when their
24//! validation policy guarantees finite values. This allows validated types to be used
25//! as keys in [`HashMap`](std::collections::HashMap) and other hash-based collections.
26//! - **Implementations**: Validation logic for [`StrictFinitePolicy`] and implementations of
27//! [`FpChecks`] are provided for [`f64`] and [`num::Complex<f64>`]. When the "rug" feature is enabled,
28//! support extends to [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html), [`rug::Complex`](https://docs.rs/rug/latest/rug/struct.Complex.html), and indirectly to the wrapper types
29//! `RealRugStrictFinite` and `ComplexRugStrictFinite` (which utilize these underlying `rug` types).
30//!
31//! The goal of this module is to ensure numerical stability and correctness by allowing
32//! functions to easily validate their inputs and outputs according to defined policies,
33//! and to provide a common interface for basic floating-point characteristic checks.
34//!
35//! ## Enhanced Type Safety with [`GuaranteesFiniteValues`]
36//!
37//! One of the key innovations of this validation framework is the [`GuaranteesFiniteValues`]
38//! marker trait. This trait enables compile-time verification that a validation policy
39//! ensures all validated values are finite, which unlocks additional type system guarantees:
40//!
41//! - **Full Equality**: These types implement [`Eq`], making equality comparisons well-defined
42//! and symmetric.
43//! - **Hashing Support**: With both [`Eq`] and [`Hash`] implemented, these types can be used
44//! as keys in hash-based collections like [`HashMap`](std::collections::HashMap) and [`HashSet`](std::collections::HashSet).
45//!
46//! **Note on Ordering**: The library intentionally does NOT implement [`Ord`]
47//! even for finite-guaranteed types, as our custom [`Max`](crate::functions::Max)
48//! and [`Min`](crate::functions::Min) traits provide more efficient reference-based
49//! operations compared to [`Ord`]'s value-based interface.
50//!
51//! Currently, [`StrictFinitePolicy`] and [`DebugValidationPolicy<StrictFinitePolicy>`]
52//! implement this marker trait.
53//!
54//!
55//! ## **Usage Examples**
56//!
57//! ### Basic Validation
58//!
59//! ```rust
60//! use num_valid::validation::{StrictFinitePolicy, ErrorsValidationRawReal};
61//! use try_create::ValidationPolicy; // For the validate() method
62//!
63//! // Valid finite number
64//! assert!(StrictFinitePolicy::<f64, 53>::validate(1.0).is_ok());
65//!
66//! // Invalid: NaN
67//! let result = StrictFinitePolicy::<f64, 53>::validate(f64::NAN);
68//! assert!(matches!(result, Err(ErrorsValidationRawReal::IsNaN { .. })));
69//!
70//! // Invalid: Infinity
71//! let result = StrictFinitePolicy::<f64, 53>::validate(f64::INFINITY);
72//! assert!(matches!(result, Err(ErrorsValidationRawReal::IsPosInfinity { .. })));
73//!
74//! // Invalid: Subnormal
75//! let subnormal_val = f64::from_bits(1); // Smallest positive subnormal f64
76//! let result = StrictFinitePolicy::<f64, 53>::validate(subnormal_val);
77//! assert!(matches!(result, Err(ErrorsValidationRawReal::IsSubnormal { .. })));
78//! ```
79//!
80//! ### Using [`FpChecks`]
81//!
82//! ```rust
83//! use num_valid::validation::FpChecks;
84//! use num::Complex;
85//!
86//! let val_f64 = 1.0f64;
87//! let nan_f64 = f64::NAN;
88//! let inf_f64 = f64::INFINITY;
89//!
90//! assert!(val_f64.is_finite());
91//! assert!(val_f64.is_normal());
92//! assert!(nan_f64.is_nan());
93//! assert!(!nan_f64.is_finite());
94//! assert!(inf_f64.is_infinite());
95//!
96//! let val_complex = Complex::new(1.0, 2.0);
97//! let nan_complex_real = Complex::new(f64::NAN, 2.0);
98//! let inf_complex_imag = Complex::new(1.0, f64::NEG_INFINITY);
99//!
100//! assert!(val_complex.is_finite());
101//! assert!(val_complex.is_normal());
102//! assert!(nan_complex_real.is_nan());
103//! assert!(!nan_complex_real.is_finite());
104//! assert!(inf_complex_imag.is_infinite());
105//! assert!(!inf_complex_imag.is_finite());
106//! assert!(!inf_complex_imag.is_nan());
107//! ```
108//!
109//! ### HashMap Usage with Finite Guarantees
110//! ```rust
111//! use num_valid::kernels::native64_validated::RealNative64StrictFinite;
112//! use std::collections::HashMap;
113//! use try_create::TryNew;
114//!
115//! let mut scores = HashMap::new();
116//! let player1 = RealNative64StrictFinite::try_new(1.5).unwrap();
117//! let player2 = RealNative64StrictFinite::try_new(2.0).unwrap();
118//!
119//! scores.insert(player1, 100); // Works because Eq + Hash are implemented
120//! scores.insert(player2, 95);
121//!
122//! assert_eq!(scores.len(), 2);
123//! ```
124//!
125//! ### Conditional Debug Validation
126//! ```rust
127//! use num_valid::validation::{DebugValidationPolicy, Native64RawRealStrictFinitePolicy};
128//! use try_create::ValidationPolicy;
129//!
130//! type ConditionalPolicy = DebugValidationPolicy<Native64RawRealStrictFinitePolicy>;
131//!
132//! let value = 42.0;
133//! // Validates only in debug builds, no-op in release
134//! assert!(ConditionalPolicy::validate(value).is_ok());
135//! ```
136
137use crate::{
138 NumKernel, RealValidated,
139 kernels::{RawComplexTrait, RawRealTrait, RawScalarTrait},
140};
141use duplicate::duplicate_item;
142use num::{Complex, Integer};
143use std::{
144 backtrace::Backtrace,
145 fmt::Display,
146 hash::{Hash, Hasher},
147 marker::PhantomData,
148 num::FpCategory,
149};
150use thiserror::Error;
151use try_create::ValidationPolicy;
152
153/// A marker for policies that apply to real types.
154///
155/// This trait refines the generic [`ValidationPolicy`] for types that implement
156/// [`RawRealTrait`]. It adds two key constraints:
157///
158/// 1. It associates a `const PRECISION` with the policy, essential for backends
159/// like `rug`.
160/// 2. It enforces that the policy's `Error` type must be the same as the canonical
161/// `ValidationErrors` type defined on the raw scalar itself. This architectural
162/// choice ensures consistent error handling throughout the library.
163pub trait ValidationPolicyReal:
164 ValidationPolicy<
165 Value: RawRealTrait,
166 Error = <<Self as ValidationPolicy>::Value as RawScalarTrait>::ValidationErrors,
167 >
168{
169 const PRECISION: u32;
170}
171
172/// A marker for policies that apply to complex types.
173///
174/// This trait refines the generic [`ValidationPolicy`] for types that implement
175/// [`RawComplexTrait`]. It adds two key constraints:
176///
177/// 1. It associates a `const PRECISION` with the policy, essential for backends
178/// like `rug`.
179/// 2. It enforces that the policy's `Error` type must be the same as the canonical
180/// `ValidationErrors` type defined on the raw scalar itself. This architectural
181/// choice ensures consistent error handling throughout the library.
182pub trait ValidationPolicyComplex:
183 ValidationPolicy<
184 Value: RawComplexTrait,
185 Error = <<Self as ValidationPolicy>::Value as RawScalarTrait>::ValidationErrors,
186 >
187{
188 const PRECISION: u32;
189}
190
191//--------------------------------------------------------------------------------------------------
192// Error types
193//--------------------------------------------------------------------------------------------------
194
195//--------------------------------------------
196// Errors for raw real number validation
197//--------------------------------------------
198/// Errors that can occur during the validation of a raw real number.
199///
200/// This enum is generic over `RawReal`, which is typically `f64` or [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html).
201/// It covers common validation failures like non-finite values, subnormality, infinity, and precision mismatch.
202#[derive(Debug, Error)]
203pub enum ErrorsValidationRawReal<RawReal: std::fmt::Debug + Display + Clone> {
204 /// The value is subnormal.
205 #[error("Value is subnormal: {value}")]
206 IsSubnormal {
207 /// The subnormal value that failed validation.
208 value: RawReal,
209
210 /// A captured backtrace for debugging purposes.
211 backtrace: Backtrace,
212 },
213
214 /// The value is NaN (Not a Number).
215 #[error("Value is NaN: {value}")]
216 IsNaN {
217 /// The NaN value that failed validation.
218 value: RawReal,
219
220 /// A captured backtrace for debugging purposes.
221 backtrace: Backtrace,
222 },
223
224 /// The value is positive infinity.
225 #[error("Value is positive infinity")]
226 IsPosInfinity {
227 /// The positive infinity value that failed validation.
228 backtrace: Backtrace,
229 },
230
231 /// The value is negative infinity.
232 #[error("Value is negative infinity")]
233 IsNegInfinity {
234 /// The negative infinity value that failed validation.
235 backtrace: Backtrace,
236 },
237
238 /// The precision of the input does not match the requested precision.
239 #[error(
240 "precision mismatch: input real has precision {actual_precision}, \
241 but the requested precision is {requested_precision}"
242 )]
243 PrecisionMismatch {
244 /// The input value.
245 input_value: RawReal,
246
247 /// The precision of the input value.
248 actual_precision: u32,
249
250 /// The requested precision.
251 requested_precision: u32,
252
253 /// A captured backtrace for debugging purposes.
254 backtrace: Backtrace,
255 },
256 /*
257 // The following variants are placeholders for potential future validation policies
258 // and are not currently used by StrictFinitePolicy.
259
260 #[error("Value is not strictly positive: {value}")]
261 NotPositive {
262 value: RawReal,
263 backtrace: Backtrace,
264 },
265
266 #[error("Value is negative: {value}")]
267 Negative {
268 value: RawReal,
269 backtrace: Backtrace,
270 },
271
272 #[error("Value is zero: {value}")]
273 IsZero {
274 value: RawReal,
275 backtrace: Backtrace,
276 },
277
278 #[error("Value {value} is outside the allowed range [{min}, {max}]")]
279 OutOfRange {
280 value: RawReal,
281 min: RawReal,
282 max: RawReal,
283 backtrace: Backtrace,
284 },
285 */
286}
287
288//--------------------------------------------
289// Errors for complex number validation
290//--------------------------------------------
291/// Errors that can occur during the validation of a complex number.
292///
293/// This enum is generic over `ErrorValidationReal`, which is the error type returned
294/// by validating the real and imaginary parts of the complex number (e.g.,
295/// [`ErrorsValidationRawReal<f64>`](ErrorsValidationRawReal)).
296///
297/// It indicates whether the real part, the imaginary part, or both parts failed validation.
298#[derive(Debug, Error)]
299pub enum ErrorsValidationRawComplex<ErrorValidationReal: std::error::Error> {
300 /// The real part of the complex number failed validation.
301 #[error("Real part validation error: {source}")]
302 InvalidRealPart {
303 /// The underlying validation error for the real part.
304 #[source]
305 #[backtrace]
306 source: ErrorValidationReal,
307 },
308
309 /// The imaginary part of the complex number failed validation.
310 #[error("Imaginary part validation error: {source}")]
311 InvalidImaginaryPart {
312 /// The underlying validation error for the imaginary part.
313 #[source]
314 #[backtrace]
315 source: ErrorValidationReal,
316 },
317
318 /// Both the real and imaginary parts of the complex number failed validation.
319 #[error("Both real and imaginary parts are invalid: real={real_error}, imag={imag_error}")]
320 InvalidBothParts {
321 /// The validation error for the real part.
322 real_error: ErrorValidationReal,
323
324 /// The validation error for the imaginary part.
325 imag_error: ErrorValidationReal,
326 },
327}
328
329//-------------------------------------------------------------
330/// Errors that can occur when trying to convert an `f64` to another real-type scalar of type `RawReal`.
331///
332/// This enum distinguishes between:
333/// 1. Failures due to the `f64` not being exactly representable at the target `PRECISION`
334/// of the `RawReal` type.
335/// 2. General validation failures of the output `RawReal` (e.g., NaN, Infinity, subnormal).
336#[derive(Debug, Error)]
337pub enum ErrorsTryFromf64<RawReal: RawRealTrait> {
338 /// The input `f64` value is not representable exactly at the target `precision` using the output `RawReal` type.
339 #[error(
340 "the input f64 value ({value_in:?}) cannot be exactly represented with the specific precision ({precision:?}). Try to increase the precision."
341 )]
342 NonRepresentableExactly {
343 /// The `f64` value that could not be exactly represented.
344 value_in: f64,
345
346 /// The input `value_in` after the conversion to the type `RawReal` with the requested `precision`.
347 value_converted_from_f64: RawReal,
348
349 /// The precision that was requested.
350 precision: u32,
351
352 /// A captured backtrace for debugging purposes.
353 backtrace: Backtrace,
354 },
355
356 /// The output value failed validation.
357 #[error("the output value is invalid!")]
358 Output {
359 /// The underlying validation error for the `output` value.
360 #[from]
361 source: ErrorsValidationRawReal<RawReal>,
362 },
363}
364//-------------------------------------------------------------
365
366//-------------------------------------------------------------
367#[derive(Debug, Error)]
368pub enum ErrorsRawRealToInteger<RawReal: RawRealTrait, IntType: Integer> {
369 /// The `RawReal` value is not finite (i.e., it is NaN or Infinity).
370 #[error("Value is not finite: {value}")]
371 NotFinite {
372 /// The non-finite `RawReal` value that caused the error.
373 value: RawReal,
374
375 /// A captured backtrace for debugging purposes.
376 backtrace: Backtrace,
377 },
378
379 /// The `RawReal` value is not an integer (i.e., it has a fractional part).
380 #[error("Value is not an integer: {value}")]
381 NotAnInteger {
382 /// The `RawReal` value that is not an integer.
383 value: RawReal,
384
385 /// A captured backtrace for debugging purposes.
386 backtrace: Backtrace,
387 },
388
389 /// The `RawReal` value is outside the range representable by the target integer type `IntType`.
390 #[error("Value {value} is out of range [{min}, {max}] for target integer type")]
391 OutOfRange {
392 /// The `RawReal` value that is out of range.
393 value: RawReal,
394
395 /// The minimum representable value of the target integer type `IntType`.
396 min: IntType,
397
398 /// The maximum representable value of the target integer type `IntType`.
399 max: IntType,
400
401 /// A captured backtrace for debugging purposes.
402 backtrace: Backtrace,
403 },
404}
405//-------------------------------------------------------------
406
407/// A validation policy that checks for strict finiteness.
408///
409/// For floating-point types (`ScalarType = `[`f64`], `ScalarType = `[`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html)), this policy ensures that the value is:
410/// - Not NaN (Not a Number).
411/// - Not positive or negative Infinity.
412/// - Not subnormal (for `f64`).
413/// While [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html) maintains its specified precision rather than having distinct subnormal
414/// representation in the IEEE 754 sense, this policy will also reject [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html) values that are classified as
415/// [`FpCategory::Subnormal`] (e.g., results of underflow that are tiny but not exactly zero).
416///
417/// For complex types (`ScalarType = `[`Complex<f64>`], `ScalarType = `[`rug::Complex`](https://docs.rs/rug/latest/rug/struct.Complex.html)), this policy applies
418/// the strict finiteness check to both the real and imaginary parts.
419///
420/// This struct is a Zero-Sized Type (ZST) and uses [`PhantomData`] to associate
421/// with the `ScalarType` it validates.
422pub struct StrictFinitePolicy<ScalarType: Sized, const PRECISION: u32>(PhantomData<ScalarType>);
423
424/// A type alias for a validation policy that enforces strict finiteness for the raw [`f64`] type.
425///
426/// This is a convenient alias for [`StrictFinitePolicy<f64, 53>`], configured specifically
427/// for Rust's native 64-bit floating-point number. It is used to validate that a given [`f64`]
428/// value is suitable for use within the library's validated types.
429///
430/// This policy ensures that a `f64` value is:
431/// - Not `NaN` (Not a Number).
432/// - Not positive or negative `Infinity`.
433/// - Not a subnormal number.
434///
435/// It is a fundamental building block for the native `f64` kernel, providing the validation
436/// logic for [`RealNative64StrictFinite`](crate::kernels::native64_validated::RealNative64StrictFinite).
437///
438/// # Example
439///
440/// ```rust
441/// use num_valid::validation::{Native64RawRealStrictFinitePolicy, ErrorsValidationRawReal};
442/// use try_create::ValidationPolicy;
443///
444/// // A valid finite number passes validation.
445/// assert!(Native64RawRealStrictFinitePolicy::validate(1.0).is_ok());
446///
447/// // NaN fails validation.
448/// let result_nan = Native64RawRealStrictFinitePolicy::validate(f64::NAN);
449/// assert!(matches!(result_nan, Err(ErrorsValidationRawReal::IsNaN { .. })));
450///
451/// // Infinity fails validation.
452/// let result_inf = Native64RawRealStrictFinitePolicy::validate(f64::INFINITY);
453/// assert!(matches!(result_inf, Err(ErrorsValidationRawReal::IsPosInfinity { .. })));
454/// ```
455pub type Native64RawRealStrictFinitePolicy = StrictFinitePolicy<f64, 53>;
456
457/// A type alias for a validation policy that enforces strict finiteness for the raw [`num::Complex<f64>`] type.
458///
459/// This is a convenient alias for [`StrictFinitePolicy<Complex<f64>, 53>`], configured for
460/// Rust's native 64-bit complex numbers. It is used to validate that a given [`num::Complex<f64>`]
461/// value is suitable for use within the library's validated types.
462///
463/// This policy applies the strict finiteness check (as defined by [`Native64RawRealStrictFinitePolicy`])
464/// to both the real and imaginary parts of the complex number. A [`num::Complex<f64>`] value is considered
465/// valid if and only if both of its components are finite (not `NaN`, `Infinity`, or subnormal).
466///
467/// It is a fundamental building block for the native `f64` kernel, providing the validation
468/// logic for [`ComplexNative64StrictFinite`](crate::kernels::native64_validated::ComplexNative64StrictFinite).
469///
470/// # Example
471///
472/// ```rust
473/// use num_valid::validation::{Native64RawComplexStrictFinitePolicy, ErrorsValidationRawComplex, ErrorsValidationRawReal};
474/// use try_create::ValidationPolicy;
475/// use num::Complex;
476///
477/// // A complex number with valid finite parts passes validation.
478/// let valid_complex = Complex::new(1.0, -2.0);
479/// assert!(Native64RawComplexStrictFinitePolicy::validate(valid_complex).is_ok());
480///
481/// // A complex number with NaN in the real part fails validation.
482/// let nan_complex = Complex::new(f64::NAN, 2.0);
483/// let result_nan = Native64RawComplexStrictFinitePolicy::validate(nan_complex);
484/// assert!(matches!(result_nan, Err(ErrorsValidationRawComplex::InvalidRealPart { .. })));
485///
486/// // A complex number with Infinity in the imaginary part fails validation.
487/// let inf_complex = Complex::new(1.0, f64::INFINITY);
488/// let result_inf = Native64RawComplexStrictFinitePolicy::validate(inf_complex);
489/// assert!(matches!(result_inf, Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })));
490///
491/// // A complex number with invalid parts in both also fails.
492/// let both_invalid = Complex::new(f64::NAN, f64::INFINITY);
493/// let result_both = Native64RawComplexStrictFinitePolicy::validate(both_invalid);
494/// assert!(matches!(result_both, Err(ErrorsValidationRawComplex::InvalidBothParts { .. })));
495/// ```
496pub type Native64RawComplexStrictFinitePolicy = StrictFinitePolicy<Complex<f64>, 53>;
497
498/// Ensures the `f64` value is strictly finite.
499///
500/// This policy checks if the `f64` value is:
501/// - Not NaN (Not a Number).
502/// - Not positive or negative Infinity.
503/// - Not subnormal.
504///
505/// # Errors
506///
507/// Returns [`ErrorsValidationRawReal<f64>`](ErrorsValidationRawReal) if the value
508/// fails any of these checks.
509impl ValidationPolicy for Native64RawRealStrictFinitePolicy {
510 type Value = f64;
511 type Error = ErrorsValidationRawReal<f64>;
512
513 fn validate_ref(value: &f64) -> Result<(), Self::Error> {
514 match value.classify() {
515 FpCategory::Nan => Err(ErrorsValidationRawReal::IsNaN {
516 value: *value,
517 backtrace: Backtrace::force_capture(),
518 }),
519 FpCategory::Infinite => {
520 if value.is_sign_positive() {
521 Err(ErrorsValidationRawReal::IsPosInfinity {
522 backtrace: Backtrace::force_capture(),
523 })
524 } else {
525 Err(ErrorsValidationRawReal::IsNegInfinity {
526 backtrace: Backtrace::force_capture(),
527 })
528 }
529 }
530 FpCategory::Subnormal => Err(ErrorsValidationRawReal::IsSubnormal {
531 value: *value,
532 backtrace: Backtrace::force_capture(),
533 }),
534 FpCategory::Normal | FpCategory::Zero => Ok(()),
535 }
536 }
537}
538
539/// Validates a complex number by checking both its real and imaginary parts.
540///
541/// This function uses the provided real validation policy `P` to validate both parts.
542/// If both parts are valid, it returns `Ok(())`. If either part is invalid,
543/// it returns an appropriate error variant from the `ErrorsValidationRawComplex` enum.
544///
545/// # Errors
546/// Returns [`ErrorsValidationRawComplex<P::Error>`](ErrorsValidationRawComplex)
547/// if either the real part, the imaginary part, or both parts fail validation
548/// using the policy `P`.
549pub(crate) fn validate_complex<P: ValidationPolicyReal>(
550 real_part: &P::Value,
551 imag_part: &P::Value,
552) -> Result<(), ErrorsValidationRawComplex<P::Error>> {
553 let real_validation = P::validate_ref(real_part);
554 let imag_validation = P::validate_ref(imag_part);
555 match (real_validation, imag_validation) {
556 (Ok(()), Ok(())) => Ok(()),
557 (Ok(()), Err(imag_err)) => {
558 Err(ErrorsValidationRawComplex::InvalidImaginaryPart { source: imag_err })
559 }
560 (Err(real_err), Ok(())) => {
561 Err(ErrorsValidationRawComplex::InvalidRealPart { source: real_err })
562 }
563 (Err(real_err), Err(imag_err)) => Err(ErrorsValidationRawComplex::InvalidBothParts {
564 real_error: real_err,
565 imag_error: imag_err,
566 }),
567 }
568}
569
570/// Ensures both the real and imaginary parts of a `Complex<f64>` value are strictly finite.
571///
572/// This policy applies the [`StrictFinitePolicy<f64>`](StrictFinitePolicy) to both
573/// the real and imaginary components of the complex number.
574///
575/// # Errors
576///
577/// Returns [`ErrorsValidationRawComplex<ErrorsValidationRawReal<f64>>`](ErrorsValidationRawComplex)
578/// if either the real part, the imaginary part, or both parts fail the
579/// `StrictFinitePolicy<f64>` checks.
580impl ValidationPolicy for Native64RawComplexStrictFinitePolicy {
581 type Value = Complex<f64>;
582 type Error = ErrorsValidationRawComplex<ErrorsValidationRawReal<f64>>;
583
584 fn validate_ref(value: &Complex<f64>) -> Result<(), Self::Error> {
585 validate_complex::<Native64RawRealStrictFinitePolicy>(&value.re, &value.im)
586 }
587}
588
589impl<RawReal: RawRealTrait, const PRECISION: u32> ValidationPolicyReal
590 for StrictFinitePolicy<RawReal, PRECISION>
591where
592 StrictFinitePolicy<RawReal, PRECISION>:
593 ValidationPolicy<Value = RawReal, Error = <RawReal as RawScalarTrait>::ValidationErrors>,
594{
595 const PRECISION: u32 = PRECISION;
596}
597
598impl<RawComplex: RawComplexTrait, const PRECISION: u32> ValidationPolicyComplex
599 for StrictFinitePolicy<RawComplex, PRECISION>
600where
601 StrictFinitePolicy<RawComplex, PRECISION>: ValidationPolicy<
602 Value = RawComplex,
603 Error = <RawComplex as RawScalarTrait>::ValidationErrors,
604 >,
605{
606 const PRECISION: u32 = PRECISION;
607}
608
609//--------------------------------------------------------------------------------------------------
610// Rug Implementations (conditional compilation)
611//--------------------------------------------------------------------------------------------------
612
613#[cfg(feature = "rug")]
614pub(crate) mod rug_impls {
615 use super::*; // Imports items from the parent module (validation)
616
617 /// Ensures the [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html) value is strictly finite.
618 ///
619 /// This policy checks if the underlying [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html) value:
620 /// - Is not NaN (Not a Number).
621 /// - Is not positive nor negative Infinity.
622 /// - Has the specified precision (equal to `PRECISION`).
623 ///
624 /// Note: [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html) does not have a direct concept of "subnormal" in the same
625 /// way IEEE 754 floats do; it maintains its specified precision.
626 ///
627 /// # Errors
628 ///
629 /// Returns [`ErrorsValidationRawReal<rug::Float>`](ErrorsValidationRawReal)
630 /// if the value fails any of these checks.
631 impl<const PRECISION: u32> ValidationPolicy for StrictFinitePolicy<rug::Float, PRECISION> {
632 type Value = rug::Float;
633 type Error = ErrorsValidationRawReal<rug::Float>;
634
635 fn validate_ref(value: &rug::Float) -> Result<(), Self::Error> {
636 let actual_precision = value.prec();
637 if actual_precision == PRECISION {
638 // rug::Float uses std::num::FpCategory via its own classify method
639 match value.classify() {
640 FpCategory::Infinite => {
641 if value.is_sign_positive() {
642 Err(ErrorsValidationRawReal::IsPosInfinity {
643 backtrace: Backtrace::force_capture(),
644 })
645 } else {
646 Err(ErrorsValidationRawReal::IsNegInfinity {
647 backtrace: Backtrace::force_capture(),
648 })
649 }
650 }
651 FpCategory::Nan => Err(ErrorsValidationRawReal::IsNaN {
652 value: value.clone(),
653 backtrace: Backtrace::force_capture(),
654 }),
655 FpCategory::Subnormal => Err(ErrorsValidationRawReal::IsSubnormal {
656 value: value.clone(),
657 backtrace: Backtrace::force_capture(),
658 }),
659 FpCategory::Zero | FpCategory::Normal => Ok(()),
660 }
661 } else {
662 Err(ErrorsValidationRawReal::PrecisionMismatch {
663 input_value: value.clone(),
664 actual_precision,
665 requested_precision: PRECISION,
666 backtrace: Backtrace::force_capture(),
667 })
668 }
669 }
670 }
671
672 /// Ensures both the real and imaginary parts of a [`rug::Complex`](https://docs.rs/rug/latest/rug/struct.Complex.html) value are strictly finite.
673 ///
674 /// This policy applies the [`StrictFinitePolicy<rug::Float>`](StrictFinitePolicy)
675 /// (which checks the underlying [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html)) to both the real and imaginary
676 /// components of the [`rug::Complex`](https://docs.rs/rug/latest/rug/struct.Complex.html).
677 ///
678 /// # Errors
679 ///
680 /// Returns [`ErrorsValidationRawComplex<ErrorsValidationRawReal<rug::Float>>`](ErrorsValidationRawComplex)
681 /// if either the real part, the imaginary part, or both parts fail the
682 /// `StrictFinitePolicy<RealRugStrictFinite<Precision>>` checks.
683 impl<const PRECISION: u32> ValidationPolicy for StrictFinitePolicy<rug::Complex, PRECISION> {
684 type Value = rug::Complex;
685 type Error = ErrorsValidationRawComplex<ErrorsValidationRawReal<rug::Float>>;
686
687 fn validate_ref(value: &rug::Complex) -> Result<(), Self::Error> {
688 validate_complex::<StrictFinitePolicy<rug::Float, PRECISION>>(
689 value.real(),
690 value.imag(),
691 )
692 }
693 }
694}
695//-------------------------------------------------------------
696
697//------------------------------------------------------------------------------------------------
698/// Provides a set of fundamental floating-point classification checks.
699///
700/// This trait defines common methods to determine the characteristics of a numerical value,
701/// such as whether it is finite, infinite, NaN (Not a Number), or normal.
702/// It is implemented for standard floating-point types (`f64`), complex numbers
703/// (`num::Complex<f64>`), and their arbitrary-precision counterparts from the `rug`
704/// library (via `RealRugStrictFinite<P>` and `ComplexRugStrictFinite<P>` which internally use [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html)
705/// and [`rug::Complex`](https://docs.rs/rug/latest/rug/struct.Complex.html)) when the "rug" feature is enabled.
706///
707/// `FpChecks` is often used as a supertrait for more general numerical traits like
708/// [`FpScalar`](crate::FpScalar), ensuring that types representing floating-point
709/// scalars can be queried for these basic properties. This is essential for
710/// validation policies and writing robust numerical algorithms.
711///
712/// Note that this trait focuses on these specific classifications. For more detailed
713/// categorization (like distinguishing subnormals or zero explicitly through this trait),
714/// types may provide other methods (e.g., `classify()` for `f64` and [`rug::Float`](https://docs.rs/rug/latest/rug/struct.Float.html),
715/// or `is_zero()` from [`FpScalar`](crate::FpScalar)).
716pub trait FpChecks {
717 /// Returns [`true`] if `self` is finite (i.e., not infinite and not NaN).
718 ///
719 /// For complex numbers, this typically means both the real and imaginary parts are finite.
720 fn is_finite(&self) -> bool;
721
722 /// Returns [`true`] if `self` is positive infinity or negative infinity.
723 ///
724 /// For complex numbers, this typically means at least one part is infinite,
725 /// and no part is NaN.
726 fn is_infinite(&self) -> bool;
727
728 /// Returns [`true`] if `self` is NaN (Not a Number).
729 ///
730 /// For complex numbers, this typically means at least one part is NaN.
731 fn is_nan(&self) -> bool;
732
733 /// Returns [`true`] if `self` is a *normal* number.
734 ///
735 /// A normal number is a finite, non-zero number that is not subnormal.
736 /// For complex numbers, this typically means both real and imaginary parts are normal
737 /// numbers (and thus individually finite, non-zero, and not subnormal).
738 fn is_normal(&self) -> bool;
739}
740
741#[duplicate_item(
742 T;
743 [f64];
744 [Complex::<f64>];
745)]
746impl FpChecks for T {
747 #[inline(always)]
748 fn is_finite(&self) -> bool {
749 T::is_finite(*self)
750 }
751
752 #[inline(always)]
753 fn is_infinite(&self) -> bool {
754 T::is_infinite(*self)
755 }
756
757 #[inline(always)]
758 fn is_nan(&self) -> bool {
759 T::is_nan(*self)
760 }
761
762 #[inline(always)]
763 fn is_normal(&self) -> bool {
764 T::is_normal(*self)
765 }
766}
767//------------------------------------------------------------------------------------------------
768
769//------------------------------------------------------------------------------------------------
770/// A validation policy that is active only in debug builds.
771///
772/// This struct acts as a wrapper around another [`ValidationPolicy`].
773///
774/// - In **debug builds** (`#[cfg(debug_assertions)]`), it delegates validation
775/// directly to the inner policy `P`.
776/// - In **release builds** (`#[cfg(not(debug_assertions))]`), it becomes a "no-op"
777/// (no operation), meaning its `validate` and `validate_ref` methods do nothing
778/// and always return `Ok`.
779///
780/// This is useful for enforcing strict, potentially expensive validations during
781/// development and testing, while eliminating their performance overhead in production code.
782///
783/// # Generic Parameters
784///
785/// - `P`: The inner [`ValidationPolicy`] to use during debug builds.
786///
787/// # Example
788///
789/// ```rust
790/// use num_valid::{validation::{DebugValidationPolicy, StrictFinitePolicy, Native64RawRealStrictFinitePolicy}};
791/// use try_create::ValidationPolicy;
792///
793/// // Define a policy that uses Native64RawRealStrictFinitePolicy
794/// // (i.e. StrictFinitePolicy<f64, 53>) only in debug builds.
795/// type MyPolicy = DebugValidationPolicy<Native64RawRealStrictFinitePolicy>;
796///
797/// let nan_value = f64::NAN;
798/// let result = MyPolicy::validate(nan_value);
799///
800/// #[cfg(debug_assertions)]
801/// {
802/// // In debug mode, the validation fails because StrictFinitePolicy catches NaN.
803/// assert!(result.is_err());
804/// }
805///
806/// #[cfg(not(debug_assertions))]
807/// {
808/// // In release mode, the validation is skipped and always succeeds.
809/// assert!(result.is_ok());
810/// }
811/// ```
812pub struct DebugValidationPolicy<P: ValidationPolicy>(PhantomData<P>);
813
814impl<P: ValidationPolicy> ValidationPolicy for DebugValidationPolicy<P> {
815 type Value = P::Value;
816 type Error = P::Error;
817
818 #[inline(always)]
819 fn validate_ref(value: &Self::Value) -> Result<(), Self::Error> {
820 // In debug builds, this implementation delegates to the inner policy `P`.
821 #[cfg(debug_assertions)]
822 {
823 // Delegate to the inner policy's validation logic.
824 P::validate_ref(value)
825 }
826 #[cfg(not(debug_assertions))]
827 {
828 let _ = value; // Avoid unused variable warning in release mode.
829 // In release mode, this is a no-op.
830 // The validation is skipped, and we always return Ok.
831 // This allows the compiler to optimize away the validation logic entirely.
832 // This is useful for performance-critical code where validation is not needed in production.
833 // Note: The `value` is not used here, but we keep it to match the signature of `validate_ref`.
834 // This way, we can still call this method without needing to change the function signature.
835 // This is particularly useful when this policy is used in generic contexts
836 // where the `validate_ref` method is expected to take a reference to the value.
837 // The `PhantomData` ensures that the type system knows about `P` even
838 // though we don't use it directly in the release build.
839 // This is a common pattern in Rust to create zero-sized types that carry type information.
840 Ok(())
841 }
842 }
843}
844
845impl<P> ValidationPolicyReal for DebugValidationPolicy<P>
846where
847 P: ValidationPolicyReal,
848{
849 const PRECISION: u32 = P::PRECISION;
850}
851
852impl<P> ValidationPolicyComplex for DebugValidationPolicy<P>
853where
854 P: ValidationPolicyComplex,
855{
856 const PRECISION: u32 = P::PRECISION;
857}
858//------------------------------------------------------------------------------------------------
859
860/// Validates a value using the given policy, but only in debug builds.
861/// In release builds, this function is a no-op and should be optimized away.
862///
863/// # Panics
864///
865/// In debug builds, this function will panic if `P::validate_ref(value)` returns an `Err`.
866#[inline(always)]
867pub fn debug_validate<P: ValidationPolicy>(value: &P::Value, msg: &str) {
868 #[cfg(debug_assertions)]
869 {
870 if let Err(e) = P::validate_ref(value) {
871 panic!("Debug validation of {msg} value failed: {e}");
872 }
873 }
874 #[cfg(not(debug_assertions))]
875 {
876 // These are no-op in release builds
877 let _ = value;
878 let _ = msg;
879 }
880}
881//-------------------------------------------------------------
882
883//-------------------------------------------------------------
884/// A marker trait for validation policies that guarantee finite real values.
885///
886/// This trait serves as a compile-time marker to identify policies that ensure
887/// all validated real values are always finite (never NaN or infinite). It enables
888/// the implementation of full equality ([`Eq`]) and hashing ([`Hash`]) for
889/// validated types, allowing them to be used as keys in hash-based collections
890/// like [`HashMap`](std::collections::HashMap) and [`HashSet`](std::collections::HashSet).
891///
892/// ## Enabled Features
893///
894/// When a validation policy implements this trait, the corresponding validated types
895/// (like [`RealValidated<K>`](crate::RealValidated)) automatically gain:
896///
897/// - **Full Equality ([`Eq`])**: Equality becomes reflexive, symmetric, and transitive
898/// since NaN values (which violate these properties) are excluded.
899/// - **Hashing ([`Hash`])**: Combined with [`Eq`], this allows validated types to be
900/// used as keys in hash-based collections like [`HashMap`](std::collections::HashMap) and [`HashSet`](std::collections::HashSet).
901///
902/// ## Hashing Implementation Details
903///
904/// The [`Hash`] implementation for validated types:
905/// - Delegates to the underlying raw type's [`RawRealTrait::compute_hash`] method
906/// - Handles IEEE 754 floating-point edge cases correctly (e.g., signed zeros)
907/// - Maintains the contract that `a == b` implies `hash(a) == hash(b)`
908/// - Works with both native `f64` and arbitrary-precision `rug` types
909///
910/// ## Design Decision: No Total Ordering
911///
912/// This trait intentionally does **not** enable total ordering ([`Ord`]) because:
913/// 1. The library's comparison functions use efficient reference-based [`PartialOrd`]
914/// 2. [`Max`](crate::functions::Max)/[`Min`](crate::functions::Min) traits provide better performance than value-based [`Ord`]
915/// 3. Mathematical operations work naturally with partial ordering semantics
916///
917/// ## Use Cases
918///
919/// This marker trait enables validated numerical types to be used in contexts that
920/// require [`Eq`] and [`Hash`]:
921///
922/// ```rust
923/// use num_valid::{RealNative64StrictFinite, functions::Max};
924/// use std::collections::{HashMap, HashSet};
925/// use try_create::TryNew;
926///
927/// // Use as HashMap keys (requires Eq + Hash)
928/// let mut scores = HashMap::new();
929/// let player1 = RealNative64StrictFinite::try_new(1.5).unwrap();
930/// scores.insert(player1, 100);
931///
932/// // Use in HashSet (requires Eq + Hash)
933/// let mut unique_values = HashSet::new();
934/// unique_values.insert(RealNative64StrictFinite::try_new(3.14).unwrap());
935///
936/// // Comparison still works efficiently with PartialOrd
937/// let a = RealNative64StrictFinite::try_new(1.0).unwrap();
938/// let b = RealNative64StrictFinite::try_new(2.0).unwrap();
939/// assert!(a < b); // Uses PartialOrd
940/// assert_eq!(a.max(&b), &b); // Uses library's Max trait
941/// ```
942///
943/// ## Hash Collision Considerations
944///
945/// The hashing implementation ensures:
946/// - Consistent hashes for mathematically equal values
947/// - Proper handling of floating-point edge cases
948/// - Compatibility with both native and arbitrary-precision backends
949/// - No hash collisions due to NaN values (which are excluded by design)
950///
951/// ## Currently Implemented By
952///
953/// - [`StrictFinitePolicy<T, P>`](StrictFinitePolicy): The primary policy that
954/// rejects NaN, infinity, and subnormal values.
955/// - [`DebugValidationPolicy<StrictFinitePolicy<T, P>>`](DebugValidationPolicy):
956/// A wrapper that applies strict validation only in debug builds.
957///
958/// ## Safety and Correctness
959///
960/// This trait should only be implemented by validation policies that genuinely
961/// guarantee finite values. Incorrect implementation could lead to hash collisions
962/// or violated equality contracts in the methods that rely on this guarantee.
963///
964/// The trait is designed as a marker trait (no methods) to emphasize that it's
965/// purely a compile-time contract rather than runtime functionality.
966pub trait GuaranteesFiniteValues: ValidationPolicyReal {}
967
968// Implement the marker for `StrictFinitePolicy` for any real scalar type.
969impl<RawReal: RawRealTrait, const PRECISION: u32> GuaranteesFiniteValues
970 for StrictFinitePolicy<RawReal, PRECISION>
971where
972 // This bound is necessary to satisfy the supertrait requirements of GuaranteesFiniteValues
973 StrictFinitePolicy<RawReal, PRECISION>: ValidationPolicyReal,
974{
975}
976
977// Implement the marker for `DebugValidationPolicy<StrictFinitePolicy>` for any real scalar type.
978impl<RawReal: RawRealTrait, const PRECISION: u32> GuaranteesFiniteValues
979 for DebugValidationPolicy<StrictFinitePolicy<RawReal, PRECISION>>
980where
981 // This bound is necessary to satisfy the supertrait requirements of GuaranteesFiniteValues
982 StrictFinitePolicy<RawReal, PRECISION>: ValidationPolicyReal,
983{
984}
985//-------------------------------------------------------------
986
987//-------------------------------------------------------------
988/// Implements total equality (`Eq`) for `RealValidated` types.
989///
990/// This implementation is only available when the kernel's `RealPolicy`
991/// implements the `GuaranteesFiniteValues` marker trait. This ensures that
992/// the underlying value is never `NaN`, making the equality relation reflexive,
993/// symmetric, and transitive, as required by `Eq`.
994impl<K: NumKernel> Eq for RealValidated<K>
995where
996 K::RealPolicy: GuaranteesFiniteValues,
997{
998 // `Eq` is a marker trait and requires no methods. Its presence simply
999 // asserts that `PartialEq`'s `eq` method forms an equivalence relation.
1000}
1001//-------------------------------------------------------------
1002
1003//-------------------------------------------------------------
1004/// Implements [`Hash`] for validated real number types with finite value guarantees.
1005///
1006/// This implementation is only available when the kernel's `RealPolicy`
1007/// implements the [`GuaranteesFiniteValues`] marker trait. This ensures that
1008/// the underlying value is never `NaN`, making hash values consistent and
1009/// allowing these types to be used as keys in hash-based collections.
1010///
1011/// ## Hashing Strategy
1012///
1013/// The implementation delegates to the [`RawRealTrait::compute_hash()`] method provided by the
1014/// underlying raw real type (via [`RawRealTrait`]). This method:
1015///
1016/// - For `f64` types: Uses IEEE 754 bit representation via `f64::to_bits()`
1017/// - For `rug::Float` types: Uses integer representation via `rug::Float::to_integer()`
1018/// - Handles signed zeros correctly (both `+0.0` and `-0.0` produce the same hash)
1019/// - Maintains the hash contract: `a == b` implies `hash(a) == hash(b)`
1020///
1021/// ## Safety and Correctness
1022///
1023/// This implementation is safe because:
1024/// - [`GuaranteesFiniteValues`] ensures no `NaN` values can exist
1025/// - The [`Eq`] implementation is well-defined for finite values
1026/// - Signed zeros are handled consistently in both equality and hashing
1027///
1028/// ## Usage Example
1029///
1030/// ```rust
1031/// use num_valid::RealNative64StrictFinite;
1032/// use std::collections::HashMap;
1033/// use try_create::TryNew;
1034///
1035/// let mut map = HashMap::new();
1036/// let key = RealNative64StrictFinite::try_new(3.14).unwrap();
1037/// map.insert(key, "pi approximation");
1038/// assert_eq!(map.len(), 1);
1039/// ```
1040impl<K: NumKernel> Hash for RealValidated<K>
1041where
1042 K::RealPolicy: GuaranteesFiniteValues,
1043{
1044 #[inline(always)]
1045 fn hash<H: Hasher>(&self, state: &mut H) {
1046 self.value.compute_hash(state);
1047 }
1048}
1049//-------------------------------------------------------------
1050
1051//-------------------------------------------------------------
1052#[cfg(test)]
1053mod tests {
1054 use super::*;
1055
1056 #[cfg(feature = "rug")]
1057 use crate::{ComplexRugStrictFinite, RealRugStrictFinite};
1058
1059 mod fp_checks {
1060 use super::*;
1061 use num::Complex;
1062
1063 mod native64 {
1064 use super::*;
1065
1066 mod real {
1067 use super::*;
1068
1069 #[test]
1070 fn test_is_finite() {
1071 assert!(<f64 as FpChecks>::is_finite(&1.0));
1072 assert!(!<f64 as FpChecks>::is_finite(&f64::INFINITY));
1073 assert!(!<f64 as FpChecks>::is_finite(&f64::NEG_INFINITY));
1074 assert!(!<f64 as FpChecks>::is_finite(&f64::NAN));
1075 }
1076
1077 #[test]
1078 fn test_is_infinite() {
1079 assert!(!<f64 as FpChecks>::is_infinite(&1.0));
1080 assert!(<f64 as FpChecks>::is_infinite(&f64::INFINITY));
1081 assert!(<f64 as FpChecks>::is_infinite(&f64::NEG_INFINITY));
1082 assert!(!<f64 as FpChecks>::is_infinite(&f64::NAN));
1083 }
1084
1085 #[test]
1086 fn test_is_nan() {
1087 assert!(!<f64 as FpChecks>::is_nan(&1.0));
1088 assert!(!<f64 as FpChecks>::is_nan(&f64::INFINITY));
1089 assert!(!<f64 as FpChecks>::is_nan(&f64::NEG_INFINITY));
1090 assert!(<f64 as FpChecks>::is_nan(&f64::NAN));
1091 }
1092
1093 #[test]
1094 fn test_is_normal() {
1095 assert!(<f64 as FpChecks>::is_normal(&1.0));
1096 assert!(!<f64 as FpChecks>::is_normal(&0.0));
1097 assert!(!<f64 as FpChecks>::is_normal(&f64::INFINITY));
1098 assert!(!<f64 as FpChecks>::is_normal(&f64::NEG_INFINITY));
1099 assert!(!<f64 as FpChecks>::is_normal(&f64::NAN));
1100 assert!(<f64 as FpChecks>::is_normal(&f64::MIN_POSITIVE));
1101 }
1102 }
1103
1104 mod complex {
1105 use super::*;
1106
1107 #[test]
1108 fn test_is_finite() {
1109 assert!(<Complex<f64> as FpChecks>::is_finite(&Complex::new(
1110 1.0, 1.0
1111 )));
1112 assert!(!<Complex<f64> as FpChecks>::is_finite(&Complex::new(
1113 f64::INFINITY,
1114 1.0
1115 )));
1116 assert!(!<Complex<f64> as FpChecks>::is_finite(&Complex::new(
1117 1.0,
1118 f64::NAN
1119 )));
1120 }
1121
1122 #[test]
1123 fn test_is_infinite() {
1124 assert!(!<Complex<f64> as FpChecks>::is_infinite(&Complex::new(
1125 1.0, 1.0
1126 )));
1127 assert!(<Complex<f64> as FpChecks>::is_infinite(&Complex::new(
1128 f64::INFINITY,
1129 1.0
1130 )));
1131 assert!(!<Complex<f64> as FpChecks>::is_infinite(&Complex::new(
1132 1.0,
1133 f64::NAN
1134 )));
1135 }
1136
1137 #[test]
1138 fn test_is_nan() {
1139 assert!(!<Complex<f64> as FpChecks>::is_nan(&Complex::new(1.0, 1.0)));
1140 assert!(<Complex<f64> as FpChecks>::is_nan(&Complex::new(
1141 f64::NAN,
1142 1.0
1143 )));
1144 assert!(<Complex<f64> as FpChecks>::is_nan(&Complex::new(
1145 1.0,
1146 f64::NAN
1147 )));
1148 }
1149
1150 #[test]
1151 fn test_is_normal() {
1152 assert!(<Complex<f64> as FpChecks>::is_normal(&Complex::new(
1153 1.0, 1.0
1154 )));
1155 assert!(!<Complex<f64> as FpChecks>::is_normal(&Complex::new(
1156 0.0, 0.0
1157 )));
1158 assert!(!<Complex<f64> as FpChecks>::is_normal(&Complex::new(
1159 f64::INFINITY,
1160 1.0
1161 )));
1162 assert!(!<Complex<f64> as FpChecks>::is_normal(&Complex::new(
1163 1.0,
1164 f64::NAN
1165 )));
1166 }
1167 }
1168 }
1169 }
1170
1171 mod strict_finite_policy {
1172 use super::*;
1173
1174 mod raw_types {
1175 use super::*;
1176
1177 mod native64 {
1178 use super::*;
1179
1180 mod real {
1181 use super::*;
1182
1183 #[test]
1184 fn f64_infinity() {
1185 let result = StrictFinitePolicy::<f64, 53>::validate(f64::INFINITY);
1186 assert!(matches!(
1187 result,
1188 Err(ErrorsValidationRawReal::IsPosInfinity { .. })
1189 ));
1190
1191 let result = StrictFinitePolicy::<f64, 53>::validate_ref(&f64::INFINITY);
1192 assert!(matches!(
1193 result,
1194 Err(ErrorsValidationRawReal::IsPosInfinity { .. })
1195 ));
1196
1197 let result = StrictFinitePolicy::<f64, 53>::validate(f64::NEG_INFINITY);
1198 assert!(matches!(
1199 result,
1200 Err(ErrorsValidationRawReal::IsNegInfinity { .. })
1201 ));
1202
1203 let result =
1204 StrictFinitePolicy::<f64, 53>::validate_ref(&f64::NEG_INFINITY);
1205 assert!(matches!(
1206 result,
1207 Err(ErrorsValidationRawReal::IsNegInfinity { .. })
1208 ));
1209 }
1210
1211 #[test]
1212 fn f64_nan() {
1213 let result = StrictFinitePolicy::<f64, 53>::validate(f64::NAN);
1214 assert!(matches!(result, Err(ErrorsValidationRawReal::IsNaN { .. })));
1215
1216 let result = StrictFinitePolicy::<f64, 53>::validate_ref(&f64::NAN);
1217 assert!(matches!(result, Err(ErrorsValidationRawReal::IsNaN { .. })));
1218 }
1219
1220 #[test]
1221 fn f64_subnormal() {
1222 // let subnormal = f64::MIN_POSITIVE / 2.0;
1223 let subnormal = f64::from_bits(1); // the smallest positive subnormal
1224
1225 let result = StrictFinitePolicy::<f64, 53>::validate(subnormal);
1226 assert!(matches!(
1227 result,
1228 Err(ErrorsValidationRawReal::<f64>::IsSubnormal { .. })
1229 ));
1230
1231 let result = StrictFinitePolicy::<f64, 53>::validate_ref(&subnormal);
1232 assert!(matches!(
1233 result,
1234 Err(ErrorsValidationRawReal::<f64>::IsSubnormal { .. })
1235 ));
1236 }
1237
1238 #[test]
1239 fn f64_zero() {
1240 let value = 0.0;
1241 let result = StrictFinitePolicy::<f64, 53>::validate(value);
1242 assert!(matches!(result, Ok(0.0)));
1243 }
1244
1245 #[test]
1246 fn f64_normal() {
1247 let value = 1.0;
1248 let result = StrictFinitePolicy::<f64, 53>::validate(value);
1249 assert!(matches!(result, Ok(1.0)));
1250
1251 let result = StrictFinitePolicy::<f64, 53>::validate_ref(&value);
1252 assert!(matches!(result, Ok(())));
1253 }
1254 }
1255
1256 mod complex {
1257 use super::*;
1258 use num::Complex;
1259
1260 #[test]
1261 fn test_invalid_both_parts() {
1262 let z = Complex::new(f64::NAN, f64::NEG_INFINITY);
1263 let err =
1264 StrictFinitePolicy::<Complex<f64>, 53>::validate_ref(&z).unwrap_err();
1265 matches!(err, ErrorsValidationRawComplex::InvalidBothParts { .. });
1266 }
1267
1268 #[test]
1269 fn complex_f64_invalid_real_part() {
1270 let value = Complex::new(f64::INFINITY, 1.0);
1271 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1272 assert!(matches!(
1273 result,
1274 Err(ErrorsValidationRawComplex::InvalidRealPart {
1275 source: ErrorsValidationRawReal::IsPosInfinity { .. },
1276 ..
1277 })
1278 ));
1279
1280 let value = Complex::new(f64::NAN, 1.0);
1281 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1282 assert!(matches!(
1283 result,
1284 Err(ErrorsValidationRawComplex::InvalidRealPart {
1285 source: ErrorsValidationRawReal::IsNaN { .. },
1286 ..
1287 })
1288 ));
1289
1290 let value = Complex::new(f64::MIN_POSITIVE / 2.0, 1.0);
1291 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1292 assert!(matches!(
1293 result,
1294 Err(ErrorsValidationRawComplex::InvalidRealPart {
1295 source: ErrorsValidationRawReal::IsSubnormal { .. },
1296 ..
1297 })
1298 ));
1299 }
1300
1301 #[test]
1302 fn complex_f64_invalid_imaginary_part() {
1303 let value = Complex::new(1.0, f64::INFINITY);
1304 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1305 assert!(matches!(
1306 result,
1307 Err(ErrorsValidationRawComplex::InvalidImaginaryPart {
1308 source: ErrorsValidationRawReal::IsPosInfinity { .. },
1309 ..
1310 })
1311 ));
1312
1313 let value = Complex::new(1.0, f64::NAN);
1314 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1315 assert!(matches!(
1316 result,
1317 Err(ErrorsValidationRawComplex::InvalidImaginaryPart {
1318 source: ErrorsValidationRawReal::IsNaN { .. },
1319 ..
1320 })
1321 ));
1322
1323 let value = Complex::new(1.0, f64::MIN_POSITIVE / 2.0);
1324 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1325 assert!(matches!(
1326 result,
1327 Err(ErrorsValidationRawComplex::InvalidImaginaryPart {
1328 source: ErrorsValidationRawReal::IsSubnormal { .. },
1329 ..
1330 })
1331 ));
1332 }
1333
1334 #[test]
1335 fn complex_f64_zero() {
1336 let value = Complex::new(0.0, 0.0);
1337 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1338 assert!(result.is_ok());
1339 }
1340
1341 #[test]
1342 fn complex_f64_normal_value() {
1343 let value = Complex::new(1.0, 1.0);
1344 let result = StrictFinitePolicy::<Complex<f64>, 53>::validate(value);
1345 assert!(result.is_ok());
1346 }
1347 }
1348 }
1349
1350 #[cfg(feature = "rug")]
1351 mod rug53 {
1352 use super::*;
1353
1354 const PRECISION: u32 = 53; // Default precision for rug tests
1355
1356 mod float {
1357 use super::*;
1358 use rug::Float;
1359
1360 #[test]
1361 fn rug_float_valid() {
1362 let x = Float::with_val(PRECISION, 1.0);
1363 assert!(StrictFinitePolicy::<Float, PRECISION>::validate_ref(&x).is_ok());
1364 assert_eq!(
1365 StrictFinitePolicy::<Float, PRECISION>::validate(x.clone()).unwrap(),
1366 x
1367 );
1368 }
1369
1370 #[test]
1371 fn rug_float_nan() {
1372 let x = Float::with_val(PRECISION, Float::parse("nan").unwrap());
1373 let err =
1374 StrictFinitePolicy::<Float, PRECISION>::validate_ref(&x).unwrap_err();
1375 assert!(matches!(err, ErrorsValidationRawReal::IsNaN { .. }));
1376 let err = StrictFinitePolicy::<Float, PRECISION>::validate(x).unwrap_err();
1377 assert!(matches!(err, ErrorsValidationRawReal::IsNaN { .. }));
1378 }
1379
1380 #[test]
1381 fn rug_float_infinity() {
1382 use rug::Float;
1383
1384 let x = Float::with_val(PRECISION, Float::parse("inf").unwrap());
1385 let err =
1386 StrictFinitePolicy::<Float, PRECISION>::validate_ref(&x).unwrap_err();
1387 assert!(matches!(err, ErrorsValidationRawReal::IsPosInfinity { .. }));
1388 let err = StrictFinitePolicy::<Float, PRECISION>::validate(x).unwrap_err();
1389 assert!(matches!(err, ErrorsValidationRawReal::IsPosInfinity { .. }));
1390
1391 let x = Float::with_val(PRECISION, Float::parse("-inf").unwrap());
1392 let err =
1393 StrictFinitePolicy::<Float, PRECISION>::validate_ref(&x).unwrap_err();
1394 assert!(matches!(err, ErrorsValidationRawReal::IsNegInfinity { .. }));
1395 let err = StrictFinitePolicy::<Float, PRECISION>::validate(x).unwrap_err();
1396 assert!(matches!(err, ErrorsValidationRawReal::IsNegInfinity { .. }));
1397 }
1398
1399 #[test]
1400 fn rug_float_zero() {
1401 let value = Float::with_val(PRECISION, 0.0);
1402 let result = StrictFinitePolicy::<Float, PRECISION>::validate_ref(&value);
1403 assert!(result.is_ok());
1404 let result =
1405 StrictFinitePolicy::<Float, PRECISION>::validate(value.clone())
1406 .unwrap();
1407 assert_eq!(result, value);
1408 }
1409 }
1410
1411 mod complex {
1412 use super::*;
1413 use rug::{Complex, Float};
1414
1415 #[test]
1416 fn rug_complex_valid() {
1417 let z = Complex::with_val(PRECISION, (1.0, -2.0));
1418 assert!(StrictFinitePolicy::<Complex, PRECISION>::validate_ref(&z).is_ok());
1419 assert_eq!(
1420 StrictFinitePolicy::<Complex, PRECISION>::validate(z.clone()).unwrap(),
1421 z
1422 );
1423 }
1424
1425 #[test]
1426 fn rug_complex_real_invalid() {
1427 let nan = Float::with_val(PRECISION, Float::parse("nan").unwrap());
1428 let z = Complex::with_val(PRECISION, (&nan, 1.0));
1429 let err =
1430 StrictFinitePolicy::<Complex, PRECISION>::validate_ref(&z).unwrap_err();
1431 assert!(matches!(
1432 err,
1433 ErrorsValidationRawComplex::InvalidRealPart {
1434 source: ErrorsValidationRawReal::IsNaN { .. }
1435 }
1436 ));
1437 }
1438
1439 #[test]
1440 fn rug_complex_imag_invalid() {
1441 let inf = Float::with_val(PRECISION, Float::parse("inf").unwrap());
1442 let z = Complex::with_val(PRECISION, (1.0, &inf));
1443 let err =
1444 StrictFinitePolicy::<Complex, PRECISION>::validate_ref(&z).unwrap_err();
1445 assert!(matches!(
1446 err,
1447 ErrorsValidationRawComplex::InvalidImaginaryPart {
1448 source: ErrorsValidationRawReal::IsPosInfinity { .. }
1449 }
1450 ));
1451 }
1452
1453 #[test]
1454 fn rug_complex_both_invalid() {
1455 let nan = Float::with_val(PRECISION, Float::parse("nan").unwrap());
1456 let inf = Float::with_val(PRECISION, Float::parse("inf").unwrap());
1457 let z = Complex::with_val(PRECISION, (&nan, &inf));
1458 let err =
1459 StrictFinitePolicy::<Complex, PRECISION>::validate_ref(&z).unwrap_err();
1460 assert!(matches!(
1461 err,
1462 ErrorsValidationRawComplex::InvalidBothParts {
1463 real_error: ErrorsValidationRawReal::IsNaN { .. },
1464 imag_error: ErrorsValidationRawReal::IsPosInfinity { .. },
1465 ..
1466 }
1467 ));
1468 }
1469
1470 #[test]
1471 fn rug_complex_zero() {
1472 let zero = Complex::with_val(
1473 PRECISION,
1474 (
1475 Float::with_val(PRECISION, 0.0),
1476 Float::with_val(PRECISION, 0.0),
1477 ),
1478 );
1479 let result = StrictFinitePolicy::<Complex, PRECISION>::validate_ref(&zero);
1480 assert!(result.is_ok());
1481 assert_eq!(
1482 StrictFinitePolicy::<Complex, PRECISION>::validate(zero.clone())
1483 .unwrap(),
1484 zero
1485 );
1486 }
1487 }
1488 }
1489 }
1490
1491 // Module for testing the wrapper types that implement TryNew
1492 #[cfg(feature = "rug")]
1493 mod wrapper_types {
1494 use super::*;
1495
1496 mod rug_wrappers {
1497 use super::*;
1498 use crate::RealScalar;
1499 use num::One;
1500 use rug::{Complex as RugComplex, Float};
1501 use try_create::{IntoInner, TryNew};
1502
1503 const PRECISION: u32 = 53; // Default precision for rug tests
1504
1505 mod real {
1506 use super::*;
1507
1508 #[test]
1509 fn try_new_real_rug_correct_precision() {
1510 // Test RealRugStrictFinite with valid rug::Float and correct precision
1511 let val = Float::with_val(PRECISION, 1.0);
1512 let res = RealRugStrictFinite::<PRECISION>::try_new(val.clone());
1513 assert!(res.is_ok(),);
1514 assert_eq!(res.unwrap().into_inner(), val);
1515 }
1516
1517 #[test]
1518 fn try_new_real_rug_incorrect_precision() {
1519 // Test RealRugStrictFinite with valid rug::Float but incorrect precision
1520 let val_wrong_prec = Float::with_val(PRECISION + 10, 1.0); // Different precision
1521 let res = RealRugStrictFinite::<PRECISION>::try_new(val_wrong_prec);
1522 assert!(res.is_err());
1523 matches!(res, Err(ErrorsValidationRawReal::PrecisionMismatch { actual_precision, .. })
1524 if actual_precision == PRECISION + 10
1525 );
1526 }
1527
1528 #[test]
1529 fn try_new_real_rug_nan() {
1530 // Test RealRugStrictFinite with rug::Float NaN
1531 let val_nan = Float::with_val(PRECISION, Float::parse("nan").unwrap());
1532 let res = RealRugStrictFinite::<PRECISION>::try_new(val_nan);
1533 assert!(res.is_err());
1534 assert!(matches!(
1535 res,
1536 Err(ErrorsValidationRawReal::<Float>::IsNaN { .. })
1537 ));
1538 }
1539
1540 #[test]
1541 fn try_new_real_rug_infinity() {
1542 // Test RealRugStrictFinite with rug::Float positive infinity
1543 let val_inf = Float::with_val(PRECISION, Float::parse("inf").unwrap());
1544 let res_pos_inf = RealRugStrictFinite::<PRECISION>::try_new(val_inf);
1545 assert!(res_pos_inf.is_err());
1546 assert!(matches!(
1547 res_pos_inf,
1548 Err(ErrorsValidationRawReal::<Float>::IsPosInfinity { .. })
1549 ));
1550
1551 // Test RealRugStrictFinite with rug::Float negative infinity
1552 let val_neg_inf = Float::with_val(PRECISION, Float::parse("-inf").unwrap());
1553 let res_neg_inf = RealRugStrictFinite::<PRECISION>::try_new(val_neg_inf);
1554 assert!(res_neg_inf.is_err());
1555 assert!(matches!(
1556 res_neg_inf,
1557 Err(ErrorsValidationRawReal::<Float>::IsNegInfinity { .. })
1558 ));
1559 }
1560 }
1561
1562 mod complex {
1563 use super::*;
1564 use crate::ComplexScalarGetParts;
1565
1566 #[test]
1567 fn try_new_complex_rug_correct_precision() {
1568 // Test ComplexRugStrictFinite with valid rug::Complex and correct precision for both parts
1569 let val = RugComplex::with_val(PRECISION, (1.0, -2.0));
1570 let res = ComplexRugStrictFinite::<PRECISION>::try_new(val.clone());
1571 assert!(res.is_ok());
1572 assert_eq!(res.unwrap().into_inner(), val);
1573 }
1574
1575 /*
1576 #[test]
1577 #[ignore = "because the precision used to construct a rug::Complex override the precision of the real and imaginary parts"]
1578 fn try_new_complex_rug_precision_mismatch_real() {
1579 // Test ComplexRugStrictFinite where real part has incorrect precision
1580 let real_part_wrong_prec = Float::with_val(PRECISION - 1, 1.0);
1581 let imag_part_correct_prec = Float::with_val(PRECISION, -2.0);
1582 let val = RugComplex::with_val(
1583 PRECISION,
1584 (real_part_wrong_prec, imag_part_correct_prec),
1585 ); // Construct with Float parts
1586
1587 let res = ComplexRugStrictFinite::<PRECISION>::try_new(val);
1588 assert!(res.is_err());
1589 assert!(matches!(
1590 res,
1591 Err(ErrorsValidationRawComplex::InvalidRealPart {
1592 source: ErrorsValidationRawReal::PrecisionMismatch { .. }
1593 })
1594 ));
1595 }
1596
1597 #[test]
1598 #[ignore = "because the precision used to construct a rug::Complex override the precision of the real and imaginary parts"]
1599 fn try_new_complex_rug_precision_mismatch_imag() {
1600 // Test ComplexRugStrictFinite where imaginary part has incorrect precision
1601 let real_part_correct_prec = Float::with_val(PRECISION, 1.0);
1602 let imag_part_wrong_prec = Float::with_val(PRECISION - 1, -2.0);
1603 let val = RugComplex::with_val(
1604 PRECISION,
1605 (real_part_correct_prec, imag_part_wrong_prec),
1606 ); // Construct with Float parts
1607
1608 let res = ComplexRugStrictFinite::<PRECISION>::try_new(val);
1609 assert!(res.is_err());
1610 matches!(res, Err(ErrorsValidationRawComplex::InvalidImaginaryPart { source })
1611 if matches!(source, ErrorsValidationRawReal::PrecisionMismatch { .. })
1612 );
1613 }
1614 */
1615
1616 #[test]
1617 fn try_new_complex_rug_precision_mismatch_both() {
1618 // Test ComplexRugStrictFinite where both parts have incorrect precision
1619 let real_part_wrong_prec = Float::with_val(PRECISION - 1, 1.0);
1620 let imag_part_wrong_prec = Float::with_val(PRECISION - 1, -2.0);
1621 let val = RugComplex::with_val(
1622 PRECISION - 1,
1623 (real_part_wrong_prec, imag_part_wrong_prec),
1624 ); // Construct with Float parts
1625
1626 let res = ComplexRugStrictFinite::<PRECISION>::try_new(val);
1627 assert!(res.is_err());
1628 assert!(matches!(
1629 res,
1630 Err(ErrorsValidationRawComplex::InvalidBothParts {
1631 real_error: ErrorsValidationRawReal::PrecisionMismatch { .. },
1632 imag_error: ErrorsValidationRawReal::PrecisionMismatch { .. },
1633 ..
1634 })
1635 ));
1636 }
1637
1638 #[test]
1639 fn try_new_complex_rug_invalid_real_nan() {
1640 // Test ComplexRugStrictFinite where real part is NaN (value validation failure)
1641 let nan_real = Float::with_val(PRECISION, Float::parse("nan").unwrap());
1642 let imag_part = Float::with_val(PRECISION, 1.0);
1643 let val = RugComplex::with_val(PRECISION, (nan_real, imag_part));
1644
1645 let res = ComplexRugStrictFinite::<PRECISION>::try_new(val);
1646 assert!(res.is_err());
1647 assert!(matches!(
1648 res,
1649 Err(ErrorsValidationRawComplex::InvalidRealPart {
1650 source: ErrorsValidationRawReal::<Float>::IsNaN { .. },
1651 })
1652 ));
1653 }
1654
1655 #[test]
1656 fn try_new_complex_rug_invalid_imag_inf() {
1657 // Test ComplexRugStrictFinite where imaginary part is Infinity (value validation failure)
1658 let real_part = Float::with_val(PRECISION, 1.0);
1659 let inf_imag = Float::with_val(PRECISION, Float::parse("inf").unwrap());
1660 let val = RugComplex::with_val(PRECISION, (real_part, inf_imag));
1661
1662 let res = ComplexRugStrictFinite::<PRECISION>::try_new(val);
1663 assert!(res.is_err());
1664 assert!(matches!(
1665 res,
1666 Err(ErrorsValidationRawComplex::InvalidImaginaryPart {
1667 source: ErrorsValidationRawReal::<Float>::IsPosInfinity { .. }
1668 })
1669 ));
1670 }
1671
1672 #[test]
1673 fn try_new_complex_rug_invalid_both() {
1674 // Test ComplexRugStrictFinite where both parts are invalid (value validation failure)
1675 let nan_real = Float::with_val(PRECISION, Float::parse("nan").unwrap());
1676 let neg_inf_imag =
1677 Float::with_val(PRECISION, Float::parse("-inf").unwrap());
1678 let val = RugComplex::with_val(PRECISION, (nan_real, neg_inf_imag));
1679
1680 let res = ComplexRugStrictFinite::<PRECISION>::try_new(val);
1681 assert!(res.is_err());
1682 assert!(matches!(
1683 res,
1684 Err(ErrorsValidationRawComplex::InvalidBothParts {
1685 real_error: ErrorsValidationRawReal::<Float>::IsNaN { .. },
1686 imag_error: ErrorsValidationRawReal::<Float>::IsNegInfinity { .. },
1687 ..
1688 })
1689 ));
1690 }
1691
1692 #[test]
1693 fn try_from_complex_f64_to_complex_rug() {
1694 // Test valid conversion
1695 let z = num::Complex::new(1., -2.);
1696 let z_rug = ComplexRugStrictFinite::<PRECISION>::try_from(z).unwrap();
1697 let expected_real = RealRugStrictFinite::<PRECISION>::one();
1698 let expected_imaginary =
1699 RealRugStrictFinite::<PRECISION>::try_from_f64(-2.).unwrap();
1700 assert_eq!(z_rug.real_part(), expected_real);
1701 assert_eq!(z_rug.imag_part(), expected_imaginary);
1702
1703 // Test invalid real part
1704 let z = num::Complex::new(f64::INFINITY, -2.0);
1705 let result = ComplexRugStrictFinite::<PRECISION>::try_from(z);
1706 assert!(result.is_err());
1707 assert!(matches!(
1708 result,
1709 Err(ErrorsValidationRawComplex::InvalidRealPart {
1710 source: ErrorsTryFromf64::Output {
1711 source: ErrorsValidationRawReal::IsPosInfinity { .. }
1712 },
1713 })
1714 ));
1715
1716 // Test invalid imaginary part
1717 let z = num::Complex::new(1.0, f64::NAN);
1718 let result = ComplexRugStrictFinite::<PRECISION>::try_from(z).unwrap_err();
1719 //println!("result = {result}");
1720 assert!(matches!(
1721 result,
1722 ErrorsValidationRawComplex::InvalidImaginaryPart {
1723 source: ErrorsTryFromf64::Output {
1724 source: ErrorsValidationRawReal::IsNaN { .. }
1725 },
1726 }
1727 ));
1728 }
1729 }
1730 }
1731 }
1732 }
1733
1734 mod conditional_validation_policy {
1735 use core::f64;
1736
1737 use super::*;
1738
1739 type F64StrictFinitePolicy = Native64RawRealStrictFinitePolicy;
1740
1741 // Define a policy that is strict in debug, and no-op in release.
1742 type MyConditionalPolicy = DebugValidationPolicy<F64StrictFinitePolicy>;
1743
1744 #[test]
1745 #[should_panic(expected = "Debug validation of input value failed: Value is NaN: NaN")]
1746 #[cfg(debug_assertions)]
1747 fn debug_validate_function() {
1748 // This should always succeed, regardless of build mode.
1749 let nan_value = f64::NAN;
1750 debug_validate::<F64StrictFinitePolicy>(&nan_value, "input");
1751 }
1752
1753 #[test]
1754 fn test_conditional_validation_on_valid_value() {
1755 let valid_value = 42.0;
1756 // This should always succeed, regardless of build mode.
1757 assert!(MyConditionalPolicy::validate(valid_value).is_ok());
1758 }
1759
1760 #[test]
1761 fn test_conditional_validation_on_invalid_value() {
1762 let invalid_value = f64::NAN;
1763 let result = MyConditionalPolicy::validate(invalid_value);
1764
1765 if cfg!(debug_assertions) {
1766 // In debug mode, we expect an error from StrictFinitePolicy.
1767 assert!(result.is_err(), "Expected validation to fail in debug mode");
1768 assert!(matches!(result, Err(ErrorsValidationRawReal::IsNaN { .. })));
1769 } else {
1770 // In release mode, we expect it to succeed (no-op).
1771 assert!(
1772 result.is_ok(),
1773 "Expected validation to succeed in release mode"
1774 );
1775 }
1776 }
1777 }
1778}