mruby-set: make hash algorithm order-independent

Replace FNV-1a with XOR-based hash algorithm to ensure sets with identical
contents produce the same hash value regardless of insertion order.

The original FNV-1a algorithm was order-dependent, causing Set[1,2,3] and
Set[3,1,2] to have different hash values despite being equal sets. This
became problematic with small table optimization where iteration order
differs from hash table order.

The new algorithm uses commutative XOR operations with golden ratio mixing
to maintain good distribution properties while ensuring hash consistency.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-08-02 12:33:55 +09:00
parent 069da276f6
commit ccdfc9a6bd
+7 -10
View File
@@ -650,31 +650,28 @@ set_hash_m(mrb_state *mrb, mrb_value self)
{
kset_t *set = set_get_kset(mrb, self);
/* Use FNV-1a hash algorithm with better distribution properties */
uint64_t hash = 0xcbf29ce484222325ULL; /* FNV offset basis */
const uint64_t fnv_prime = 0x100000001b3ULL; /* FNV prime */
/* Use order-independent hash algorithm for sets */
uint64_t hash = 0; /* Start with zero for XOR accumulation */
/* Include the size of the set in the hash */
size_t size = kset_size(set);
hash ^= size;
hash *= fnv_prime;
hash ^= size * 0x9e3779b97f4a7c15ULL; /* Multiply by golden ratio prime */
if (!kset_is_uninitialized(set) && size > 0) {
/* Process each element */
/* Process each element - order independent using XOR */
int ai = mrb_gc_arena_save(mrb);
KSET_FOREACH(set, k) {
/* Get element's hash code */
khint_t elem_hash = (khint_t)mrb_obj_hash_code(mrb, kset_key(set, k));
/* Mix using FNV-1a algorithm */
hash ^= elem_hash;
hash *= fnv_prime;
/* XOR is commutative, so order doesn't matter */
hash ^= elem_hash * 0x9e3779b97f4a7c15ULL; /* Mix with golden ratio */
mrb_gc_arena_restore(mrb, ai);
}
}
/* Final mixing to improve avalanche effect */
/* Final mixing to improve distribution */
hash ^= hash >> 32;
return mrb_fixnum_value((mrb_int)hash);