1use num_traits::Float;
16use std::fmt::Debug;
17
18use crate::error::{CoreError, CoreResult, ErrorContext};
19
20pub trait MachineConstants: Float + Debug {
29 fn machine_epsilon() -> Self;
31
32 fn min_positive_normal() -> Self;
34
35 fn max_finite() -> Self;
37
38 fn min_positive_subnormal() -> Self;
40
41 fn mantissa_digits() -> u32;
43
44 fn radix() -> u32 {
46 2
47 }
48}
49
50impl MachineConstants for f32 {
51 fn machine_epsilon() -> Self {
52 f32::EPSILON
53 }
54
55 fn min_positive_normal() -> Self {
56 f32::MIN_POSITIVE
57 }
58
59 fn max_finite() -> Self {
60 f32::MAX
61 }
62
63 fn min_positive_subnormal() -> Self {
64 1.401_298_4e-45_f32
68 }
69
70 fn mantissa_digits() -> u32 {
71 f32::MANTISSA_DIGITS
72 }
73}
74
75impl MachineConstants for f64 {
76 fn machine_epsilon() -> Self {
77 f64::EPSILON
78 }
79
80 fn min_positive_normal() -> Self {
81 f64::MIN_POSITIVE
82 }
83
84 fn max_finite() -> Self {
85 f64::MAX
86 }
87
88 fn min_positive_subnormal() -> Self {
89 5e-324_f64
91 }
92
93 fn mantissa_digits() -> u32 {
94 f64::MANTISSA_DIGITS
95 }
96}
97
98pub fn approx_eq<T: Float>(a: T, b: T, tol: T) -> bool {
118 if a.is_nan() || b.is_nan() {
119 return false;
120 }
121 if a == b {
123 return true;
124 }
125 (a - b).abs() <= tol
126}
127
128pub fn approx_eq_relative<T: Float>(a: T, b: T, rel_tol: T, abs_tol: T) -> bool {
134 if a.is_nan() || b.is_nan() {
135 return false;
136 }
137 if a == b {
138 return true;
139 }
140 let diff = (a - b).abs();
141 let max_abs = a.abs().max(b.abs());
142 diff <= rel_tol * max_abs || diff <= abs_tol
143}
144
145pub fn relative_error<T: Float + Debug>(computed: T, exact: T) -> CoreResult<T> {
159 if computed.is_nan() || exact.is_nan() {
160 return Err(CoreError::ValueError(ErrorContext::new(
161 "relative_error: NaN input is not allowed",
162 )));
163 }
164 if exact.is_zero() {
165 return Err(CoreError::DomainError(ErrorContext::new(
166 "relative_error: exact value is zero, relative error is undefined",
167 )));
168 }
169 Ok((computed - exact).abs() / exact.abs())
170}
171
172pub fn relative_error_safe<T: Float + Debug>(computed: T, exact: T, floor: T) -> CoreResult<T> {
176 if computed.is_nan() || exact.is_nan() || floor.is_nan() {
177 return Err(CoreError::ValueError(ErrorContext::new(
178 "relative_error_safe: NaN input is not allowed",
179 )));
180 }
181 let denom = exact.abs().max(floor);
182 if denom.is_zero() {
183 return Err(CoreError::DomainError(ErrorContext::new(
184 "relative_error_safe: denominator is zero even with floor",
185 )));
186 }
187 Ok((computed - exact).abs() / denom)
188}
189
190pub fn ulp_distance(a: f64, b: f64) -> CoreResult<u64> {
208 if a.is_nan() || b.is_nan() {
209 return Err(CoreError::ValueError(ErrorContext::new(
210 "ulp_distance: NaN input is not allowed",
211 )));
212 }
213 if a == b {
214 return Ok(0);
215 }
216
217 let a_bits = a.to_bits() as i64;
218 let b_bits = b.to_bits() as i64;
219
220 let a_mapped = if a_bits < 0 {
224 i64::MIN - a_bits
225 } else {
226 a_bits
227 };
228 let b_mapped = if b_bits < 0 {
229 i64::MIN - b_bits
230 } else {
231 b_bits
232 };
233
234 Ok((a_mapped - b_mapped).unsigned_abs())
235}
236
237pub fn ulp_distance_f32(a: f32, b: f32) -> CoreResult<u32> {
239 if a.is_nan() || b.is_nan() {
240 return Err(CoreError::ValueError(ErrorContext::new(
241 "ulp_distance_f32: NaN input is not allowed",
242 )));
243 }
244 if a == b {
245 return Ok(0);
246 }
247
248 let a_bits = a.to_bits() as i32;
249 let b_bits = b.to_bits() as i32;
250
251 let a_mapped = if a_bits < 0 {
252 i32::MIN - a_bits
253 } else {
254 a_bits
255 };
256 let b_mapped = if b_bits < 0 {
257 i32::MIN - b_bits
258 } else {
259 b_bits
260 };
261
262 Ok((a_mapped - b_mapped).unsigned_abs())
263}
264
265pub fn is_finite_and_positive<T: Float>(x: T) -> bool {
273 x.is_finite() && x > T::zero()
274}
275
276pub fn is_finite_and_non_negative<T: Float>(x: T) -> bool {
278 x.is_finite() && x >= T::zero()
279}
280
281pub fn clamp_to_range<T: Float + Debug>(x: T, min: T, max: T) -> CoreResult<T> {
296 if x.is_nan() {
297 return Err(CoreError::ValueError(ErrorContext::new(
298 "clamp_to_range: input is NaN",
299 )));
300 }
301 if min > max {
302 return Err(CoreError::DomainError(ErrorContext::new(format!(
303 "clamp_to_range: min ({min:?}) > max ({max:?})"
304 ))));
305 }
306 if x < min {
307 Ok(min)
308 } else if x > max {
309 Ok(max)
310 } else {
311 Ok(x)
312 }
313}
314
315pub fn slices_approx_eq<T: Float>(a: &[T], b: &[T], tol: T) -> bool {
321 if a.len() != b.len() {
322 return false;
323 }
324 a.iter().zip(b.iter()).all(|(&x, &y)| approx_eq(x, y, tol))
325}
326
327pub fn max_abs_difference<T: Float>(a: &[T], b: &[T]) -> CoreResult<T> {
331 if a.len() != b.len() {
332 return Err(CoreError::DimensionError(ErrorContext::new(format!(
333 "max_abs_difference: length mismatch ({} vs {})",
334 a.len(),
335 b.len()
336 ))));
337 }
338 if a.is_empty() {
339 return Ok(T::zero());
340 }
341 let max = a
342 .iter()
343 .zip(b.iter())
344 .map(|(&x, &y)| (x - y).abs())
345 .fold(T::zero(), |acc, d| if d > acc { d } else { acc });
346 Ok(max)
347}
348
349#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
360 fn test_approx_eq_basic() {
361 assert!(approx_eq(1.0_f64, 1.0 + 1e-11, 1e-10));
362 assert!(!approx_eq(1.0_f64, 1.0 + 1e-9, 1e-10));
363 }
364
365 #[test]
366 fn test_approx_eq_nan() {
367 assert!(!approx_eq(f64::NAN, 1.0, 1e-10));
368 assert!(!approx_eq(1.0, f64::NAN, 1e-10));
369 assert!(!approx_eq(f64::NAN, f64::NAN, 1e-10));
370 }
371
372 #[test]
373 fn test_approx_eq_infinity() {
374 assert!(approx_eq(f64::INFINITY, f64::INFINITY, 1e-10));
375 assert!(approx_eq(f64::NEG_INFINITY, f64::NEG_INFINITY, 1e-10));
376 assert!(!approx_eq(f64::INFINITY, f64::NEG_INFINITY, 1e-10));
377 }
378
379 #[test]
380 fn test_approx_eq_zero() {
381 assert!(approx_eq(0.0_f64, 0.0, 0.0));
382 assert!(approx_eq(0.0_f64, 1e-16, 1e-15));
383 }
384
385 #[test]
386 fn test_approx_eq_f32() {
387 assert!(approx_eq(1.0_f32, 1.0 + 1e-6, 1e-5));
388 assert!(!approx_eq(1.0_f32, 1.0 + 1e-4, 1e-5));
389 }
390
391 #[test]
392 fn test_approx_eq_relative_basic() {
393 assert!(approx_eq_relative(100.0_f64, 100.001, 1e-4, 1e-10));
394 assert!(!approx_eq_relative(100.0_f64, 101.0, 1e-4, 1e-10));
395 }
396
397 #[test]
400 fn test_relative_error_basic() {
401 let err = relative_error(1.01_f64, 1.0).expect("should succeed");
402 assert!(approx_eq(err, 0.01, 1e-14));
403 }
404
405 #[test]
406 fn test_relative_error_exact() {
407 let err = relative_error(1.23_f64, 1.23).expect("should succeed");
408 assert_eq!(err, 0.0);
409 }
410
411 #[test]
412 fn test_relative_error_zero_exact() {
413 let result = relative_error(1.0_f64, 0.0);
414 assert!(result.is_err());
415 }
416
417 #[test]
418 fn test_relative_error_nan() {
419 assert!(relative_error(f64::NAN, 1.0).is_err());
420 assert!(relative_error(1.0, f64::NAN).is_err());
421 }
422
423 #[test]
424 fn test_relative_error_safe_near_zero() {
425 let err = relative_error_safe(0.001_f64, 0.0, 1.0).expect("should succeed");
426 assert!(approx_eq(err, 0.001, 1e-14));
427 }
428
429 #[test]
432 fn test_ulp_distance_same() {
433 assert_eq!(ulp_distance(1.0, 1.0).expect("should succeed"), 0);
434 }
435
436 #[test]
437 fn test_ulp_distance_adjacent() {
438 let a = 1.0_f64;
439 let b = f64::from_bits(a.to_bits() + 1);
440 assert_eq!(ulp_distance(a, b).expect("should succeed"), 1);
441 }
442
443 #[test]
444 fn test_ulp_distance_symmetric() {
445 let a = 1.0_f64;
446 let b = 1.0 + f64::EPSILON;
447 let d1 = ulp_distance(a, b).expect("should succeed");
448 let d2 = ulp_distance(b, a).expect("should succeed");
449 assert_eq!(d1, d2);
450 }
451
452 #[test]
453 fn test_ulp_distance_nan() {
454 assert!(ulp_distance(f64::NAN, 1.0).is_err());
455 }
456
457 #[test]
458 fn test_ulp_distance_across_zero() {
459 let d = ulp_distance(-0.0_f64, 0.0).expect("should succeed");
461 assert_eq!(d, 0);
463 }
464
465 #[test]
466 fn test_ulp_distance_f32_basic() {
467 let a = 1.0_f32;
468 let b = f32::from_bits(a.to_bits() + 1);
469 assert_eq!(ulp_distance_f32(a, b).expect("should succeed"), 1);
470 }
471
472 #[test]
473 fn test_ulp_distance_f32_nan() {
474 assert!(ulp_distance_f32(f32::NAN, 1.0).is_err());
475 }
476
477 #[test]
480 fn test_is_finite_and_positive_basic() {
481 assert!(is_finite_and_positive(1.0_f64));
482 assert!(is_finite_and_positive(f64::MIN_POSITIVE));
483 assert!(!is_finite_and_positive(0.0_f64));
484 assert!(!is_finite_and_positive(-1.0_f64));
485 assert!(!is_finite_and_positive(f64::INFINITY));
486 assert!(!is_finite_and_positive(f64::NAN));
487 }
488
489 #[test]
490 fn test_is_finite_and_positive_f32() {
491 assert!(is_finite_and_positive(0.001_f32));
492 assert!(!is_finite_and_positive(f32::NEG_INFINITY));
493 }
494
495 #[test]
496 fn test_is_finite_and_non_negative() {
497 assert!(is_finite_and_non_negative(0.0_f64));
498 assert!(is_finite_and_non_negative(1.0_f64));
499 assert!(!is_finite_and_non_negative(-0.001_f64));
500 assert!(!is_finite_and_non_negative(f64::NAN));
501 }
502
503 #[test]
506 fn test_clamp_to_range_normal() {
507 assert_eq!(clamp_to_range(5.0_f64, 0.0, 10.0).expect("ok"), 5.0);
508 }
509
510 #[test]
511 fn test_clamp_to_range_below() {
512 assert_eq!(clamp_to_range(-5.0_f64, 0.0, 10.0).expect("ok"), 0.0);
513 }
514
515 #[test]
516 fn test_clamp_to_range_above() {
517 assert_eq!(clamp_to_range(15.0_f64, 0.0, 10.0).expect("ok"), 10.0);
518 }
519
520 #[test]
521 fn test_clamp_to_range_nan() {
522 let result = clamp_to_range(f64::NAN, 0.0, 10.0);
523 assert!(result.is_err());
524 }
525
526 #[test]
527 fn test_clamp_to_range_inverted() {
528 let result = clamp_to_range(5.0_f64, 10.0, 0.0);
529 assert!(result.is_err());
530 }
531
532 #[test]
533 fn test_clamp_to_range_exact_boundary() {
534 assert_eq!(clamp_to_range(0.0_f64, 0.0, 10.0).expect("ok"), 0.0);
535 assert_eq!(clamp_to_range(10.0_f64, 0.0, 10.0).expect("ok"), 10.0);
536 }
537
538 #[test]
539 fn test_clamp_to_range_f32() {
540 assert_eq!(clamp_to_range(100.0_f32, -1.0, 1.0).expect("ok"), 1.0);
541 }
542
543 #[test]
546 fn test_machine_constants_f64() {
547 assert_eq!(f64::machine_epsilon(), f64::EPSILON);
548 assert_eq!(f64::min_positive_normal(), f64::MIN_POSITIVE);
549 assert_eq!(f64::max_finite(), f64::MAX);
550 assert_eq!(f64::mantissa_digits(), 53);
551 assert_eq!(f64::radix(), 2);
552 assert!(f64::min_positive_subnormal() > 0.0);
553 assert!(f64::min_positive_subnormal() < f64::MIN_POSITIVE);
554 }
555
556 #[test]
557 fn test_machine_constants_f32() {
558 assert_eq!(f32::machine_epsilon(), f32::EPSILON);
559 assert_eq!(f32::min_positive_normal(), f32::MIN_POSITIVE);
560 assert_eq!(f32::max_finite(), f32::MAX);
561 assert_eq!(f32::mantissa_digits(), 24);
562 assert!(f32::min_positive_subnormal() > 0.0);
563 assert!(f32::min_positive_subnormal() < f32::MIN_POSITIVE);
564 }
565
566 #[test]
569 fn test_slices_approx_eq_basic() {
570 let a = [1.0_f64, 2.0, 3.0];
571 let b = [1.0 + 1e-12, 2.0 - 1e-12, 3.0];
572 assert!(slices_approx_eq(&a, &b, 1e-10));
573 }
574
575 #[test]
576 fn test_slices_approx_eq_different_lengths() {
577 let a = [1.0_f64, 2.0];
578 let b = [1.0_f64];
579 assert!(!slices_approx_eq(&a, &b, 1e-10));
580 }
581
582 #[test]
583 fn test_slices_approx_eq_not_equal() {
584 let a = [1.0_f64, 2.0];
585 let b = [1.0_f64, 3.0];
586 assert!(!slices_approx_eq(&a, &b, 0.5));
587 }
588
589 #[test]
592 fn test_max_abs_difference_basic() {
593 let a = [1.0_f64, 2.0, 3.0];
594 let b = [1.1, 2.0, 2.5];
595 let d = max_abs_difference(&a, &b).expect("ok");
596 assert!(approx_eq(d, 0.5, 1e-14));
597 }
598
599 #[test]
600 fn test_max_abs_difference_empty() {
601 let a: [f64; 0] = [];
602 let b: [f64; 0] = [];
603 assert_eq!(max_abs_difference(&a, &b).expect("ok"), 0.0);
604 }
605
606 #[test]
607 fn test_max_abs_difference_length_mismatch() {
608 let a = [1.0_f64];
609 let b = [1.0_f64, 2.0];
610 assert!(max_abs_difference(&a, &b).is_err());
611 }
612}