From 3f32ad48f4aee546057059985c2bb08fe85af748 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 29 Jun 2017 11:52:25 -0700 Subject: [PATCH] rational: make sure Hash agrees with Eq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can't use a derived `Hash` when we have a manual `Eq`, because we need to uphold the invariant `a == b` → `h(a) == h(b)`. Since `Eq` doesn't require them to be in reduced form, `Hash` also needs to be normalized. --- rational/src/lib.rs | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/rational/src/lib.rs b/rational/src/lib.rs index f984548..640ca31 100644 --- a/rational/src/lib.rs +++ b/rational/src/lib.rs @@ -27,8 +27,7 @@ extern crate num_integer as integer; use std::cmp; use std::error::Error; use std::fmt; -#[cfg(test)] -use std::hash; +use std::hash::{Hash, Hasher}; use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; use std::str::FromStr; @@ -39,7 +38,7 @@ use integer::Integer; use traits::{FromPrimitive, Float, PrimInt, Num, Signed, Zero, One, Bounded, NumCast}; /// Represents the ratio between 2 numbers. -#[derive(Copy, Clone, Hash, Debug)] +#[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[allow(missing_docs)] pub struct Ratio { @@ -347,6 +346,24 @@ impl PartialEq for Ratio { impl Eq for Ratio {} +// NB: We can't just `#[derive(Hash)]`, because it needs to agree +// with `Eq` even for non-reduced ratios. +impl Hash for Ratio { + fn hash(&self, state: &mut H) { + recurse(&self.numer, &self.denom, state); + + fn recurse(numer: &T, denom: &T, state: &mut H) { + if !denom.is_zero() { + let (int, rem) = numer.div_mod_floor(denom); + int.hash(state); + recurse(denom, &rem, state); + } else { + denom.hash(state); + } + } + } +} + macro_rules! forward_val_val_binop { (impl $imp:ident, $method:ident) => { @@ -843,8 +860,8 @@ fn approximate_float_unsigned(val: F, max_error: F, max_iterations: usize) } #[cfg(test)] -fn hash(x: &T) -> u64 { - use std::hash::{BuildHasher, Hasher}; +fn hash(x: &T) -> u64 { + use std::hash::BuildHasher; use std::collections::hash_map::RandomState; let mut hasher = ::Hasher::new(); x.hash(&mut hasher); @@ -1366,6 +1383,17 @@ mod test { fn test_hash() { assert!(::hash(&_0) != ::hash(&_1)); assert!(::hash(&_0) != ::hash(&_3_2)); + + // a == b -> hash(a) == hash(b) + let a = Rational::new_raw(4, 2); + let b = Rational::new_raw(6, 3); + assert_eq!(a, b); + assert_eq!(::hash(&a), ::hash(&b)); + + let a = Rational::new_raw(123456789, 1000); + let b = Rational::new_raw(123456789 * 5, 5000); + assert_eq!(a, b); + assert_eq!(::hash(&a), ::hash(&b)); } #[test]