I'm making a complex number class in Java like this:
public class Complex {
public final double real, imag;
public Complex(double real, double imag) {
this.real = real;
this.imag = imag;
}
... methods for arithmetic follow ...
}
I implemented the equals method like this:
@Override
public boolean equals(Object obj) {
if (obj instanceof Complex) {
Complex other = (Complex)obj;
return (
this.real == other.real &&
this.imag == other.imag
);
}
return false;
}
But if you override equals, you're supposed to override hashCode too. One of the rules is:
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
Comparing float
s and double
s with ==
does a numeric comparison, so +0.0 == -0.0
and NaN values are inequal to everything including themselves. So I tried implementing the hashCode method to match the equals method like this:
@Override
public int hashCode() {
long real = Double.doubleToLongBits(this.real); // harmonize NaN bit patterns
long imag = Double.doubleToLongBits(this.imag);
if (real == 1L << 63) real = 0; // convert -0.0 to +0.0
if (imag == 1L << 63) imag = 0;
long h = real ^ imag;
return (int)h ^ (int)(h >>> 32);
}
But then I realized that this would work strangely in a hash map if either field is NaN, because this.equals(this)
will always be false, but maybe that's not incorrect. On the other hand, I could do what Double
and Float
do, where the equals methods compare +0.0 != -0.0
, but still harmonize the different NaN bit patterns, and let NaN == NaN
, so then I get:
@Override
public boolean equals(Object obj) {
if (obj instanceof Complex) {
Complex other = (Complex)obj;
return (
Double.doubleToLongBits(this.real) ==
Double.doubleToLongBits(other.real) &&
Double.doubleToLongBits(this.imag) ==
Double.doubleToLongBits(other.imag)
);
}
return false;
}
@Override
public int hashCode() {
long h = (
Double.doubleToLongBits(real) +
Double.doubleToLongBits(imag)
);
return (int)h ^ (int)(h >>> 32);
}
But if I do that then my complex numbers don't behave like real numbers, where +0.0 == -0.0
. But I don't really need to put my Complex numbers in hash maps anyway -- I just want to do the right thing, follow best practices, etc. And now I'm just confused. Can anyone advise me on the best way to proceed?
The Complex Plane a is called the real part of the complex number, and b is called the imaginary part. Two complex numbers are equal if and only if their real parts are equal and their imaginary parts are equal. I.e., a+bi = c+di if and only if a = c, and b = d.
An equation of the form z= a+ib, where a and b are real numbers, is defined to be a complex number. The real part is denoted by Re z = a and the imaginary part is denoted by Im z = ib.
Among any two integers or real numbers one is larger, another smaller. But you can't compare two complex numbers.
"i" is defined as the number whose square is equal to negative 1. This is the definition of "i", and it leads to all sorts of interesting things. Now some places you will see "i" defined this way; "i" as being equal to the principle square root of negative one.
I've thought about this some more. The problem stems from trying to balance two uses of equals: IEEE 754 arithmetic comparison and Object/hashtable comparison. For floating-point types, the two needs can never be satisfied at once due to NaN. The arithmetic comparison wants NaN != NaN
, but the Object/hashtable comparison (equals method) requires this.equals(this)
.
Double
implements the methods correctly according to the contract of Object
, so NaN == NaN
. It also does +0.0 != -0.0
. Both behaviors are the opposite from comparisons on primitive float/double types.
java.util.Arrays.equals(double[], double[])
compares elements the same way as Double
(NaN == NaN
, +0.0 != -0.0
).
java.awt.geom.Point2D
does it technically wrong. Its equals method compares the coordinates with just ==
, so this.equals(this)
can be false. Meanwhile, its hashCode method uses doubleToLongBits
, so its hashCode can be different for two objects even when equals returns true. The doc makes no mention of the subtleties, which implies the issue is not important: people don't put these types of tuples in hash tables! (And it wouldn't be very effective if they did, because you'd have to get exactly the same numbers to get an equal key.)
In a tuple of floating-points like a complex number class, the simplest correct implementation of equals and hashCode is to not override them at all.
If you want the methods to take the value in account, then the correct thing to do is what Double
does: use doubleToLongBits
(or floatToLongBits
) in both methods. If that's not suitable for arithmetic, a separate method is needed; perhaps equals(Complex other, double epsilon)
to compare numbers for equality within a tolerance.
Note that you can override equals(Complex other)
without interfering with equals(Object other)
, but that seems too confusing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With