0

I have double types within my class and have to override equals()/hashCode(). So I need to compare double values.

Which is the correct way?

Version 1:

boolean isEqual(double a, double b){
    return Double.doubleToLongBits(a) == Double.doubleToLongBits(b);}

Version 2:

boolean isEqual(double a, double b){
    final double THRESHOLD = .0001;
    return Math.abs(a - b) < THRESHOLD;
}

Or should I avoid primitive double at all and use its wrapper type Double ? With this I can use Objects.equals(a,b), if a and b are Double.

nimo23
  • 5,170
  • 10
  • 46
  • 75
  • 3
    Do you want to require the doubles to be exactly equal, or nearly equal? Bearing in mind that "nearly equal" is not compatible with using the double as part of a hash code. – khelwood Sep 30 '19 at 11:05
  • @khelwood I want to use this method within my overriden hashCode()/equals()-method of the class.. – nimo23 Sep 30 '19 at 11:06
  • @nimo23 If you have two doubles that are nearly the same but not exactly, would you like them to show as equal or not equal? – khelwood Sep 30 '19 at 11:07
  • @nimo23 That does not answer khelwood's question. What do you consider "equal"? Floating Point values are prone to tiny errors which will make them unequal even though they should be. – Fildor Sep 30 '19 at 11:08
  • they should be the same even if they are only nearly equal..Should I better use the wrapper type `Double` instead of `double`? Because with `Double`, I can compare the objects and dont need to worry about `double`-comparisons.. – nimo23 Sep 30 '19 at 11:10
  • Perhaps you should explain a bit about your class. – Kayaman Sep 30 '19 at 11:10
  • 1
    Your first version checks if the values are precisely the same. The second version checks if they are within `.0001` of each other. Which (if either) of those is appropriate depends on what you use it for. If you use the second you'll have to omit the double from the calculation of your hash code. – khelwood Sep 30 '19 at 11:11
  • my class has a property of type double and I need to add this property within my equals()/hashCode() of this class. So I need to check that property for equality.. – nimo23 Sep 30 '19 at 11:11
  • @khelwood so the right way is to use version 1. Thanks. I accept this as an answer. The possible duplicate post does not show this as the right answer. – nimo23 Sep 30 '19 at 11:12
  • I meant properly explain about your class. We already know you have doubles and you have trouble comparing those doubles. But why do you have those in your class? That affects how you treat them. – Kayaman Sep 30 '19 at 11:15
  • I could change that to Double, however it needs more space and could be null. Should I change it to Double? By the way, why not using `Double.compare(a,b)`? I could use this `Double.compare()` with primitive types – nimo23 Sep 30 '19 at 11:17
  • can you use `BigDecimal` instead? You are subjecting yourself to rounding errors of type `new YourClass(0.3d).equals(new YourClass(0.1 + 0.2)) //returns false` or remove the value from the notion of equality (and hashCode) – diginoise Sep 30 '19 at 11:20
  • Which one should I use `Double.doubleToRawLongBits()`, `Double.doubleToLongBits` or `Double.compare()`? – nimo23 Sep 30 '19 at 11:21
  • Should I use BigDecimal only because I need to override equals()-method of the class? Bad idea. – nimo23 Sep 30 '19 at 11:22
  • Using imprecise representation to establish equality is equally bad idea – diginoise Sep 30 '19 at 11:26
  • @diginoise So why does Java has the Double.compare(a,b) method? Before using BigDecimal, I am better to use `Double` and compare that object with Objects.equals(a,b). No need to use BigDecimal. It is much slower and needs more memory than double. – nimo23 Sep 30 '19 at 11:32
  • @nimo23 you are trading correctness (as in Java `0.1d + 0.2d != 0.3d`) for performance benefits. In other words it will be a bit faster, but a whole lot incorrect. – diginoise Sep 30 '19 at 11:40
  • When speaking decimal 0.1d + 0.2d != 0.3d to a binary computer, it thinks you're saying 0.100000000000000005551115123126 + 0.200000000000000011102230246252 != 0.299999999999999988897769753748. – Mark Jeronimus Sep 30 '19 at 12:42

2 Answers2

3

The recommended way for use in equals/hashcode methods[citation needed] is to use Double.doubleToLongBits() and Double.hashcode() respectively.

This is because the contract of equals requires the two inputs to evaluate to 'different' if the hash codes are different. The other way around has no restriction.

