复数等于方法

时间:2013-09-17 17:48:11

标签: java equals complex-numbers

我正在用Java创建一个复杂的数字类:

public class Complex {
    public final double real, imag;

    public Complex(double real, double imag) {
        this.real = real;
        this.imag = imag;
    }

    ... methods for arithmetic follow ...
}

我实现了这样的equals方法:

@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;
}

但是如果你重写equals,你也应该覆盖hashCode。其中一条规则是:

  

如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须产生相同的整数结果。

floatdouble==进行比较会进行数值比较,因此+0.0 == -0.0和NaN值不等于包括它们在内的所有内容。所以我尝试实现hashCode方法来匹配equals方法,如下所示:

@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);
}

但后来我意识到,如果任何一个字段是NaN,这在哈希映射中会有奇怪的效果,因为this.equals(this)总是为假,但也许这不是错误的。另一方面,我可以执行DoubleFloat所做的事情,其中​​equals方法比较+0.0 != -0.0,但仍然协调不同的NaN位模式,并让NaN == NaN,所以我得到了:

@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);
}

但是如果我这样做那么我的复数就不像真实数字那样+0.0 == -0.0。但我真的不需要将我的复杂数字放在哈希映射中 - 我只想做正确的事情,遵循最佳实践等等。现在我只是感到困惑。有人可以告诉我最好的方法吗?

3 个答案:

答案 0 :(得分:1)

我已经考虑过这个了。问题源于试图平衡两种equals的使用:IEEE 754算术比较和Object / hashtable比较。对于浮点类型,由于NaN,永远不能满足这两个需求。算术比较需要NaN != NaN,但是对象/哈希表比较(等于方法)需要this.equals(this)

Double根据Object的合同正确实施方法,因此NaN == NaN。它也+0.0 != -0.0。这两种行为与原始浮点数/双精度类型的比较相反。

java.util.Arrays.equals(double[], double[])以与DoubleNaN == NaN+0.0 != -0.0)相同的方式对元素进行比较。

java.awt.geom.Point2D在技术上是错误的。它的equals方法将坐标与==进行比较,因此this.equals(this)可能为false。同时,它的hashCode方法使用doubleToLongBits,因此即使equals返回true,它的hashCode对于两个对象也可以不同。 doc没有提到细微之处,这意味着问题并不重要:人们不会将这些类型的元组放在哈希表中! (如果他们这样做会不会很有效,因为你必须得到完全相同的数字才能得到一个相同的密钥。)

在像复数类这样的浮点元组中,equals和hashCode的最简单正确实现是完全覆盖它们。 如果您希望方法在帐户中获取值,那么正确的事情是Double所做的:在两种方法中使用doubleToLongBits(或floatToLongBits)。如果那不适合算术,则需要单独的方法;或许equals(Complex other, double epsilon)来比较公差范围内的相等数字。

请注意,您可以覆盖equals(Complex other)而不会干扰equals(Object other),但这似乎太令人困惑了。

答案 1 :(得分:0)

病态案例似乎是0.0 != -0.0,所以我确保永远不会发生,并完全按照Joshua Bloch在“Effective Java”中告诉你的方式做其余部分。

答案 2 :(得分:0)

或者,如果对象相等,则hashCode契约保证hashCodes相等,但如果对象不同,则hashtring不同。所以你可以使用this.real作为哈希码,并接受冲突。除非有关于您的库实际遇到的数字分布的先验知识,否则可能无法做得更好:您有128位值和32位散列,因此冲突是不可避免的(并且无害,除非您可以表明他们对你期望的数据集的查找感到悲观。)