Skip to content

Commit

Permalink
stdlib: better Hash for Float64/Float32
Browse files Browse the repository at this point in the history
fixes #90
  • Loading branch information
soc committed Dec 21, 2023
1 parent 8970cfe commit 5aef0c4
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 11 deletions.
12 changes: 10 additions & 2 deletions dora/stdlib/primitives.dora
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,10 @@ impl Float32 {
@pub @internal fun isNan(): Bool;
@pub @internal fun sqrt(): Float32;

@pub fun hash(): Int32 = self.asInt32();
@pub fun hash(): Int32 =
// discard the sign, such that 0.0 and -0.0 hash the same, at the cost of all positive numbers
// and their negative counterparts hashing the same, but keeping the computation branch-free.
self.asInt32() & 0x7FFFFFFFi32;

// should be lets, not funs
@pub @static fun bits(): Int32 = 32i32;
Expand Down Expand Up @@ -405,7 +408,12 @@ impl Float64 {
@pub @internal fun isNan(): Bool;
@pub @internal fun sqrt(): Float64;

@pub fun hash(): Int32 = self.asInt64().toInt32();
@pub fun hash(): Int32 {
// discard the sign, such that 0.0 and -0.0 hash the same, at the cost of all positive numbers
// and their negative counterparts hashing the same, but keeping the computation branch-free.
let hash = self.asInt64() & 0x7FFFFFFFFFFFFFFF;
(hash ^ hash.shiftRight(31i32)).toInt32()
}

// should be lets, not funs
@pub @static fun bits(): Int32 = 64i32;
Expand Down
12 changes: 12 additions & 0 deletions tests/float/float32-hash.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fun main(): Unit {
assert( 0.0f32 .hash() == 0i32);
assert((-0.0f32).hash() == 0i32);

assert( 1.0f32 .hash() == 1065353216i32);
assert((-1.0f32).hash() == 1065353216i32);

assert(Float32::infinityPositive().hash() == 2139095040i32);
assert(Float32::infinityNegative().hash() == 2139095040i32);

assert(Float32::notANumber().hash() == 2143289344i32);
}
12 changes: 12 additions & 0 deletions tests/float/float64-hash.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fun main(): Unit {
assert( 0.0 .hash() == 0i32);
assert((-0.0).hash() == 0i32);

assert( 1.0 .hash() == 2145386496i32);
assert((-1.0).hash() == 2145386496i32);

assert(Float64::infinityPositive().hash() == -2097152i32);
assert(Float64::infinityNegative().hash() == -2097152i32);

assert(Float64::notANumber().hash() == -1048576i32);
}
3 changes: 0 additions & 3 deletions tests/num-hash.dora
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@ fun main(): Unit {
assert(1.toUInt8().hash() == 1i32);
assert(1.hash() == 1i32);
assert(1i64.hash() == 1i32);
assert(1.0f32.hash() == 1065353216i32);
// double needs a better hash implementation
assert(1.0.hash() == 0i32);
}
8 changes: 4 additions & 4 deletions tests/stdlib/hashmap-contains-zero.dora
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ fun float64(): Unit {
assert(map.size() == 1);
assert(map.contains(0.0));
assert(map.get(0.0).getOrPanic() == "a");
assert(map.contains(-0.0)); // FIXME: this only works by coincidence, because the hash function sucks
assert(map.get(-0.0).getOrPanic() == "a"); // FIXME: this only works by coincidence, because the hash function sucks
assert(map.contains(-0.0));
assert(map.get(-0.0).getOrPanic() == "a");
}

fun float32(): Unit {
Expand All @@ -19,6 +19,6 @@ fun float32(): Unit {
assert(map.size() == 1);
assert(map.contains(0.0f32));
assert(map.get(0.0f32).getOrPanic() == "a");
assert(map.contains(-0.0f32).not()); // FIXME: should be true instead
// assert(map.get(-0.0f32).getOrPanic() == "a"); // FIXME: should be true instead
assert(map.contains(-0.0f32));
assert(map.get(-0.0f32).getOrPanic() == "a");
}
4 changes: 2 additions & 2 deletions tests/stdlib/hashset-contains-zero.dora
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ fun float64(): Unit {

assert(set.size() == 1);
assert(set.contains(0.0));
assert(set.contains(-0.0)); // FIXME: this only works by coincidence, because the hash function sucks
assert(set.contains(-0.0));
}

fun float32(): Unit {
let set = std::HashSet[Float32]::new(0.0f32);

assert(set.size() == 1);
assert(set.contains(0.0f32));
assert(set.contains(-0.0f32).not()); // FIXME: should be true instead
assert(set.contains(-0.0f32));
}

0 comments on commit 5aef0c4

Please sign in to comment.