use uninum::{Number, num};
fn generate_test_numbers() -> Vec<Number> {
let numbers = vec![
Number::from(0_u64),
Number::from(65536_u64),
Number::from(4294967295_u64),
Number::from(-2147483648_i64),
Number::from(0_i64),
Number::from(2147483647_i64),
Number::from(0_u64),
Number::from(4294967296_u64),
Number::from(u64::MAX),
Number::from(i64::MIN),
Number::from(0_i64),
Number::from(i64::MAX),
num!(0.0f64),
num!(-0.0f64),
num!(1.0f64),
num!(-1.0f64),
Number::from(std::f64::consts::PI),
Number::from(-std::f64::consts::PI),
num!(f64::INFINITY),
num!(f64::NEG_INFINITY),
num!(f64::NAN),
num!(0.0f64),
num!(-0.0f64),
num!(1.0f64),
num!(-1.0f64),
Number::from(std::f64::consts::PI),
Number::from(-std::f64::consts::PI),
num!(f64::INFINITY),
num!(f64::NEG_INFINITY),
num!(f64::NAN),
Number::from(42_u64),
Number::from(42_i64),
num!(42.0f64),
num!(42.0f64),
];
#[cfg(feature = "decimal")]
let numbers = {
use rust_decimal::Decimal;
let mut numbers = numbers;
numbers.extend([
Number::from(Decimal::new(0, 0)),
Number::from(Decimal::new(1, 0)),
Number::from(Decimal::new(42, 0)),
Number::from(Decimal::new(314159, 5)), Number::from(Decimal::new(-314159, 5)), ]);
numbers
};
numbers
}
#[test]
fn test_approx_eq_reflexive_property() {
let numbers = generate_test_numbers();
let epsilon = 1e-10;
for a in &numbers {
assert!(
a.approx_eq(a, epsilon, 0.0),
"Approximate equality reflexive property violated for {a:?}"
);
}
}
#[test]
fn test_approx_eq_symmetric_property() {
let numbers = generate_test_numbers();
let epsilon = 1e-10;
for a in &numbers {
for b in &numbers {
if a.approx_eq(b, epsilon, 0.0) {
assert!(
b.approx_eq(a, epsilon, 0.0),
"Approximate equality symmetric property violated: {a:?}.approx_eq({b:?}, \
{epsilon}, 0.0) is true but {b:?}.approx_eq({a:?}, {epsilon}, 0.0) is false"
);
}
}
}
}
#[test]
fn test_approx_eq_with_zero_epsilon() {
let numbers = generate_test_numbers();
for a in &numbers {
for b in &numbers {
let exact_eq = a == b;
let approx_eq = a.approx_eq(b, 0.0, 0.0);
assert_eq!(
exact_eq, approx_eq,
"approx_eq with epsilon=0 should match exact equality for {a:?} and {b:?}"
);
}
}
}
#[test]
fn test_approx_eq_epsilon_scaling() {
let test_cases = vec![
(num!(1.0f64), num!(1.1f64)),
(num!(1.0f64), num!(1.01f64)),
(num!(1.0f64), num!(1.001f64)),
(Number::from(100_u64), Number::from(110_u64)),
(Number::from(100_u64), Number::from(101_u64)),
(num!(0.1f64), num!(0.11f64)),
];
for (a, b) in test_cases {
assert!(
!a.approx_eq(&b, 1e-10, 0.0),
"{a:?} and {b:?} should not be approximately equal with epsilon 1e-10"
);
let epsilon = match (&a, &b) {
(a_val, b_val)
if a_val.try_get_u64() == Some(100) && b_val.try_get_u64() == Some(110) =>
{
10.0
}
(a_val, b_val)
if a_val.try_get_u64() == Some(100) && b_val.try_get_u64() == Some(101) =>
{
1.0
}
_ => 1.0,
};
assert!(
a.approx_eq(&b, epsilon, 0.0),
"{a:?} and {b:?} should be approximately equal with epsilon {epsilon}"
);
}
}
#[test]
fn test_approx_eq_special_values_properties() {
let nan_f64_2 = num!(f64::NAN);
let nan_f64 = num!(f64::NAN);
let normal = num!(1.0f64);
assert!(
nan_f64_2.approx_eq(&nan_f64_2, 1e-10, 0.0),
"NaN should be approximately equal to itself"
);
assert!(
nan_f64.approx_eq(&nan_f64, 1e-10, 0.0),
"NaN should be approximately equal to itself"
);
assert!(
nan_f64_2.approx_eq(&nan_f64, 1e-10, 0.0),
"Different NaN values should be approximately equal"
);
assert!(
!nan_f64_2.approx_eq(&normal, 1e-10, 0.0),
"NaN should not be approximately equal to normal values"
);
assert!(
!normal.approx_eq(&nan_f64_2, 1e-10, 0.0),
"Normal values should not be approximately equal to NaN"
);
let pos_inf = num!(f64::INFINITY);
let neg_inf = num!(f64::NEG_INFINITY);
assert!(
pos_inf.approx_eq(&pos_inf, 1e-10, 0.0),
"Positive infinity should be approximately equal to itself"
);
assert!(
neg_inf.approx_eq(&neg_inf, 1e-10, 0.0),
"Negative infinity should be approximately equal to itself"
);
assert!(
!pos_inf.approx_eq(&neg_inf, 1e-10, 0.0),
"Positive and negative infinity should not be approximately equal"
);
assert!(
!pos_inf.approx_eq(&normal, 1e-10, 0.0),
"Infinity should not be approximately equal to normal values"
);
assert!(
!normal.approx_eq(&pos_inf, 1e-10, 0.0),
"Normal values should not be approximately equal to infinity"
);
}
#[test]
fn test_approx_eq_cross_type_consistency() {
let value_sets = vec![
vec![
Number::from(42_u64),
Number::from(42_i64),
Number::from(42_u64),
Number::from(42_i64),
num!(42.0f64),
num!(42.0f64),
],
vec![
Number::from(0_u64),
Number::from(0_i64),
Number::from(0_u64),
Number::from(0_i64),
num!(0.0f64),
num!(-0.0f64),
num!(0.0f64),
num!(-0.0f64),
],
];
#[cfg(feature = "decimal")]
let value_sets = {
use rust_decimal::Decimal;
let mut value_sets = value_sets;
value_sets[0].push(Number::from(Decimal::new(42, 0)));
value_sets[1].push(Number::from(Decimal::new(0, 0)));
value_sets
};
let epsilon = 1e-10;
for set in value_sets {
for a in &set {
for b in &set {
assert!(
a.approx_eq(b, epsilon, 0.0),
"Cross-type approximate equality failed: {a:?} and {b:?} should be \
approximately equal"
);
}
}
}
}
#[test]
fn test_approx_eq_tolerance_boundaries() {
let a = num!(1.0f64);
let b = num!(1.0 + 1e-10);
assert!(
!a.approx_eq(&b, 1e-11, 0.0),
"Should not be approximately equal with epsilon smaller than difference"
);
assert!(
a.approx_eq(&b, 1e-9, 0.0),
"Should be approximately equal with epsilon larger than difference"
);
assert!(
a.approx_eq(&b, 1.1e-10, 0.0),
"Should be approximately equal with epsilon slightly larger than difference"
);
}
#[test]
fn test_approx_eq_for_f64_precision_comparison() {
let f64_val = num!(std::f64::consts::PI);
let f64_val2 = num!(std::f64::consts::PI);
assert_eq!(f64_val, f64_val2);
let epsilon = 1e-15;
assert!(f64_val.approx_eq(&f64_val2, epsilon, 0.0));
let f64_precise = Number::from(std::f64::consts::PI);
let f64_close = Number::from(std::f64::consts::PI);
assert!(f64_precise.approx_eq(&f64_close, 1e-15, 0.0));
}
#[test]
fn test_approx_eq_cross_type_all_combinations() {
let int_val = Number::from(42_i64);
let float_val = num!(42.0f64);
let f64_val2 = num!(42.0f64);
assert!(int_val.approx_eq(&float_val, 1e-10, 0.0));
assert!(int_val.approx_eq(&f64_val2, 1e-6, 0.0));
assert!(float_val.approx_eq(&f64_val2, 1e-6, 0.0));
#[cfg(feature = "decimal")]
{
use rust_decimal::Decimal;
let decimal_val = Number::from(Decimal::new(42, 0));
assert!(int_val.approx_eq(&decimal_val, 1e-10, 0.0));
assert!(float_val.approx_eq(&decimal_val, 1e-10, 0.0));
assert!(f64_val2.approx_eq(&decimal_val, 1e-6, 0.0));
}
}
#[test]
fn test_approx_eq_epsilon_behavior_validation() {
let a = num!(1.0f64);
let b = num!(1.1f64);
assert!(!a.approx_eq(&b, 1e-10, 0.0));
assert!(a.approx_eq(&b, 0.2, 0.0));
let c = num!(1.0f64);
let d = num!(1.0 + 1e-10);
assert!(c.approx_eq(&d, 1e-9, 0.0)); }
#[test]
fn test_approx_eq_special_float_validation() {
let nan1 = num!(f64::NAN);
let nan2 = num!(f64::NAN);
let inf = num!(f64::INFINITY);
let neg_inf = num!(f64::NEG_INFINITY);
assert!(nan1.approx_eq(&nan2, 1e-10, 0.0));
assert!(inf.approx_eq(&inf, 1e-10, 0.0));
assert!(neg_inf.approx_eq(&neg_inf, 1e-10, 0.0));
assert!(!inf.approx_eq(&neg_inf, 1e-10, 0.0));
}
#[test]
fn test_approx_eq_with_different_number_magnitudes() {
let small = num!(1e-10f64);
let large = num!(1e10f64);
let zero = num!(0.0f64);
assert!(!small.approx_eq(&large, 1e-5, 0.0));
assert!(small.approx_eq(&large, 1e11, 0.0));
assert!(zero.approx_eq(&small, 1e-9, 0.0));
assert!(!zero.approx_eq(&small, 1e-11, 0.0));
}
#[test]
fn test_approx_eq_integer_precision() {
let int_val = Number::from(9007199254740993_u64); let float_val = num!(9007199254740993.0f64);
if int_val != float_val {
assert!(
int_val.approx_eq(&float_val, 2.0, 0.0),
"Large integer and its float representation should be approximately equal"
);
}
}
#[test]
fn test_approx_eq_rel_basic_functionality() {
let a = num!(1.0f64);
let b = num!(1.1f64);
assert!(!a.approx_eq(&b, 1e-10, 1e-10));
assert!(a.approx_eq(&b, 0.0, 0.15));
assert!(a.approx_eq(&b, 0.2, 0.0));
assert!(a.approx_eq(&b, 0.05, 0.05)); }
#[test]
fn test_approx_eq_rel_small_numbers() {
let a = num!(1e-10f64);
let b = num!(2e-10f64);
assert!(a.approx_eq(&b, 1.5e-10, 0.5)); assert!(!a.approx_eq(&b, 5e-11, 0.2)); }
#[test]
fn test_approx_eq_rel_large_numbers() {
let a = num!(1000000.0f64);
let b = num!(1000001.0f64);
assert!(a.approx_eq(&b, 0.0, 1e-5)); assert!(!a.approx_eq(&b, 0.5, 0.0)); assert!(a.approx_eq(&b, 0.1, 1e-5)); }
#[test]
fn test_approx_eq_rel_financial_examples() {
let price1 = num!(19.99f64);
let price2 = num!(19.991f64);
assert!(price1.approx_eq(&price2, 0.01, 0.001));
assert!(!price1.approx_eq(&price2, 0.0001, 0.00001));
let amount1 = num!(1234.567f64);
let amount2 = num!(1234.569f64);
assert!(amount1.approx_eq(&amount2, 0.01, 0.0));
let rate1 = num!(0.0525f64); let rate2 = num!(0.052501f64); assert!(rate1.approx_eq(&rate2, 0.0001, 0.001)); }
#[test]
fn test_approx_eq_rel_scientific_calculations() {
let measurement1 = num!(1.23456789e10f64);
let measurement2 = num!(1.23456790e10f64);
assert!(measurement1.approx_eq(&measurement2, 0.0, 1e-8));
assert!(!measurement1.approx_eq(&measurement2, 0.0, 1e-9));
let big1 = num!(6.022e23f64); let big2 = num!(6.023e23f64);
assert!(big1.approx_eq(&big2, 0.0, 0.002)); assert!(!big1.approx_eq(&big2, 0.0, 0.0001)); }
#[test]
fn test_approx_eq_rel_special_values() {
let normal = num!(1.0f64);
let nan = num!(f64::NAN);
let pos_inf = num!(f64::INFINITY);
let neg_inf = num!(f64::NEG_INFINITY);
assert!(nan.approx_eq(&nan, 1e-10, 1e-10));
assert!(!nan.approx_eq(&normal, 1e-10, 1e-10));
assert!(!normal.approx_eq(&nan, 1e-10, 1e-10));
assert!(pos_inf.approx_eq(&pos_inf, 1e-10, 1e-10));
assert!(neg_inf.approx_eq(&neg_inf, 1e-10, 1e-10));
assert!(!pos_inf.approx_eq(&neg_inf, 1e-10, 1e-10));
assert!(!pos_inf.approx_eq(&normal, 1e-10, 1e-10));
assert!(!normal.approx_eq(&pos_inf, 1e-10, 1e-10));
assert!(!neg_inf.approx_eq(&normal, 1e-10, 1e-10));
assert!(!normal.approx_eq(&neg_inf, 1e-10, 1e-10));
}
#[test]
fn test_approx_eq_rel_zero_handling() {
let zero = num!(0.0f64);
let small_pos = num!(1e-10f64);
let small_neg = num!(-1e-10f64);
assert!(zero.approx_eq(&small_pos, 2e-10, 1.0)); assert!(!zero.approx_eq(&small_pos, 5e-11, 0.3));
let neg_zero = num!(-0.0f64);
assert!(zero.approx_eq(&neg_zero, 0.0, 0.0));
assert!(small_pos.approx_eq(&small_neg, 3e-10, 0.0));
assert!(!small_pos.approx_eq(&small_neg, 1e-10, 0.0));
}
#[test]
fn test_approx_eq_rel_pure_absolute_mode() {
let a = num!(1000.0f64);
let b = num!(1001.0f64);
assert!(a.approx_eq(&b, 2.0, 0.0)); assert!(!a.approx_eq(&b, 0.5, 0.0));
let c = num!(1.0f64);
let d = num!(1.1f64);
assert_eq!(c.approx_eq(&d, 0.2, 0.0), c.approx_eq(&d, 0.2, 0.0));
assert_eq!(c.approx_eq(&d, 0.05, 0.0), c.approx_eq(&d, 0.05, 0.0));
}
#[test]
fn test_approx_eq_rel_pure_relative_mode() {
let a = num!(1000.0f64);
let b = num!(1010.0f64);
assert!(a.approx_eq(&b, 0.0, 0.02)); assert!(!a.approx_eq(&b, 0.0, 0.005));
let c = num!(10.0f64);
let d = num!(10.1f64);
assert!(c.approx_eq(&d, 0.0, 0.02)); assert!(!c.approx_eq(&d, 0.0, 0.005)); }
#[test]
fn test_approx_eq_rel_symmetry_property() {
let test_pairs = vec![
(num!(1.0f64), num!(1.1f64)),
(num!(1000.0f64), num!(1001.0f64)),
(num!(1e-10f64), num!(2e-10f64)),
(Number::from(42_u64), num!(42.1f64)),
(Number::from(-100_i64), num!(-100.01f64)),
];
let abs_tol = 0.1;
let rel_tol = 0.01;
for (a, b) in test_pairs {
let a_to_b = a.approx_eq(&b, abs_tol, rel_tol);
let b_to_a = b.approx_eq(&a, abs_tol, rel_tol);
assert_eq!(
a_to_b, b_to_a,
"Symmetry property violated for {a:?} and {b:?}"
);
}
}
#[test]
fn test_approx_eq_rel_reflexivity_property() {
let numbers = generate_test_numbers();
for num in numbers {
assert!(
num.approx_eq(&num, 1e-10, 1e-10),
"Reflexivity property violated for {num:?}"
);
assert!(
num.approx_eq(&num, 0.0, 1e-10),
"Reflexivity property violated for {num:?} (pure relative)"
);
assert!(
num.approx_eq(&num, 1e-10, 0.0),
"Reflexivity property violated for {num:?} (pure absolute)"
);
}
}
#[test]
fn test_approx_eq_rel_cross_type_consistency() {
let int_val = Number::from(1000_u64);
let float_val = num!(1000.0f64);
let close_float = num!(1001.0f64);
assert!(int_val.approx_eq(&float_val, 1e-10, 1e-10));
assert!(int_val.approx_eq(&close_float, 2.0, 0.002)); assert!(!int_val.approx_eq(&close_float, 0.5, 0.0003));
#[cfg(feature = "decimal")]
{
use rust_decimal::Decimal;
let decimal_val = Number::from(Decimal::new(1000, 0));
let close_decimal = Number::from(Decimal::new(10001, 1));
assert!(int_val.approx_eq(&decimal_val, 1e-10, 1e-10));
assert!(float_val.approx_eq(&decimal_val, 1e-10, 1e-10));
assert!(int_val.approx_eq(&close_decimal, 0.2, 0.001));
}
}
#[test]
fn test_approx_eq_rel_edge_cases() {
let a = num!(1.0f64);
let b = num!(1.0000001f64);
assert!(a.approx_eq(&b, 0.0, 1e-6)); assert!(!a.approx_eq(&b, 0.0, 1e-8));
let c = num!(1000.0f64);
let d = num!(1000.0001f64);
assert!(c.approx_eq(&d, 1e-3, 0.0)); assert!(!c.approx_eq(&d, 1e-5, 0.0));
let abs_tol = 1e-6;
let rel_tol = 1e-6;
let e = num!(1.0f64);
let f = num!(1.0000005f64);
assert!(e.approx_eq(&f, abs_tol, rel_tol));
let g = num!(1e6f64);
let h = num!(1e6 + 0.5);
assert!(g.approx_eq(&h, abs_tol, rel_tol)); assert!(!g.approx_eq(&h, abs_tol, 0.0)); }
#[test]
fn test_approx_eq_rel_performance_equivalence() {
let test_cases = vec![
(num!(1.0f64), num!(1.1f64), 0.2),
(num!(1.0f64), num!(1.1f64), 0.05),
(Number::from(100_u64), Number::from(101_u64), 2.0),
(Number::from(100_u64), Number::from(105_u64), 3.0),
(num!(0.0f64), num!(1e-10f64), 2e-10),
(num!(f64::NAN), num!(1.0f64), 1e-6),
(num!(f64::INFINITY), num!(f64::INFINITY), 1e-6),
];
for (a, b, epsilon) in test_cases {
let original = a.approx_eq(&b, epsilon, 0.0);
let new_pure_abs = a.approx_eq(&b, epsilon, 0.0);
assert_eq!(
original, new_pure_abs,
"approx_eq({epsilon}) should equal approx_eq({epsilon}, 0.0) for {a:?} and {b:?}"
);
}
}
#[test]
fn test_approx_eq_rel_tolerance_scaling() {
let base_cases = vec![
(1.0f64, 1.01f64), (10.0f64, 10.1f64), (100.0f64, 101.0f64), (1000.0f64, 1010.0f64), ];
let rel_tolerance = 0.015;
for (base, modified) in base_cases {
let a = num!(base);
let b = num!(modified);
assert!(
a.approx_eq(&b, 0.0, rel_tolerance),
"1% difference should be within 1.5% tolerance for {base} and {modified}"
);
assert!(
!a.approx_eq(&b, 0.0, 0.005),
"1% difference should not be within 0.5% tolerance for {base} and {modified}"
);
}
}