311: rational: make sure Hash agrees with Eq r=cuviper

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.

Fixes #310.
This commit is contained in:
bors[bot] 2017-07-10 20:12:23 +00:00
commit bcccab17dd
1 changed files with 33 additions and 5 deletions

View File

@ -27,8 +27,7 @@ extern crate num_integer as integer;
use std::cmp; use std::cmp;
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
#[cfg(test)] use std::hash::{Hash, Hasher};
use std::hash;
use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
use std::str::FromStr; use std::str::FromStr;
@ -39,7 +38,7 @@ use integer::Integer;
use traits::{FromPrimitive, Float, PrimInt, Num, Signed, Zero, One, Bounded, NumCast}; use traits::{FromPrimitive, Float, PrimInt, Num, Signed, Zero, One, Bounded, NumCast};
/// Represents the ratio between 2 numbers. /// Represents the ratio between 2 numbers.
#[derive(Copy, Clone, Hash, Debug)] #[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct Ratio<T> { pub struct Ratio<T> {
@ -347,6 +346,24 @@ impl<T: Clone + Integer> PartialEq for Ratio<T> {
impl<T: Clone + Integer> Eq for Ratio<T> {} impl<T: Clone + Integer> Eq for Ratio<T> {}
// NB: We can't just `#[derive(Hash)]`, because it needs to agree
// with `Eq` even for non-reduced ratios.
impl<T: Clone + Integer + Hash> Hash for Ratio<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
recurse(&self.numer, &self.denom, state);
fn recurse<T: Integer + Hash, H: Hasher>(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 { macro_rules! forward_val_val_binop {
(impl $imp:ident, $method:ident) => { (impl $imp:ident, $method:ident) => {
@ -843,8 +860,8 @@ fn approximate_float_unsigned<T, F>(val: F, max_error: F, max_iterations: usize)
} }
#[cfg(test)] #[cfg(test)]
fn hash<T: hash::Hash>(x: &T) -> u64 { fn hash<T: Hash>(x: &T) -> u64 {
use std::hash::{BuildHasher, Hasher}; use std::hash::BuildHasher;
use std::collections::hash_map::RandomState; use std::collections::hash_map::RandomState;
let mut hasher = <RandomState as BuildHasher>::Hasher::new(); let mut hasher = <RandomState as BuildHasher>::Hasher::new();
x.hash(&mut hasher); x.hash(&mut hasher);
@ -1366,6 +1383,17 @@ mod test {
fn test_hash() { fn test_hash() {
assert!(::hash(&_0) != ::hash(&_1)); assert!(::hash(&_0) != ::hash(&_1));
assert!(::hash(&_0) != ::hash(&_3_2)); 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] #[test]