2010-12-08 6 views
9

Ich habe eine Klasse mit einem float Feld. Zum Beispiel:Implementieren "tolerant" `equals` &` hashCode` für eine Klasse mit Gleitkommamitgliedern

public class MultipleFields { 
    final int count; 
    final float floatValue; 

    public MultipleFields(int count, float floatValue) { 
    this.count = count; 
    this.floatValue = floatValue; 
    } 

} 

Ich muss in der Lage sein, Instanzen nach Wert zu vergleichen. Nun, wie kann ich equals & hashCode richtig implementieren?

Die übliche Art zu implementieren equals und hashCode ist nur alle Felder zu berücksichtigen. Z.B. Eclipse wird die folgende equals erzeugen:

public boolean equals(Object obj) { 
    // irrelevant type checks removed 
    .... 
    MultipleFields other = (MultipleFields) obj; 
    if (count != other.count) 
     return false; 
    if (Float.floatToIntBits(floatValue) != Float.floatToIntBits(other.floatValue)) 
     return false; 
    return true; 
    } 

(und eine ähnliche hashCode, daß im wesentlichen berechnet count* 31 + Float.floatToIntBits(floatValue)).

Das Problem dabei ist, dass meine FP-Werte Rundungsfehler unterliegen (sie können von Benutzereingaben, von einem DB, etc. stammen). Also brauche ich einen "toleranten" Vergleich. Die gemeinsame Lösung besteht darin, einen Epsilon-Wert zu vergleichen (siehe z.B. Comparing IEEE floats and doubles for equality). Allerdings bin ich nicht ganz sicher, wie ich equals mit dieser Methode implementieren kann, und immer noch eine hashCode, die mit equals konsistent ist.

Meine Idee ist es, die Anzahl der signifikanten Stellen für den Vergleich, dann immer rund auf diese Anzahl von Ziffern in beide equals und hashCode zu definieren:

long comparisonFloatValue = Math.round(floatValue* (Math.pow(10, RELEVANT_DIGITS))); 

Dann, wenn ich alle Verwendungen von floatValue mit comparisonFloatValue in equals ersetzen und sollte ich einen "toleranten" Vergleich bekommen, der mit hashCode übereinstimmt.

  • Funktioniert das?
  • Sehen Sie irgendwelche Probleme mit diesem Ansatz?
  • Gibt es einen besseren Weg, dies zu tun? Es scheint ziemlich kompliziert.
+0

Haben Sie nur in Betracht gezogen, ** Fließkomma ** nicht zu verwenden und stattdessen zu einer Festkommadarstellung zu wechseln? –

+0

@Karl: Ja, guter Punkt, und ja, tat ich. Das Problem ist, dass die DB den Wert als FP enthält (was schwer zu ändern ist, da es nicht unter unserer Kontrolle steht), so dass ich beim Lesen aus der DB runden müsste. Ich möchte das nicht tun, da ich denke, dass es besser ist, FP intern zu verwenden und nur die Ausgabe zu runden. – sleske

+0

Hoppla, habe gerade bemerkt, dass ich den Operator^benutzt habe. Natürlich meinte ich die Potenzierung, nicht den binären Operator. Fixed um 'Math.pow' zu verwenden. – sleske

Antwort

10

Das große Problem dabei ist, dass zwei Float-Werte noch sehr nahe beieinander sein könnten, aber vergleichen noch ungleich. Im Grunde teilen Sie den Bereich der Gleitkommawerte in Buckets - und zwei Werte könnten sehr nahe beieinander liegen, ohne sich im selben Bucket zu befinden. Stellen Sie sich vor, Sie verwenden zwei signifikante Ziffern, indem Sie trunkation anwenden, um beispielsweise den Bucket zu erhalten ... dann wären 11.999999 und 12.000001 ungleich, aber 12.000001 und 12.9999999 wären gleich, obwohl sie viel weiter voneinander entfernt sind.

Wenn Sie nicht Bucket-Werte wie folgt verwenden, können Sie Equals aufgrund von Transitivität nicht entsprechend implementieren: x und y können eng beieinander liegen, y und z können eng beieinander liegen, aber das ist nicht möglich bedeuten, dass x und z nahe beieinander liegen.

+0

Entschuldigung, mein comparisonFloatValue wurde falsch berechnet (ich glaubte, Java benutzt '^' für die Potenzierung :-(). Bearbeitete meine Antwort. So sollten 0.99999999999999 und 1.0000000001 als gleich vergleichen, es sei denn, Sie setzen RELEVANT_DIGITS auf mehr als 9 oder so. – sleske

+0

Natürlich bleibt Ihr Punkt: Zwei FP-Werte werden in meiner Implementierung als unterschiedlich betrachtet, wenn sie in unterschiedliche Buckets fallen, und das kann bei beliebig nahe beieinanderliegenden FP-Zahlen passieren. Das ergibt also keinen "toleranten Vergleich": (. – sleske

+0

@sleske: Java verwendet^nicht zur Potenzierung - das ist XOR. Benutze Math.pow zur Potenzierung. Mein Beispiel war wahrscheinlich unglücklich - ich werde es bearbeiten. Aber ja, du musst deine Anforderungen grundlegend ausarbeiten :) –

Verwandte Themen