Understanding Java Float -0.0 and hashCode: Why 0.0 and -0.0 Behave Differently in Maps
This article explains why Java's Float values 0.0 and -0.0 have different hashCode results, how that affects their use as Map keys, and demonstrates the underlying IEEE‑754 representation and related debugging steps with concrete code examples.
The author encountered a puzzling case while solving the LeetCode‑style problem "max‑points‑on‑a‑line" in Java: points that should lie on the same line were counted incorrectly because the map used Float slopes as keys, and the values 0.0f and -0.0f produced different hash codes.
Initial code snippet:
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int maxPoints(Point[] points) {
if (points.length <= 2) {
return points.length;
}
int max = 2;
for (int i = 0; i < points.length - 1; i++) {
Map<Float, Integer> map = new HashMap<>(16);
int ver = 0, cur, dup = 0;
for (int j = i + 1; j < points.length; j++) {
if (points[j].x == points[i].x) {
if (points[j].y != points[i].y) {
ver++;
} else {
dup++;
}
} else {
float d = (float) ((points[j].y - points[i].y) / (double) (points[j].x - points[i].x));
map.put(d, map.get(d) == null ? 1 : map.get(d) + 1);
}
}
cur = ver;
for (int v : map.values()) {
cur = Math.max(v, cur);
}
max = Math.max(max, cur + dup + 1);
}
return max;
}
}Running a test with points (2,3), (3,3), (-5,3) produced a result of 2 because the slopes (3‑3)/(3‑2) = 0.0 and (3‑3)/(-5‑2) = -0.0 were treated as different keys. Debugging showed that 0.0 == -0.0 evaluates to true, but their hash codes differ.
System.out.println(0.0 == -0.0); // true
System.out.println(new Float(0.0).hashCode() == new Float(-0.0).hashCode()); // falseThe discrepancy originates from Float.hashCode() , which ultimately calls Float.floatToIntBits() . According to the IEEE‑754 standard, +0.0 and -0.0 have distinct bit patterns (0x00000000 vs 0x80000000), so their integer representations – and thus hash codes – differ.
public static int floatToIntBits(float value) {
int result = floatToRawIntBits(value);
if (((result & FloatConsts.EXP_BIT_MASK) == FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0)
result = 0x7fc00000; // canonical NaN
return result;
}Because Float.equals() also compares the raw bits, 0.0f and -0.0f are considered unequal, reinforcing the conclusion that using floating‑point numbers as map keys is unsafe.
The article concludes that Java floating‑point semantics follow IEEE‑754, which defines positive/negative zero, infinities, and NaN. These edge cases can cause unexpected behavior in collections, so developers should avoid using Float/Double as keys or employ a more robust representation such as the line equation Ax+By+C=0.
Finally, the author provides a short checklist of the key take‑aways and warns against relying on floating‑point values for hash‑based structures.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.