(Note: It turns out that Double.compare() internally uses doubleToLongBits() but this is not specified by the API. As such I won't recommend it. On the other hand, hashCode() does specify that it uses doubleToLongBits().)

Practical example:

@Override
public boolean equals(Object obj) {
    if (obj == null || getClass() != obj.getClass())
        return false;

    Vector2d other = (Vector2d)obj;
    return Double.doubleToLongBits(x) == Double.doubleToLongBits(other.x) &&
           Double.doubleToLongBits(y) == Double.doubleToLongBits(other.y);
}

@Override
public int hashCode() {
    int hash = 0x811C9DC5;
    hash ^= Double.hashCode(x);
    hash *= 0x01000193;
    hash ^= Double.hashCode(y);
    hash *= 0x01000193;
    return hash;
}
Mark Jeronimus
  • 9,278
  • 3
  • 37
  • 50
  • You have to use return `Double.compare(a, b) == 1`; – nimo23 Sep 30 '19 at 11:44
  • Almost correct, but good point. – Mark Jeronimus Sep 30 '19 at 11:44
  • One question: If I had the wrapper type `Double` instead of `double`, then I could use `Objects.equals(a,b)` and dont need `Double.compare()`. Am I right? – nimo23 Sep 30 '19 at 11:47
  • Changed the answer as I misinterpreted the original intent – Mark Jeronimus Sep 30 '19 at 11:53
  • 1
    @nimo23 `Double.equals` uses `doubleToLongBits`(-0.0 and +0.0 differ) but has the "advantage" that NaN's are considered equal. All in all float and double are the incorrrigible drunk types. – Joop Eggen Sep 30 '19 at 11:53
  • But what should I use if I want to treat `-0.0`as the same as `+0.0`? – nimo23 Sep 30 '19 at 12:04
  • According to https://stackoverflow.com/questions/23438530/meaning-of-double-doubletolongbitsx, with `doubleToLongBits` `+0.0` is equal to `-0.0`. – nimo23 Sep 30 '19 at 12:12
  • @nimo23 how can the bits of `+0.0` be the same as `-0.0`? the machine must have a way to differentiate them. (`+0.0 -> 0L`; `-0.0 -> -9223372036854775808L`) – user85421 Sep 30 '19 at 12:14
  • 2
    I tested it before and it's not. When you want to treat them equally, you should 'normalize' the double values, either as a class restraint or in these methods themseves. Normalizing is as simple as `x+0` which as expected converts -0.0 to 0.0. – Mark Jeronimus Sep 30 '19 at 12:17
  • @nimo23 are you sure it isn't meant to be what `==` does in that post? And again, how can the virtual machine differentiate them (despite of the obvious output I am getting) – user85421 Sep 30 '19 at 12:19
  • @MarkJeronimus so both `doubleToLongBits` and `doubleToRawLongBits` see `+0.0 != -0.0` ? – nimo23 Sep 30 '19 at 12:19
  • Correct. Although they sometimes can give different results, i think one case is with subnormal numbers. That SO post you linked only says NaN is different with `==` and equal with `doubleToLongBits()` – Mark Jeronimus Sep 30 '19 at 12:20
  • I guess, the only difference of `doubleToLongBits` and `doubleToRawLongBits` is when it comes about the comparision of NaN: `doubleToLongBits` (`NaN == NaN`) and `doubleToRawLongBits` (`NaN != NaN`). For both, `+0.0 != -0.0` is true. – nimo23 Sep 30 '19 at 12:22
  • `doubleToLongBits` is calling `doubleToRawLongBits`, only doing an additional change if the value is `NaN` (instead of returning all possible values that represent `NaN`,, it returns `0x7ff8000000000000L`). Doc of raw one: "If the argument is NaN, the result is the {@code long} integer representing the actual NaN value. Unlike the {@code doubleToLongBits} method, {@code doubleToRawLongBits} does not collapse all the bit patterns encoding a NaN to a single "canonical" NaN value" – user85421 Sep 30 '19 at 12:23
  • @CarlosHeuberger so to sum up: for `doubleToLongBits` (`NaN == NaN` is `TRUE`) and for `doubleToRawLongBits` (`NaN != NaN` is `TRUE`). Am I right? – nimo23 Sep 30 '19 at 12:27
  • 2
    No. For `doubleToLongBits` all NaN are equal and for `doubleToRawLongBits` only identical NaN are equal. Note: Any NaN other than Double.NaN is never generated as a result of an expression, only when you use `Double.longBitsToDouble()`. – Mark Jeronimus Sep 30 '19 at 12:38
2

double values should not be used as a component to establish object equality and therefore its hashcode.

It comes from the fact that there is inherent imprecision in floating point numbers and double saturates artificially at +/-Infinity

To illustrate this problem:

System.out.println(Double.compare(0.1d + 0.2d, 0.3d));
System.out.println(Double.compare(Math.pow(3e27d, 127d), 17e256d / 7e-128d));

prints:

1
0

... which translates to the following 2 false statements:

0.1 + 0.2 > 0.3

(3 * 1027)127 == 17 * 10256 / (7 * 10-128)

So your software will make you act on 2 equal numbers being unequal, or 2 very large or very small unequal numbers being equal.

diginoise
  • 7,352
  • 2
  • 31
  • 39