Skip to main content

kohebi_core/
hash.rs

1//! Python's hash, and the key type a dict is built on.
2//!
3//! The rule that makes this worth reproducing exactly rather than approximating
4//! is that `1 == 1.0 == True`, so all three have to hash the same and
5//! `{1: 'a'}[True]` has to find the value. CPython gets that by hashing every
6//! number, of every type, as its value modulo the prime 2^61-1, which is a
7//! construction that agrees with itself across types by arithmetic rather than
8//! by anyone remembering to keep the cases in step. So `hash(2**80)` and
9//! `hash(2.0**80)` are both 524288 and neither one had to know about the other.
10//!
11//! Everything numeric here matches CPython's own answer, checked against a
12//! running 3.14 rather than against memory. Strings are the exception and have
13//! to be: CPython seeds `SipHash` from the environment, so `hash('a')` is a
14//! different number in two runs of the same interpreter on the same machine.
15//! Matching it is not possible and is not something any program may depend on.
16//! What a string hash owes us is that equal strings hash equally, and that it
17//! is the same within one run.
18//!
19//! ## What is not here yet
20//!
21//! `__hash__` is user code, and a class that defines one has its hash come from
22//! there rather than from this file. When classes arrive this becomes the
23//! answer for the builtin types and the fallback for everything else, which is
24//! the same shape [`Object::truthy`] has.
25
26use std::hash::{Hash, Hasher};
27
28use num_bigint::BigInt;
29use num_traits::FromPrimitive as _;
30
31use crate::int::Int;
32use crate::object::Object;
33use crate::text::Str;
34
35/// The prime every number is reduced modulo, which is `sys.hash_info.modulus`.
36pub const MODULUS: u64 = (1 << 61) - 1;
37
38/// The width of that modulus, which is `sys.hash_info.width` minus the sign.
39const BITS: u32 = 61;
40
41/// What an infinity hashes to, sign applied, which is `sys.hash_info.inf`.
42const INF: i64 = 314_159;
43
44/// `hash(None)`, which stopped being derived from the address in 3.12 and is
45/// now this constant, so it is one of the few object hashes we can match.
46const NONE: i64 = 0xFCA8_6420;
47
48/// `hash(...)` and `hash(NotImplemented)` come from the address in CPython and
49/// so differ between runs. These are ours, and nothing may depend on them.
50const ELLIPSIS: i64 = 0x1CE1_1195;
51const NOT_IMPLEMENTED: i64 = 0x2B0E_9C7A;
52
53/// The constants CPython's tuple hash is built from, which are xxHash's.
54const XXPRIME_1: u64 = 11_400_714_785_074_694_791;
55const XXPRIME_2: u64 = 14_029_467_366_897_019_727;
56const XXPRIME_5: u64 = 2_870_177_450_012_600_261;
57
58/// A value that cannot be a dict key or a set member.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct Unhashable {
61    /// The type that was asked, which for a tuple is the type inside it that
62    /// refused rather than the tuple, because that is the one to go and fix.
63    pub type_name: &'static str,
64}
65
66impl Unhashable {
67    /// The `TypeError` message CPython raises for this.
68    #[must_use]
69    pub fn message(&self) -> String {
70        format!("unhashable type: '{}'", self.type_name)
71    }
72}
73
74/// What `hash(object)` gives back.
75///
76/// # Errors
77///
78/// A list has no hash, and neither does a tuple containing one.
79pub fn hash(object: &Object) -> Result<i64, Unhashable> {
80    match object {
81        Object::None => Ok(NONE),
82        Object::Ellipsis => Ok(ELLIPSIS),
83        Object::NotImplemented => Ok(NOT_IMPLEMENTED),
84        // A bool is an int, so it hashes as one rather than as itself.
85        Object::Bool(value) => Ok(i64::from(*value)),
86        Object::Int(value) => Ok(int(value)),
87        Object::Float(value) => Ok(float(*value)),
88        Object::Str(value) => Ok(text(value)),
89        Object::Bytes(value) => Ok(blob(value)),
90        Object::Tuple(items) => tuple(items),
91        // A slice hashes as its three parts, which is what CPython does and is
92        // what puts `x[1:2]` in a dict and finds it again. The number itself is
93        // not CPython's, for the same reason a string hash is not: what it owes
94        // us is that equal slices hash equally within one run.
95        Object::Slice(value) => lanes(value.parts().into_iter(), 3),
96        // A native value with a hash of its own is one whose equality is by
97        // value rather than by identity, and it has to answer this the same way
98        // for two values it calls equal. Everything else falls back to the
99        // address, which is what CPython's default `__hash__` is derived from.
100        // That is worth no more than CPython's is: it differs between runs and
101        // nothing may depend on it. What it owes us is that the same object
102        // hashes the same way twice, which is what puts a builtin function in a
103        // dict and finds it again.
104        Object::Native(value) => Ok(value
105            .hash()
106            .unwrap_or_else(|| address(std::ptr::from_ref(value.as_ref()).cast::<()>()))),
107        // Everything that can change is out, because a key that could change
108        // could move out from under the slot it was filed in. A `frozenset`
109        // is the way Python gives you a hashable one, and there is not one
110        // of those yet.
111        // Spelled out rather than asked of the object, because the name here
112        // outlives the value it came from and only a class defined in Python has
113        // a name that does not. None of those reach this arm.
114        Object::List(_) => Err(Unhashable { type_name: "list" }),
115        Object::Dict(_) => Err(Unhashable { type_name: "dict" }),
116        Object::Set(_) => Err(Unhashable { type_name: "set" }),
117    }
118}
119
120/// CPython's `_Py_HashPointer`, which rotates the address right by four so that
121/// the alignment bits every heap pointer shares stop landing in the low bits a
122/// dict indexes on.
123fn address(pointer: *const ()) -> i64 {
124    let rotated = pointer.addr().rotate_right(4);
125    settle(rotated.cast_signed() as i64)
126}
127
128/// An integer's value modulo 2^61-1, with the sign put back on afterwards.
129///
130/// The modulus is prime, so this is a ring homomorphism and the answer for a
131/// number does not depend on how the number was written down or which arm of
132/// [`Int`] happens to be holding it.
133fn int(value: &Int) -> i64 {
134    // Rust truncates a remainder toward zero, so it already carries the sign of
135    // the number, which is what CPython puts back on by hand.
136    let reduced: i128 = match value {
137        // `i128` so that the reduction happens before anything can wrap.
138        Int::Small(n) => i128::from(*n) % i128::from(MODULUS),
139        Int::Big(n) => (n.as_ref() % BigInt::from(MODULUS))
140            .try_into()
141            .expect("a remainder mod 2^61-1 is far inside an i128"),
142    };
143    let reduced = i64::try_from(reduced).expect("a remainder mod 2^61-1 fits in an i64");
144    settle(reduced)
145}
146
147/// A float's value modulo the same prime, so that a float equal to an integer
148/// hashes the same as that integer.
149///
150/// The number is split into a mantissa and an exponent, the mantissa is walked
151/// 28 bits at a time so the loop is exact in both binary and hexadecimal
152/// floating point, and the exponent is applied at the end as a rotation, which
153/// is what multiplying by a power of two comes to in this ring.
154#[expect(
155    clippy::cast_possible_truncation,
156    clippy::cast_possible_wrap,
157    clippy::cast_precision_loss,
158    clippy::cast_sign_loss,
159    reason = "every cast in here is exact by construction, and the comment \
160              next to each one says what makes it exact"
161)]
162fn float(value: f64) -> i64 {
163    if !value.is_finite() {
164        // A NaN takes its hash from its address in CPython, so there is nothing
165        // to match. Zero is what `sys.hash_info.nan` still reports.
166        return if value.is_infinite() {
167            if value > 0.0 { INF } else { -INF }
168        } else {
169            0
170        };
171    }
172    let (mut mantissa, mut exponent) = frexp(value);
173    let sign = if mantissa < 0.0 {
174        mantissa = -mantissa;
175        -1
176    } else {
177        1
178    };
179
180    let mut x: u64 = 0;
181    while mantissa != 0.0 {
182        x = ((x << 28) & MODULUS) | (x >> (BITS - 28));
183        mantissa *= 268_435_456.0; // 2^28
184        exponent -= 28;
185        // The integer part of what is left, which is at most 28 bits, so the
186        // cast cannot lose anything and the addition cannot overflow.
187        let digit = mantissa as u64;
188        mantissa -= digit as f64;
189        x += digit;
190        if x >= MODULUS {
191            x -= MODULUS;
192        }
193    }
194
195    // Multiplying by 2^k in this ring is a rotation by k, and rotating by the
196    // width is the identity, so only the exponent modulo the width matters.
197    // Both branches land in `0..BITS`, which is why the cast back is safe.
198    let bits = BITS as i32;
199    let exponent = if exponent >= 0 {
200        exponent % bits
201    } else {
202        bits - 1 - ((-1 - exponent) % bits)
203    } as u32;
204    x = ((x << exponent) & MODULUS) | (x >> (BITS - exponent));
205
206    // `x` is a residue mod 2^61-1, so it is well inside the positive half.
207    settle((x as i64) * sign)
208}
209
210/// The mantissa in `[0.5, 1)` and the exponent that puts it back, which is C's
211/// `frexp` and which Rust does not have.
212fn frexp(value: f64) -> (f64, i32) {
213    if value == 0.0 {
214        // Keeps the sign of a negative zero, which the caller then strips.
215        return (value, 0);
216    }
217    let bits = value.to_bits();
218    let biased = ((bits >> 52) & 0x7ff) as i32;
219    if biased == 0 {
220        // Subnormal, so there is no implicit leading one to read the exponent
221        // against. Scale it into the normal range and take the shift back off.
222        let (mantissa, exponent) = frexp(value * f64::from_bits(0x43f0_0000_0000_0000));
223        return (mantissa, exponent - 64);
224    }
225    // Replace the exponent field with the one that means `[0.5, 1)` and keep
226    // the sign and the fraction exactly as they were.
227    let mantissa = f64::from_bits((bits & !(0x7ffu64 << 52)) | (1022u64 << 52));
228    (mantissa, biased - 1022)
229}
230
231/// A tuple's hash, which is xxHash over its elements' hashes.
232fn tuple(items: &[Object]) -> Result<i64, Unhashable> {
233    lanes(items.iter(), items.len())
234}
235
236/// The same algorithm over anything that can be walked, so that a slice can
237/// hash as its three parts without first being collected into a tuple.
238#[expect(
239    clippy::cast_possible_wrap,
240    clippy::cast_sign_loss,
241    clippy::decimal_bitwise_operands,
242    reason = "the arithmetic is unsigned and wrapping on purpose, and the odd \
243              constant is written the way CPython writes it so the two can be \
244              compared by eye"
245)]
246fn lanes<'a>(items: impl Iterator<Item = &'a Object>, len: usize) -> Result<i64, Unhashable> {
247    let mut acc = XXPRIME_5;
248    for item in items {
249        let lane = hash(item)? as u64;
250        acc = acc.wrapping_add(lane.wrapping_mul(XXPRIME_2));
251        acc = acc.rotate_left(31);
252        acc = acc.wrapping_mul(XXPRIME_1);
253    }
254    // The length goes in mangled, which is what keeps `hash(())` at the value
255    // it had before this algorithm replaced the previous one.
256    acc = acc.wrapping_add((len as u64) ^ (XXPRIME_5 ^ 3_527_539));
257    // The one forbidden answer, and CPython's chosen replacement for it.
258    if acc == u64::MAX {
259        return Ok(1_546_275_796);
260    }
261    Ok(acc as i64)
262}
263
264/// A string's hash.
265///
266/// The arm goes into the hash because `Str` compares equal only within an arm,
267/// so mixing them could only ever cost a collision, never correctness.
268#[expect(
269    clippy::cast_possible_wrap,
270    reason = "a hash is a number, and which half of the range it lands in is \
271              not information anyone is entitled to"
272)]
273fn text(value: &Str) -> i64 {
274    let mut hasher = std::hash::DefaultHasher::new();
275    match value {
276        Str::Utf8(s) => {
277            0u8.hash(&mut hasher);
278            s.hash(&mut hasher);
279        }
280        Str::Wide(w) => {
281            1u8.hash(&mut hasher);
282            w.hash(&mut hasher);
283        }
284    }
285    settle(hasher.finish() as i64)
286}
287
288/// A bytes object's hash, kept apart from a string's so that the two never
289/// collide on purpose, since they are never equal however alike they look.
290#[expect(clippy::cast_possible_wrap, reason = "the same as for a string")]
291fn blob(value: &[u8]) -> i64 {
292    let mut hasher = std::hash::DefaultHasher::new();
293    2u8.hash(&mut hasher);
294    value.hash(&mut hasher);
295    settle(hasher.finish() as i64)
296}
297
298/// `-1` is how CPython's C functions report an error, so no hash may be it and
299/// the one value that would be is moved out of the way.
300const fn settle(value: i64) -> i64 {
301    if value == -1 { -2 } else { value }
302}
303
304/// A value used as a dict key or a set member.
305///
306/// Two jobs. It carries the hash, computed once when the key was made, because
307/// a dict asks for it on every lookup and a big tuple would otherwise walk
308/// itself each time. And it gives Rust's `Hash` and `Eq` Python's meaning
309/// rather than the derived one, so `1`, `1.0` and `True` are one key.
310///
311/// Making one is where an unhashable value is caught, so a `Key` that exists is
312/// a value that had a hash at the time it was made. Nothing here can change
313/// afterwards, since the only mutable object in the object model is a list and
314/// a list has no hash.
315#[derive(Debug, Clone)]
316pub struct Key {
317    object: Object,
318    hash: i64,
319}
320
321impl Key {
322    /// Takes a value as a key.
323    ///
324    /// # Errors
325    ///
326    /// The value has no hash, so it cannot be one.
327    pub fn new(object: Object) -> Result<Self, Unhashable> {
328        let hash = hash(&object)?;
329        Ok(Key { object, hash })
330    }
331
332    /// The value itself, which is what iterating a dict hands back.
333    #[must_use]
334    pub const fn object(&self) -> &Object {
335        &self.object
336    }
337
338    /// The value, giving up the key.
339    #[must_use]
340    pub fn into_object(self) -> Object {
341        self.object
342    }
343
344    /// The hash, computed when this was made.
345    #[must_use]
346    pub const fn hash(&self) -> i64 {
347        self.hash
348    }
349}
350
351impl Hash for Key {
352    fn hash<H: Hasher>(&self, state: &mut H) {
353        state.write_i64(self.hash);
354    }
355}
356
357impl PartialEq for Key {
358    /// What a dict lookup asks, which is not quite `==`.
359    ///
360    /// Identity comes first, and that is the whole reason a NaN can be used as
361    /// a dict key and found again. `x == x` is false for one, so a lookup that
362    /// only asked `==` would store it and then never find it.
363    fn eq(&self, other: &Self) -> bool {
364        self.object.same_value(&other.object)
365    }
366}
367
368impl Eq for Key {}
369
370/// An integer and a float are equal when they are the same number, which has to
371/// be decided exactly rather than by converting one to the other and hoping.
372#[expect(
373    clippy::cast_possible_truncation,
374    reason = "the cast is guarded by the range check on the line above it, and \
375              both ends of that range are exactly representable"
376)]
377pub(crate) fn int_eq_float(int: &Int, float: f64) -> bool {
378    // An infinity is larger than every integer and a NaN equals nothing, and a
379    // float with a fractional part is not any integer either.
380    if !float.is_finite() || float.fract() != 0.0 {
381        return false;
382    }
383    if let Int::Small(n) = int {
384        // Both bounds are exactly representable, so this window is exact.
385        if (-9_223_372_036_854_775_808.0..9_223_372_036_854_775_808.0).contains(&float) {
386            return *n == float as i64;
387        }
388    }
389    // Exact for an integral float, which is the only kind that gets here.
390    BigInt::from_f64(float).is_some_and(|value| value == int.to_big())
391}
392
393#[cfg(test)]
394#[expect(
395    clippy::unreadable_literal,
396    clippy::approx_constant,
397    reason = "these are the numbers a CPython 3.14 printed, kept in the form it \
398              printed them so that a reader can check them against it"
399)]
400mod tests {
401    use super::*;
402
403    fn h(object: &Object) -> i64 {
404        hash(object).expect("expected this to be hashable")
405    }
406
407    /// The values below are what CPython 3.14 answers, taken from a running
408    /// one rather than worked out here.
409    #[test]
410    fn an_integer_hashes_as_its_value_modulo_the_prime() {
411        for (value, expected) in [
412            (0i64, 0i64),
413            (1, 1),
414            (2, 2),
415            (7, 7),
416            (2305843009213693950, 2305843009213693950),
417            // The modulus itself, which is where the wrap happens.
418            (2305843009213693951, 0),
419            (2305843009213693952, 1),
420            (4611686018427387904, 2),
421            (-2, -2),
422        ] {
423            assert_eq!(h(&Object::int(value)), expected, "hash({value})");
424        }
425    }
426
427    /// `-1` is how a hash reports failure in C, so it is the one answer no
428    /// value may have, and the number whose hash it would be gets moved.
429    #[test]
430    fn the_one_hash_nothing_is_allowed_to_have() {
431        assert_eq!(h(&Object::int(-1)), -2);
432        assert_eq!(h(&Object::Float(-1.0)), -2);
433        // Which is why two different numbers really do share a hash here.
434        assert_eq!(h(&Object::int(-2)), -2);
435    }
436
437    #[test]
438    fn a_big_integer_hashes_the_same_way_a_small_one_does() {
439        for (digits, expected) in [
440            ("100000000000000000000", 848750603811160107i64),
441            ("-100000000000000000000", -848750603811160107),
442            ("1208925819614629174706176", 524288),
443            ("-1208925819614629174706176", -524288),
444            (
445                "1606938044258990275541962092341162602522202993782792835313721",
446                143417,
447            ),
448            (
449                "-1606938044258990275541962092341162602522202993782792835313721",
450                -143417,
451            ),
452        ] {
453            let (text, sign) = digits
454                .strip_prefix('-')
455                .map_or((digits, 1), |rest| (rest, -1));
456            let value = Int::parse(text, 10).expect("expected this to parse");
457            let value = if sign < 0 { value.neg() } else { value };
458            assert_eq!(h(&Object::Int(value)), expected, "hash({digits})");
459        }
460    }
461
462    #[test]
463    fn a_float_hashes_as_its_value_too() {
464        for (value, expected) in [
465            (0.0f64, 0i64),
466            (-0.0, 0),
467            (1.0, 1),
468            (1024.0, 1024),
469            (0.5, 1152921504606846976),
470            (1.5, 1152921504606846977),
471            (-1.5, -1152921504606846977),
472            (-2.5, -1152921504606846978),
473            (0.1, 230584300921369408),
474            (-0.1, -230584300921369408),
475            (1e16, 10000000000000000),
476            (1e300, 1224995262755759164),
477            (-1e300, -1224995262755759164),
478            (1e-300, 482449582752280463),
479            (3.14159265358979, 326490430436033539),
480            (f64::MAX, 2234066890152476671),
481            (f64::MIN_POSITIVE, 32768),
482            // The smallest subnormal, which is the case `frexp` has to scale
483            // into the normal range before it can read an exponent off it.
484            (5e-324, 16777216),
485        ] {
486            assert_eq!(h(&Object::Float(value)), expected, "hash({value:?})");
487        }
488    }
489
490    #[test]
491    fn an_infinity_hashes_to_the_number_it_always_has() {
492        assert_eq!(h(&Object::Float(f64::INFINITY)), 314159);
493        assert_eq!(h(&Object::Float(f64::NEG_INFINITY)), -314159);
494    }
495
496    /// The point of the whole construction. These three are one number as far
497    /// as Python is concerned, so they are one dict key.
498    #[test]
499    fn the_same_number_in_three_types_has_one_hash() {
500        assert_eq!(h(&Object::int(1)), h(&Object::Float(1.0)));
501        assert_eq!(h(&Object::int(1)), h(&Object::Bool(true)));
502        assert_eq!(h(&Object::int(0)), h(&Object::Bool(false)));
503        // And it holds past the point where a float stops being able to count.
504        let big = Int::parse("1208925819614629174706176", 10).expect("expected this to parse");
505        assert_eq!(h(&Object::Int(big)), h(&Object::Float(2.0f64.powi(80))));
506    }
507
508    #[test]
509    fn a_tuple_hashes_the_way_cpython_hashes_one() {
510        let t = |items: Vec<Object>| h(&Object::tuple(items));
511        assert_eq!(t(vec![]), 5740354900026072187);
512        assert_eq!(t(vec![Object::int(1)]), -6644214454873602895);
513        assert_eq!(t(vec![Object::int(0)]), -8753497827991233192);
514        assert_eq!(t(vec![Object::int(-1)]), 8078679518589016365);
515        assert_eq!(
516            t(vec![Object::int(1), Object::int(2)]),
517            -3550055125485641917
518        );
519        assert_eq!(
520            t(vec![Object::int(1), Object::int(2), Object::int(3)]),
521            529344067295497451
522        );
523        assert_eq!(
524            t(vec![
525                Object::tuple(vec![Object::int(1), Object::int(2)]),
526                Object::int(3)
527            ]),
528            -333907151259015829
529        );
530        assert_eq!(t((0..20).map(Object::int).collect()), -9217304902224717415);
531    }
532
533    #[test]
534    fn a_list_has_no_hash_and_neither_does_a_tuple_holding_one() {
535        let refused = hash(&Object::list(vec![])).expect_err("a list has no hash");
536        assert_eq!(refused.message(), "unhashable type: 'list'");
537        // The tuple names what refused rather than naming itself, because the
538        // list is the thing to go and fix.
539        let nested = Object::tuple(vec![Object::int(1), Object::list(vec![])]);
540        assert_eq!(
541            hash(&nested).expect_err("a tuple holding a list has no hash"),
542            refused
543        );
544    }
545
546    #[test]
547    fn equal_strings_hash_equally_and_different_ones_usually_do_not() {
548        assert_eq!(h(&Object::str("hello")), h(&Object::str("hello")));
549        assert_ne!(h(&Object::str("hello")), h(&Object::str("hellp")));
550        // A string and the bytes that spell it are never equal, so their
551        // hashes are kept apart on purpose.
552        assert_ne!(
553            h(&Object::str("abc")),
554            h(&Object::Bytes(std::rc::Rc::from(&b"abc"[..])))
555        );
556    }
557
558    #[test]
559    fn a_key_is_the_hash_and_pythons_equality_rather_than_rusts() {
560        let key = |object| Key::new(object).expect("expected this to be hashable");
561        assert_eq!(key(Object::int(1)), key(Object::Float(1.0)));
562        assert_eq!(key(Object::int(1)), key(Object::Bool(true)));
563        assert_eq!(key(Object::int(0)), key(Object::Bool(false)));
564        assert_ne!(key(Object::int(1)), key(Object::int(2)));
565        assert_ne!(key(Object::str("1")), key(Object::int(1)));
566        assert_eq!(key(Object::int(1)).hash(), 1);
567
568        let refused = Key::new(Object::list(vec![])).expect_err("a list is not a key");
569        assert_eq!(refused.message(), "unhashable type: 'list'");
570    }
571
572    /// `x == x` is false for a NaN, so a dict that only asked `==` would store
573    /// one under a key it could never find again. Identity comes first.
574    ///
575    /// CPython goes further than we can: two separately made NaNs are two
576    /// objects there and so are two keys, where a float is an immediate here
577    /// and they are one. That is the same divergence [`Object::is`] documents
578    /// for a large integer, and it is the loudest case of it.
579    #[test]
580    fn a_nan_can_be_a_key_and_can_be_found_again() {
581        let nan = Object::Float(f64::NAN);
582        let key = Key::new(nan.clone()).expect("expected this to be hashable");
583        let same = Key::new(nan).expect("expected this to be hashable");
584        assert_eq!(key, same);
585        // A NaN is not equal to anything, including another number.
586        let other = Key::new(Object::Float(1.0)).expect("expected this to be hashable");
587        assert_ne!(key, other);
588        assert!(!Object::Float(f64::NAN).equals(&Object::Float(f64::NAN)));
589    }
590
591    #[test]
592    fn an_integer_and_a_float_are_equal_when_they_are_the_same_number() {
593        assert!(int_eq_float(&Int::Small(1), 1.0));
594        assert!(!int_eq_float(&Int::Small(1), 1.5));
595        assert!(!int_eq_float(&Int::Small(1), f64::NAN));
596        assert!(!int_eq_float(&Int::Small(1), f64::INFINITY));
597        assert!(int_eq_float(
598            &Int::Small(i64::MIN),
599            -9_223_372_036_854_775_808.0
600        ));
601        // Past the word arm, where the answer has to stay exact rather than
602        // going through a float and losing the low bits.
603        let big = Int::parse("1208925819614629174706176", 10).expect("expected this to parse");
604        assert!(int_eq_float(&big, 2.0f64.powi(80)));
605        assert!(!int_eq_float(&big.add(&Int::Small(1)), 2.0f64.powi(80)));
606    }
607}