Skip to content

Commit 2bdc33a

Browse files
authored
Merge pull request RustPython#3951 from jopemachine/float-nan-hash
Fix `float` and `complex`'s incorrect hash value when using with `nan`
2 parents 1f00aa0 + d7f17d8 commit 2bdc33a

File tree

4 files changed

+34
-24
lines changed

4 files changed

+34
-24
lines changed

Lib/test/test_float.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,6 @@ def test_hash(self):
582582
self.assertEqual(hash(float('inf')), sys.hash_info.inf)
583583
self.assertEqual(hash(float('-inf')), -sys.hash_info.inf)
584584

585-
# TODO: RUSTPYTHON
586-
@unittest.expectedFailure
587585
def test_hash_nan(self):
588586
value = float('nan')
589587
self.assertEqual(hash(value), object.__hash__(value))

common/src/hash.rs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
use num_bigint::BigInt;
2-
use num_complex::Complex64;
32
use num_traits::ToPrimitive;
43
use siphasher::sip::SipHasher24;
5-
use std::{
6-
hash::{BuildHasher, Hash, Hasher},
7-
num::Wrapping,
8-
};
4+
use std::hash::{BuildHasher, Hash, Hasher};
95

106
pub type PyHash = i64;
117
pub type PyUHash = u64;
@@ -94,17 +90,14 @@ impl HashSecret {
9490
}
9591
}
9692

97-
pub fn hash_float(value: f64) -> PyHash {
93+
#[inline]
94+
pub fn hash_float(value: f64) -> Option<PyHash> {
9895
// cpython _Py_HashDouble
9996
if !value.is_finite() {
10097
return if value.is_infinite() {
101-
if value > 0.0 {
102-
INF
103-
} else {
104-
-INF
105-
}
98+
Some(if value > 0.0 { INF } else { -INF })
10699
} else {
107-
NAN
100+
None
108101
};
109102
}
110103

@@ -136,14 +129,7 @@ pub fn hash_float(value: f64) -> PyHash {
136129
};
137130
x = ((x << e) & MODULUS) | x >> (BITS32 - e);
138131

139-
fix_sentinel(x as PyHash * value.signum() as PyHash)
140-
}
141-
142-
pub fn hash_complex(value: &Complex64) -> PyHash {
143-
let re_hash = hash_float(value.re);
144-
let im_hash = hash_float(value.im);
145-
let Wrapping(ret) = Wrapping(re_hash) + Wrapping(im_hash) * Wrapping(IMAG);
146-
fix_sentinel(ret)
132+
Some(fix_sentinel(x as PyHash * value.signum() as PyHash))
147133
}
148134

149135
pub fn hash_iter_unordered<'a, T: 'a, I, F, E>(iter: I, hashf: F) -> Result<PyHash, E>
@@ -192,3 +178,19 @@ pub fn lcg_urandom(mut x: u32, buf: &mut [u8]) {
192178
*b = ((x >> 16) & 0xff) as u8;
193179
}
194180
}
181+
182+
#[inline]
183+
pub fn hash_object_id_raw(p: usize) -> PyHash {
184+
// TODO: Use commented logic when below issue resolved.
185+
// Ref: https://github.com/RustPython/RustPython/pull/3951#issuecomment-1193108966
186+
187+
/* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid
188+
excessive hash collisions for dicts and sets */
189+
// p.rotate_right(4) as PyHash
190+
p as PyHash
191+
}
192+
193+
#[inline]
194+
pub fn hash_object_id(p: usize) -> PyHash {
195+
fix_sentinel(hash_object_id_raw(p))
196+
}

vm/src/builtins/complex.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
use num_complex::Complex64;
1717
use num_traits::Zero;
1818
use rustpython_common::{float_ops, hash};
19+
use std::num::Wrapping;
1920

2021
/// Create a complex number from a real part and an optional imaginary part.
2122
///
@@ -417,7 +418,16 @@ impl Comparable for PyComplex {
417418
impl Hashable for PyComplex {
418419
#[inline]
419420
fn hash(zelf: &crate::Py<Self>, _vm: &VirtualMachine) -> PyResult<hash::PyHash> {
420-
Ok(hash::hash_complex(&zelf.value))
421+
let value = zelf.value;
422+
423+
let re_hash =
424+
hash::hash_float(value.re).unwrap_or_else(|| hash::hash_object_id(zelf.get_id()));
425+
426+
let im_hash =
427+
hash::hash_float(value.im).unwrap_or_else(|| hash::hash_object_id(zelf.get_id()));
428+
429+
let Wrapping(ret) = Wrapping(re_hash) + Wrapping(im_hash) * Wrapping(hash::IMAG);
430+
Ok(hash::fix_sentinel(ret))
421431
}
422432
}
423433

vm/src/builtins/float.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ impl Comparable for PyFloat {
539539
impl Hashable for PyFloat {
540540
#[inline]
541541
fn hash(zelf: &crate::Py<Self>, _vm: &VirtualMachine) -> PyResult<hash::PyHash> {
542-
Ok(hash::hash_float(zelf.to_f64()))
542+
Ok(hash::hash_float(zelf.to_f64()).unwrap_or_else(|| hash::hash_object_id(zelf.get_id())))
543543
}
544544
}
545545

0 commit comments

Comments
 (0)