11use std:: fmt:: { self , Display , Formatter } ;
2+ use std:: hash:: { Hash , Hasher } ;
23use std:: ops:: Neg ;
34use std:: str:: FromStr ;
45
@@ -88,7 +89,7 @@ use crate::World;
8889/// to rounding. When those two operations do not surpass the digit limits, they
8990/// are fully precise.
9091#[ ty( scope, cast) ]
91- #[ derive( Debug , Default , Clone , Copy , PartialEq , Eq , Hash , PartialOrd , Ord ) ]
92+ #[ derive( Debug , Default , Clone , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
9293pub struct Decimal ( rust_decimal:: Decimal ) ;
9394
9495impl Decimal {
@@ -370,6 +371,22 @@ impl Neg for Decimal {
370371 }
371372}
372373
374+ impl Hash for Decimal {
375+ fn hash < H : Hasher > ( & self , state : & mut H ) {
376+ // `rust_decimal`'s Hash implementation normalizes decimals before
377+ // hashing them. This means decimals with different scales but
378+ // equivalent value not only compare equal but also hash equally. Here,
379+ // we hash all bytes explicitly to ensure the scale is also considered.
380+ // This means that 123.314 == 123.31400, but 123.314.hash() !=
381+ // 123.31400.hash().
382+ //
383+ // Note that this implies that equal decimals can have different hashes,
384+ // which might generate problems with certain data structures, such as
385+ // HashSet and HashMap.
386+ self . 0 . serialize ( ) . hash ( state) ;
387+ }
388+ }
389+
373390/// A value that can be cast to a decimal.
374391pub enum ToDecimal {
375392 /// A string with the decimal's representation.
@@ -386,3 +403,27 @@ cast! {
386403 v: f64 => Self :: Float ( v) ,
387404 v: Str => Self :: Str ( EcoString :: from( v) ) ,
388405}
406+
407+ #[ cfg( test) ]
408+ mod tests {
409+ use std:: str:: FromStr ;
410+
411+ use super :: Decimal ;
412+ use crate :: utils:: hash128;
413+
414+ #[ test]
415+ fn test_decimals_with_equal_scales_hash_identically ( ) {
416+ let a = Decimal :: from_str ( "3.14" ) . unwrap ( ) ;
417+ let b = Decimal :: from_str ( "3.14" ) . unwrap ( ) ;
418+ assert_eq ! ( a, b) ;
419+ assert_eq ! ( hash128( & a) , hash128( & b) ) ;
420+ }
421+
422+ #[ test]
423+ fn test_decimals_with_different_scales_hash_differently ( ) {
424+ let a = Decimal :: from_str ( "3.140" ) . unwrap ( ) ;
425+ let b = Decimal :: from_str ( "3.14000" ) . unwrap ( ) ;
426+ assert_eq ! ( a, b) ;
427+ assert_ne ! ( hash128( & a) , hash128( & b) ) ;
428+ }
429+ }
0 commit comments