use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[cfg(feature = "decimal")]
use std::str::FromStr;
use proptest::prelude::*;
use uninum::{Number, num};
fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut hasher = DefaultHasher::new();
t.hash(&mut hasher);
hasher.finish()
}
fn number_strategy() -> impl Strategy<Value = Number> {
prop_oneof![
any::<u32>().prop_map(|x| Number::from(u64::from(x))),
any::<i32>().prop_map(|x| Number::from(i64::from(x))),
any::<u32>().prop_map(|x| Number::from(u64::from(x))),
any::<i32>().prop_map(|x| Number::from(i64::from(x))),
any::<u32>().prop_map(|x| Number::from(u64::from(x))),
any::<i32>().prop_map(|x| Number::from(i64::from(x))),
any::<u64>().prop_map(Number::from),
any::<i64>().prop_map(Number::from),
any::<f64>()
.prop_filter("finite f64", |f| f.is_finite())
.prop_map(|f| num!(f)),
any::<f64>()
.prop_filter("finite f64", |f| f.is_finite())
.prop_map(|f| num!(f)),
]
}
fn equivalent_number_pairs() -> impl Strategy<Value = (Number, Number)> {
prop_oneof![
(0i32..=127i32).prop_map(|i| {
let variants = [
Number::from(u64::from(i as u32)),
Number::from(i64::from(i)),
Number::from(u64::from(i as u32)),
Number::from(i64::from(i)),
Number::from(u64::from(i as u32)),
Number::from(i64::from(i)),
Number::from(i as u64),
Number::from(i as i64),
num!(i as f64),
num!(i as f64),
];
let idx1 = (i as usize) % variants.len();
let idx2 = ((i as usize) + 1) % variants.len();
(variants[idx1].clone(), variants[idx2].clone())
}),
Just((Number::from(0i64), num!(0.0f64))),
Just((Number::from(0u64), num!(0.0f64))),
Just((Number::from(0i64), Number::from(0u64))),
Just((Number::from(1i64), num!(1.0f64))),
Just((Number::from(1u64), num!(1.0f64))),
Just((Number::from(1i64), Number::from(1u64))),
]
}
fn special_float_strategy() -> impl Strategy<Value = Number> {
prop_oneof![
Just(num!(f64::NAN)),
Just(num!(f64::NAN)),
Just(num!(f64::INFINITY)),
Just(num!(f64::INFINITY)),
Just(num!(f64::NEG_INFINITY)),
Just(num!(f64::NEG_INFINITY)),
Just(num!(0.0f64)),
Just(num!(-0.0f64)),
Just(num!(0.0f64)),
Just(num!(-0.0f64)),
]
}
#[cfg(feature = "decimal")]
fn decimal_strategy() -> impl Strategy<Value = Number> {
use rust_decimal::Decimal;
prop_oneof![
any::<i32>().prop_map(|i| Number::from(Decimal::new(i.abs() as i64, 0))),
(any::<i32>(), 0u32..=28u32)
.prop_map(|(i, scale)| Number::from(Decimal::new(i.abs() as i64, scale))),
]
}
proptest! {
#[test]
fn prop_hash_consistency(a in number_strategy(), b in number_strategy()) {
if a == b {
prop_assert_eq!(calculate_hash(&a), calculate_hash(&b));
}
}
#[test]
fn prop_equivalent_pairs_hash_same((a, b) in equivalent_number_pairs()) {
if a == b {
prop_assert_eq!(
calculate_hash(&a),
calculate_hash(&b),
"Equivalent numbers {:?} and {:?} should have the same hash",
a, b
);
}
}
#[test]
fn prop_cross_type_integer_consistency(val in 0i32..=127i32) {
let numbers = [Number::from(u64::from(val as u32)),
Number::from(i64::from(val)),
Number::from(u64::from(val as u32)),
Number::from(i64::from(val)),
Number::from(u64::from(val as u32)),
Number::from(i64::from(val)),
Number::from(val as u64),
Number::from(val as i64),
num!(val as f64),
num!(val as f64)];
let first_hash = calculate_hash(&numbers[0]);
for num in &numbers[1..] {
if numbers[0] == *num {
prop_assert_eq!(
first_hash,
calculate_hash(num),
"All equivalent representations of {} should hash the same",
val
);
}
}
}
#[test]
fn prop_special_float_consistency(special in special_float_strategy()) {
let hash1 = calculate_hash(&special);
let hash2 = calculate_hash(&special);
prop_assert_eq!(hash1, hash2, "Hash should be consistent for special value {:?}", special);
}
#[test]
fn prop_hashmap_consistency(pairs in prop::collection::vec(equivalent_number_pairs(), 1..10)) {
use std::collections::HashMap;
for (a, b) in pairs {
if a == b {
let mut map = HashMap::new();
map.insert(a.clone(), "first");
map.insert(b.clone(), "second");
prop_assert_eq!(map.len(), 1, "Equal values should result in one HashMap entry");
prop_assert_eq!(map.get(&a), map.get(&b), "Equal keys should resolve to same value");
}
}
}
#[test]
#[cfg(feature = "decimal")]
fn prop_decimal_consistency(dec in decimal_strategy()) {
if let Some(arc_dec) = dec.try_get_decimal() {
let decimal_val = arc_dec.as_ref();
if decimal_val.scale() == 0
&& let Ok(int_val) = decimal_val.to_string().parse::<i64>()
&& int_val >= 0 && int_val <= i32::MAX as i64 {
let int_num = Number::from(i64::from(int_val as i32));
if dec == int_num {
prop_assert_eq!(
calculate_hash(&dec),
calculate_hash(&int_num),
"Decimal {} should hash the same as integer equivalent",
decimal_val
);
}
}
}
}
}
#[test]
fn prop_nan_consistency() {
let nan_f64 = num!(f64::NAN);
assert_eq!(
calculate_hash(&nan_f64),
calculate_hash(&nan_f64),
"All NaN values should hash the same"
);
}
#[test]
fn prop_infinity_consistency() {
let pos_inf_f64 = num!(f64::INFINITY);
assert_eq!(
calculate_hash(&pos_inf_f64),
calculate_hash(&pos_inf_f64),
"Positive infinities should hash the same"
);
let neg_inf_f64 = num!(f64::NEG_INFINITY);
assert_eq!(
calculate_hash(&neg_inf_f64),
calculate_hash(&neg_inf_f64),
"Negative infinities should hash the same"
);
assert_ne!(
calculate_hash(&pos_inf_f64),
calculate_hash(&neg_inf_f64),
"Positive and negative infinities should hash differently"
);
}
#[test]
fn prop_zero_consistency() {
let pos_zero_f64_2 = num!(0.0f64);
let neg_zero_f64_2 = num!(-0.0f64);
let pos_zero_f64 = num!(0.0f64);
let neg_zero_f64 = num!(-0.0f64);
assert_eq!(
calculate_hash(&pos_zero_f64_2),
calculate_hash(&neg_zero_f64_2),
"+0.0 and -0.0 f64 should hash the same"
);
assert_eq!(
calculate_hash(&pos_zero_f64),
calculate_hash(&neg_zero_f64),
"+0.0 and -0.0 f64 should hash the same"
);
}
fn generate_test_numbers() -> Vec<Number> {
let numbers = vec![
Number::from(0u64),
Number::from(1u64),
Number::from(42u64),
Number::from(255u64),
Number::from(-128i64),
Number::from(-1i64),
Number::from(0i64),
Number::from(1i64),
Number::from(127i64),
Number::from(0u64),
Number::from(256u64),
Number::from(65535u64),
Number::from(-32768i64),
Number::from(0i64),
Number::from(32767i64),
Number::from(0u64),
Number::from(65536u64),
Number::from(4294967295u64),
Number::from(-2147483648i64),
Number::from(0i64),
Number::from(2147483647i64),
Number::from(0u64),
Number::from(4294967296u64),
Number::from(u64::MAX),
Number::from(i64::MIN),
Number::from(0i64),
Number::from(i64::MAX),
num!(0.0f64),
num!(-0.0f64),
num!(1.0f64),
num!(-1.0f64),
num!(3.15159f64),
num!(-3.15159f64),
num!(f64::INFINITY),
num!(f64::NEG_INFINITY),
num!(f64::NAN),
num!(0.0f64),
num!(-0.0f64),
num!(1.0f64),
num!(-1.0f64),
num!(std::f64::consts::PI),
Number::from(-std::f64::consts::PI),
num!(f64::INFINITY),
num!(f64::NEG_INFINITY),
num!(f64::NAN),
Number::from(42u64),
Number::from(42i64),
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_hash_consistency_property() {
let numbers = generate_test_numbers();
for (i, a) in numbers.iter().enumerate() {
for (j, b) in numbers.iter().enumerate() {
if i != j && a == b {
let hash_a = calculate_hash(a);
let hash_b = calculate_hash(b);
assert_eq!(
hash_a, hash_b,
"Hash consistency violated: {a:?} == {b:?} but hash({a:?}) = {hash_a} != \
{hash_b} = hash({b:?})"
);
}
}
}
}
#[test]
fn test_hash_distribution_quality() {
let numbers = generate_test_numbers();
let mut hashes = std::collections::HashMap::new();
let mut collisions = 0;
for num in &numbers {
let hash = calculate_hash(num);
if let Some(existing) = hashes.get(&hash) {
if existing != num {
collisions += 1;
}
} else {
hashes.insert(hash, num.clone());
}
}
let collision_rate = collisions as f64 / numbers.len() as f64;
assert!(
collision_rate < 0.1,
"Too many hash collisions: {} out of {} values ({}%)",
collisions,
numbers.len(),
collision_rate * 100.0
);
}
#[test]
fn test_cross_type_hash_consistency() {
let equivalent_groups = vec![
vec![
Number::from(1u64),
Number::from(1i64),
Number::from(1u64),
Number::from(1i64),
Number::from(1u64),
Number::from(1i64),
Number::from(1u64),
Number::from(1i64),
num!(1.0f64),
num!(1.0f64),
],
vec![
Number::from(0u64),
Number::from(0i64),
Number::from(0u64),
Number::from(0i64),
Number::from(0u64),
Number::from(0i64),
Number::from(0u64),
Number::from(0i64),
num!(0.0f64),
num!(0.0f64),
],
vec![
Number::from(42u64),
Number::from(42i64),
Number::from(42u64),
Number::from(42i64),
Number::from(42u64),
Number::from(42i64),
Number::from(42u64),
Number::from(42i64),
num!(42.0f64),
num!(42.0f64),
],
];
#[cfg(feature = "decimal")]
let equivalent_groups = {
use rust_decimal::Decimal;
let mut equivalent_groups = equivalent_groups;
equivalent_groups[0].push(Number::from(Decimal::new(1, 0)));
equivalent_groups[1].push(Number::from(Decimal::new(0, 0)));
equivalent_groups[2].push(Number::from(Decimal::new(42, 0)));
equivalent_groups
};
for group in equivalent_groups {
for a in &group {
for b in &group {
if a == b {
let hash_a = calculate_hash(a);
let hash_b = calculate_hash(b);
assert_eq!(
hash_a, hash_b,
"Cross-type hash consistency violated: {a:?} == {b:?} but hash({a:?}) = \
{hash_a} != {hash_b} = hash({b:?})"
);
}
}
}
}
}
#[test]
fn test_special_value_hashing() {
let nan_f64 = num!(f64::NAN);
assert_eq!(
calculate_hash(&nan_f64),
calculate_hash(&nan_f64),
"All NaN values should hash the same"
);
let pos_inf_f64 = num!(f64::INFINITY);
let neg_inf_f64 = num!(f64::NEG_INFINITY);
assert_eq!(
calculate_hash(&pos_inf_f64),
calculate_hash(&pos_inf_f64),
"Positive infinities should hash the same"
);
assert_eq!(
calculate_hash(&neg_inf_f64),
calculate_hash(&neg_inf_f64),
"Negative infinities should hash the same"
);
assert_ne!(
calculate_hash(&pos_inf_f64),
calculate_hash(&neg_inf_f64),
"Positive and negative infinities should hash differently"
);
let pos_zero = num!(0.0f64);
let neg_zero = num!(-0.0f64);
assert_eq!(
calculate_hash(&pos_zero),
calculate_hash(&neg_zero),
"+0 and -0 should hash the same"
);
}
#[test]
fn test_large_u64_eq_hash_consistency() {
let large_u64_val = (i64::MAX as u64) + 1;
let u64_num = Number::from(large_u64_val);
let f64_num = num!(large_u64_val as f64);
assert!(u64_num.is_integer());
assert!(f64_num.is_integer());
assert!(u64_num.as_i128().is_some());
assert!(f64_num.as_i128().is_some());
let u64_i128 = u64_num.as_i128().unwrap();
let f64_i128 = f64_num.as_i128().unwrap();
if u64_i128 == f64_i128 {
assert_eq!(
u64_num, f64_num,
"Large U64 and equivalent F64 should be equal"
);
let u64_hash = calculate_hash(&u64_num);
let f64_hash = calculate_hash(&f64_num);
assert_eq!(
u64_hash, f64_hash,
"Hash values must be equal for equal items. u64 hash: {u64_hash}, f64 hash: {f64_hash}"
);
} else {
assert_ne!(
u64_num, f64_num,
"Numbers with different i128 representations should not be equal"
);
}
}
#[test]
fn test_other_large_u64_values() {
let large_values = [
u64::MAX,
u64::MAX - 1,
(i64::MAX as u64) + 1,
(i64::MAX as u64) + 100,
];
for &val in &large_values {
let u64_num = Number::from(val);
let f64_num = num!(val as f64);
let are_equal = u64_num == f64_num;
let hash_equal = calculate_hash(&u64_num) == calculate_hash(&f64_num);
if are_equal {
assert!(
hash_equal,
"For value {}: numbers are equal but hashes differ. u64 hash: {}, f64 hash: {}",
val,
calculate_hash(&u64_num),
calculate_hash(&f64_num)
);
}
}
}
#[test]
fn test_hashmap_usage() {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(Number::from(100u64), "hundred");
assert_eq!(map.get(&Number::from(100i64)), Some(&"hundred"));
assert_eq!(map.get(&num!(100.0f64)), Some(&"hundred"));
map.insert(num!(100.0f64), "one hundred");
assert_eq!(map.get(&Number::from(100u64)), Some(&"one hundred"));
assert_eq!(map.len(), 1);
map.insert(Number::from(200u64), "two hundred");
assert_eq!(map.len(), 2);
}
#[test]
fn test_hashset_usage() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(Number::from(42u64));
set.insert(Number::from(42u64));
set.insert(Number::from(42u64));
set.insert(Number::from(42u64));
set.insert(Number::from(42i64));
set.insert(Number::from(42i64));
set.insert(Number::from(42i64));
set.insert(Number::from(42i64));
set.insert(num!(42.0f64));
assert_eq!(set.len(), 1);
set.insert(num!(42.5f64));
assert_eq!(set.len(), 2);
}
#[test]
fn test_large_u64_hashmap_behavior() {
use std::collections::HashMap;
let large_u64_val = (i64::MAX as u64) + 1;
let u64_num = Number::from(large_u64_val);
let f64_num = num!(large_u64_val as f64);
if u64_num == f64_num {
let mut map = HashMap::new();
map.insert(u64_num.clone(), "u64_num");
map.insert(f64_num.clone(), "f64_num");
assert_eq!(
map.len(),
1,
"HashMap should contain only one entry for equal values"
);
assert_eq!(
map.get(&u64_num),
map.get(&f64_num),
"Lookups should return the same value"
);
} else {
let mut map = HashMap::new();
map.insert(u64_num.clone(), "u64_num");
map.insert(f64_num.clone(), "f64_num");
assert_eq!(
map.len(),
2,
"HashMap should contain two entries for different values"
);
}
}
#[test]
fn test_decimal_hash_consistency() {
#[cfg(feature = "decimal")]
{
use std::collections::HashSet;
use rust_decimal::Decimal;
let mut set = HashSet::new();
set.insert(Number::from(Decimal::from(1)));
set.insert(Number::from(1u64));
set.insert(Number::from(1i64));
set.insert(num!(1.0f64));
assert_eq!(
set.len(),
1,
"Decimal(1), U64(1), I64(1), and F64(1.0) should hash to same value"
);
}
}
#[test]
fn test_hash_canonical_integer_path() {
let u32_val = Number::from(42u64);
let i32_val = Number::from(42i64);
let u64_val = Number::from(42u64);
let i64_val = Number::from(42i64);
let u32_hash = calculate_hash(&u32_val);
let i32_hash = calculate_hash(&i32_val);
let u64_hash = calculate_hash(&u64_val);
let i64_hash = calculate_hash(&i64_val);
assert_eq!(
u32_hash, i32_hash,
"U64(42) and I64(42) should hash the same"
);
assert_eq!(
u32_hash, u64_hash,
"U64(42) and U64(42) should hash the same"
);
assert_eq!(
u32_hash, i64_hash,
"U64(42) and I64(42) should hash the same"
);
let neg_i32 = Number::from(-42i64);
let neg_i64 = Number::from(-42i64);
let neg_i32_hash = calculate_hash(&neg_i32);
let neg_i64_hash = calculate_hash(&neg_i64);
assert_eq!(
neg_i32_hash, neg_i64_hash,
"I64(-42) and I64(-42) should hash the same"
);
assert_ne!(u32_hash, neg_i32_hash, "42 and -42 should hash differently");
}
#[test]
fn test_hash_canonical_float_path() {
let f64_frac = num!(42.5);
let f64_frac_hash = calculate_hash(&f64_frac);
let f64_int = num!(42.0);
let f64_int_hash = calculate_hash(&f64_int);
assert_ne!(
f64_frac_hash, f64_int_hash,
"42.5 and 42.0 should hash differently"
);
let large_u64 = Number::from((i64::MAX as u64) + 1);
let large_u64_hash = calculate_hash(&large_u64);
let small_u64 = Number::from(42u64);
let small_u64_hash = calculate_hash(&small_u64);
assert_ne!(
large_u64_hash, small_u64_hash,
"Large U64 and small U64 should hash differently"
);
}
#[test]
fn test_hash_special_float_values() {
let nan = num!(f64::NAN);
let nan_hash = calculate_hash(&nan);
let nan2 = num!(f64::NAN);
let nan2_hash = calculate_hash(&nan2);
assert_eq!(nan_hash, nan2_hash, "All NaN values should hash the same");
let finite = num!(42.5);
let finite_hash = calculate_hash(&finite);
assert_ne!(
nan_hash, finite_hash,
"NaN and finite values should hash differently"
);
let pos_inf = num!(f64::INFINITY);
let neg_inf = num!(f64::NEG_INFINITY);
let pos_inf_hash = calculate_hash(&pos_inf);
let neg_inf_hash = calculate_hash(&neg_inf);
assert_ne!(
pos_inf_hash, neg_inf_hash,
"Positive and negative infinity should hash differently"
);
assert_ne!(
pos_inf_hash, nan_hash,
"Infinity and NaN should hash differently"
);
}
#[cfg(feature = "decimal")]
#[test]
fn test_decimal_hash_distinguishes_close_values() {
use rust_decimal::Decimal;
let a = Number::from(Decimal::from_str("0.1000000000000000000000000000").unwrap());
let b = Number::from(Decimal::from_str("0.1000000000000000000000000001").unwrap());
let mut set = HashSet::new();
set.insert(a.clone());
set.insert(b.clone());
assert_eq!(
set.len(),
2,
"HashSet should retain both distinct decimal values"
);
assert!(set.contains(&a));
assert!(set.contains(&b));
}
#[test]
fn test_hash_zero_canonical_handling() {
let pos_zero_f64 = num!(0.0);
let neg_zero_f64 = num!(-0.0);
let pos_zero_hash = calculate_hash(&pos_zero_f64);
let neg_zero_hash = calculate_hash(&neg_zero_f64);
assert_eq!(
pos_zero_hash, neg_zero_hash,
"+0.0 and -0.0 should hash the same"
);
let zero_u32 = Number::from(0u64);
let zero_i32 = Number::from(0i64);
let zero_u64 = Number::from(0u64);
let zero_i64 = Number::from(0i64);
let zero_u32_hash = calculate_hash(&zero_u32);
let zero_i32_hash = calculate_hash(&zero_i32);
let zero_u64_hash = calculate_hash(&zero_u64);
let zero_i64_hash = calculate_hash(&zero_i64);
assert_eq!(
zero_u32_hash, zero_i32_hash,
"U64(0) and I64(0) should hash the same"
);
assert_eq!(
zero_u32_hash, zero_u64_hash,
"U64(0) and U64(0) should hash the same"
);
assert_eq!(
zero_u32_hash, zero_i64_hash,
"U64(0) and I64(0) should hash the same"
);
assert_eq!(
zero_u32_hash, pos_zero_hash,
"Integer zero and float zero should hash the same"
);
}
#[test]
fn test_hash_integer_canonical_path_coverage() {
let _integers = vec![
Number::from(0u64),
Number::from(42u64),
Number::from(u64::from(u32::MAX)),
Number::from(0i64),
Number::from(42i64),
Number::from(i64::from(i32::MAX)),
Number::from(0u64),
Number::from(42u64),
Number::from(i64::MAX as u64), Number::from(0i64),
Number::from(42i64),
Number::from(i64::MAX),
num!(0.0), num!(42.0), ];
let _negative_integers = [
Number::from(-1i64),
Number::from(-42i64),
Number::from(i64::from(i32::MIN)),
Number::from(-1i64),
Number::from(-42i64),
Number::from(i64::MIN),
num!(-1.0), num!(-42.0), ];
let same_42 = [
Number::from(42u64),
Number::from(42i64),
Number::from(42u64),
Number::from(42i64),
num!(42.0),
];
let hash_42 = calculate_hash(&same_42[0]);
for num in &same_42[1..] {
assert_eq!(
hash_42,
calculate_hash(num),
"All representations of 42 should use integer canonical path and hash the same"
);
}
let pos_42 = Number::from(42i64);
let neg_42 = Number::from(-42i64);
assert_ne!(
calculate_hash(&pos_42),
calculate_hash(&neg_42),
"Positive and negative integers should hash differently via integer canonical path"
);
let zeros = [
Number::from(0u64),
Number::from(0i64),
Number::from(0u64),
Number::from(0i64),
num!(0.0),
num!(-0.0), ];
let hash_zero = calculate_hash(&zeros[0]);
for num in &zeros[1..] {
assert_eq!(
hash_zero,
calculate_hash(num),
"All zero representations should use integer canonical path and hash the same"
);
}
#[cfg(feature = "decimal")]
{
use rust_decimal::Decimal;
let decimal_42 = Number::from(Decimal::new(42, 0));
let decimal_neg42 = Number::from(Decimal::new(-42, 0));
let decimal_zero = Number::from(Decimal::ZERO);
assert_eq!(
hash_42,
calculate_hash(&decimal_42),
"Decimal(42) should use integer canonical path"
);
assert_eq!(
calculate_hash(&neg_42),
calculate_hash(&decimal_neg42),
"Decimal(-42) should use integer canonical path"
);
assert_eq!(
hash_zero,
calculate_hash(&decimal_zero),
"Decimal(0) should use integer canonical path"
);
}
}
#[test]
fn test_hash_float_canonical_path_coverage() {
let large_u64_1 = Number::from((i64::MAX as u64) + 1);
let large_u64_2 = Number::from(u64::MAX);
let hash_large_1 = calculate_hash(&large_u64_1);
let hash_large_2 = calculate_hash(&large_u64_2);
assert_ne!(
hash_large_1, hash_large_2,
"Different large U64s should hash differently"
);
assert_ne!(
hash_large_1,
calculate_hash(&Number::from(42u64)),
"Large U64 should hash differently from small U64"
);
let _float_fractionals = [num!(3.16), num!(-3.16), num!(0.5), num!(-0.5), num!(42.001)];
assert_ne!(
calculate_hash(&num!(3.16)),
calculate_hash(&Number::from(3i64)),
"Fractional float should hash differently from integer"
);
let _special_floats = [num!(f64::NAN), num!(f64::INFINITY), num!(f64::NEG_INFINITY)];
let nan1 = num!(f64::NAN);
let nan2 = num!(f64::NAN);
assert_eq!(
calculate_hash(&nan1),
calculate_hash(&nan2),
"All NaN values should hash the same via float canonical path"
);
let pos_inf = num!(f64::INFINITY);
let neg_inf = num!(f64::NEG_INFINITY);
assert_eq!(
calculate_hash(&pos_inf),
calculate_hash(&num!(f64::INFINITY)),
"Positive infinities should hash the same"
);
assert_ne!(
calculate_hash(&pos_inf),
calculate_hash(&neg_inf),
"Positive and negative infinity should hash differently via float canonical path"
);
#[cfg(feature = "decimal")]
{
use rust_decimal::Decimal;
let decimal_frac = Number::from(Decimal::new(316, 2)); let float_equiv = num!(3.16);
assert_eq!(
calculate_hash(&decimal_frac),
calculate_hash(&float_equiv),
"Decimal(3.16) and F64(3.16) should use float canonical path and hash the same"
);
}
}
#[test]
fn test_hash_negative_value_handling() {
let neg_i32 = Number::from(-1i64);
let pos_i32 = Number::from(1i64);
let neg_i32_hash = calculate_hash(&neg_i32);
let pos_i32_hash = calculate_hash(&pos_i32);
assert_ne!(
neg_i32_hash, pos_i32_hash,
"Negative and positive I64 should hash differently"
);
let neg_i64 = Number::from(-1i64);
let pos_i64 = Number::from(1i64);
let neg_i64_hash = calculate_hash(&neg_i64);
let pos_i64_hash = calculate_hash(&pos_i64);
assert_ne!(
neg_i64_hash, pos_i64_hash,
"Negative and positive I64 should hash differently"
);
assert_eq!(
neg_i32_hash, neg_i64_hash,
"I64(-1) and I64(-1) should hash the same"
);
assert_eq!(
pos_i32_hash, pos_i64_hash,
"I64(1) and I64(1) should hash the same"
);
let neg_f64 = num!(-1.0);
let pos_f64 = num!(1.0);
let neg_f64_hash = calculate_hash(&neg_f64);
let pos_f64_hash = calculate_hash(&pos_f64);
assert_ne!(
neg_f64_hash, pos_f64_hash,
"Negative and positive F64 should hash differently"
);
assert_eq!(
neg_i32_hash, neg_f64_hash,
"I64(-1) and F64(-1.0) should hash the same"
);
assert_eq!(
pos_i32_hash, pos_f64_hash,
"I64(1) and F64(1.0) should hash the same"
);
}
#[test]
fn test_large_f64_integer_hash_coverage() {
let large_positive = num!(1e40);
let large_negative = num!(-1e40);
assert!(large_positive.is_integer());
assert!(large_negative.is_integer());
assert!(large_positive.as_i128().is_none());
assert!(large_negative.as_i128().is_none());
let mut set = HashSet::new();
set.insert(large_positive);
set.insert(large_negative);
assert!(set.contains(&num!(1e40)));
assert!(set.contains(&num!(-1e40)));
let large_positive_2 = num!(1e40);
assert_eq!(num!(1e40), large_positive_2);
assert!(set.contains(&large_positive_2));
}