What is negative zero (-0.0) in Java? Since -0.0 == 0.0 is true, how can the two be distinguished, and when does the distinction matter?
answer
- IEEE 754 keeps a sign bit even for zero → +0.0 and -0.0
- -0.0 == 0.0 is true (primitive value semantics)
- Double.compare(-0.0, 0.0) = -1; Double.equals says not equal
- Reciprocal trick: 1.0/-0.0 = -Infinity
- Matters in sorting, HashMap keys, equals/hashCode, sign-tracking
basics
~10 sFloating-point has two zeros: +0.0 and -0.0. They compare equal with ==, but you can tell them apart with Double.compare or by checking 1.0/x (gives +Infinity for +0.0, -Infinity for -0.0).
solid answer
~50 sIEEE 754 stores a sign bit even for zero, so Java has both +0.0 and -0.0. Negative zero appears from operations like -1.0 * 0.0 or underflow of a tiny negative value. With the primitive ==, -0.0 == 0.0 is true (they are numerically equal), and arithmetic mostly ignores the sign. But they are distinguishable: Double.compare(-0.0, 0.0) returns -1, Double.equals treats them as different, Double.doubleToRawLongBits gives different bit patterns, and dividing into them reveals the sign — 1.0/0.0 is +Infinity while 1.0/-0.0 is -Infinity. The distinction bites in three places: as a HashMap key or in a TreeSet/sort (the total-ordering path separates them), when the sign carries meaning (e.g. crossing-from-below), and in equals/hashCode implementations where Double.compare disagrees with ==. Use == when only the numeric value matters; use Double.compare when you need the IEEE total order.
code
java · 11 linesdouble negZero = -1.0 * 0.0; // -0.0
System.out.println(negZero == 0.0); // true (value semantics)
System.out.println(Double.compare(negZero, 0.0)); // -1 (total ordering)
System.out.println(1.0 / negZero); // -Infinity (reciprocal trick)
System.out.println(Double.doubleToRawLongBits(negZero)); // -9223372036854775808
// HashMap treats them as different keys (Double.equals/hashCode differ):
var m = new java.util.HashMap<Double, String>();
m.put(0.0, "pos");
m.put(-0.0, "neg");
System.out.println(m.size()); // 2go deeper
Aware that -0.0 exists and that -0.0 == 0.0 is true.
Can produce -0.0 and knows at least one way to distinguish it (Double.compare or the reciprocal trick).
Explains the primitive-vs-total-ordering split and where -0.0 causes real bugs (sorting, HashMap keys, equals/hashCode).
Defines team conventions for double equality (Double.compare), reasons about sign-carrying semantics in domain models, and audits serialization for silent normalization.
## Why there are two zeros **IEEE 754** represents every floating-point number with a separate **sign bit**. That sign bit exists even when the magnitude is zero, so there are two distinct zeros: **positive zero `+0.0`** and **negative zero `-0.0`**. They are numerically equal but have different bit patterns. ## How -0.0 is produced - `-1.0 * 0.0` → `-0.0` - `0.0 / -1.0` → `-0.0` - `-1.0e-300 / 1.0e300` → `-0.0` (underflow: a tiny negative result rounds to negative zero) - `Math.round`-style negatives, parsing the literal `-0.0`, etc. ## The surprising equality With the **primitive** comparison operator: ```java -0.0 == 0.0 // true -0.0 < 0.0 // false ``` So as far as `==`/`<`/`>` are concerned, the two zeros are the *same number*. Most arithmetic also ignores the sign of zero: `(-0.0) + 0.0 == 0.0`, and adding them gives `+0.0`. ## How to distinguish them Three reliable techniques: 1. **Reciprocal trick** — divide into the zero and inspect the infinity sign: ```java 1.0 / 0.0 // +Infinity 1.0 / -0.0 // -Infinity ``` 2. **Total-ordering library calls** — these treat `-0.0` as strictly less than `+0.0`: ```java Double.compare(-0.0, 0.0) // -1 Double.valueOf(-0.0).equals(0.0) // false ``` 3. **Raw bits**: ```java Double.doubleToRawLongBits(-0.0) // 0x8000000000000000 Double.doubleToRawLongBits(0.0) // 0x0000000000000000 ``` (`doubleToLongBits` canonicalizes NaN but still keeps the two zeros distinct.) ## The two comparison worlds (the crux) This topic shares the same split as NaN: - **Primitive `==`** says `-0.0 == 0.0` (equal) — *value* semantics. - **`Double.compare` / `Double.equals` / boxed `compareTo`** say they differ, with `-0.0 < +0.0` — *total-ordering* semantics required for sorting and the equals/hashCode contract. This means a `HashMap<Double,…>` will store `+0.0` and `-0.0` under **different** keys (because `Double.equals`/`hashCode` differ), while a primitive-keyed structure or a manual `==` check would treat them as one. Likewise `Arrays.sort` will place `-0.0` before `+0.0`. ## When it actually matters 1. **Collections / sorting** — surprising duplicate-looking keys or ordering. 2. **equals()/hashCode() of value objects** holding doubles — using `Double.compare` (recommended) makes `-0.0` and `+0.0` unequal; using `==` makes them equal. Pick one deliberately. 3. **Sign-carrying domains** — e.g. a value approaching zero 'from below' in graphics, physics, or financial sign tracking, where `-0.0` records the direction. 4. **Serialization** — most formats render both as `0`, silently erasing the distinction. ## Rule of thumb If you only care about the numeric value, use `==` (then `-0.0` and `+0.0` are the same). If you need IEEE total ordering or contract-correct equality, use `Double.compare`, and be aware it separates the two zeros and orders NaN last.
- Which is recommended in a value object's equals() for double fields, == or Double.compare, and why?Double.compare — it gives a consistent total order, makes equals/hashCode self-consistent, and treats NaN as equal to itself. Using == is the source of subtle bugs because == says NaN != NaN and -0.0 == 0.0, which can break the equals contract.
- How would you detect -0.0 specifically without library calls?Check the reciprocal: x == 0.0 && 1.0/x == Double.NEGATIVE_INFINITY identifies negative zero; or inspect Double.doubleToRawLongBits(x) for the sign bit.
saying these in an interview costs you the question
- Claiming -0.0 and 0.0 are bit-identical (different sign bit)
- Assuming Double.compare matches == for the two zeros (it doesn't)
- Writing equals() with == on doubles and being surprised by -0.0 handling
- Thinking -0.0 never appears in practice (underflow and sign multiplication produce it)