From 57d398b105908727ae92821cb7a88b6d298eb7b5 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 24 Jul 2025 09:10:44 +0900 Subject: [PATCH] hash.c: use value-based hash for numbers Fixes a correctness bug where float and bignum hash codes were based on object identity instead of their numerical value. This change introduces value-based hashing for these types, ensuring that two numbers with the same value produce the same hash code, as required by Ruby semantics. - Floats are now hashed based on their bit representation. - Bignums are hashed using the dedicated `mrb_bint_hash` function. This change makes hash behavior correct and more performant by avoiding VM callbacks for core numeric types. Co-authored-by: Gemini --- src/hash.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/hash.c b/src/hash.c index 2a27a7ffb..33846a3da 100644 --- a/src/hash.c +++ b/src/hash.c @@ -311,6 +311,17 @@ h_check_modified_validate(mrb_state *mrb, struct h_check_modified *checker, stru } } +#ifndef MRB_NO_FLOAT +static uint32_t +float_hash_code(mrb_float f) +{ + if (f == 0.0) return 0; + /* normalize -0.0 to 0.0 */ + if (f == -0.0) f = 0.0; + return mrb_byte_hash((const uint8_t*)&f, sizeof(f)); +} +#endif + uint32_t mrb_obj_hash_code(mrb_state *mrb, mrb_value key) { @@ -329,14 +340,18 @@ mrb_obj_hash_code(mrb_state *mrb, mrb_value key) case MRB_TT_INTEGER: if (mrb_fixnum_p(key)) { hash_code = U32(mrb_fixnum(key)); - break; } -#ifndef MRB_NO_FLOAT - /* fall through */ - case MRB_TT_FLOAT: +#ifdef MRB_USE_BIGINT + else { + hash_code = U32(mrb_integer(mrb_bint_hash(mrb, key))); + } #endif - hash_code = U32(mrb_obj_id(key)); break; +#ifndef MRB_NO_FLOAT + case MRB_TT_FLOAT: + hash_code = float_hash_code(mrb_float(key)); + break; +#endif default: hash_code_obj = mrb_funcall_argv(mrb, key, MRB_SYM(hash), 0, NULL); hash_code = U32(tt) ^ U32(mrb_integer(hash_code_obj));