Implémentation «tolérant» `equals` &` hashCode` pour une classe avec des membres à virgule flottante

J’ai une classe avec un champ float . Par exemple:

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

Je dois pouvoir comparer les instances par valeur. Maintenant, comment puis-je implémenter correctement equals & hashCode ?

La manière habituelle d’implémenter equals et hashCode consiste à considérer tous les champs. Par exemple, Eclipse générera les equals suivants:

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

(et un hashCode similaire, qui calcule essentiellement le count* 31 + Float.floatToIntBits(floatValue) ).

Le problème avec ceci est que mes valeurs de FP sont sujettes à des erreurs d’arrondi (elles peuvent provenir d’une entrée utilisateur, d’une firebase database, etc.). J’ai donc besoin d’une comparaison “tolérante”.

La solution courante consiste à comparer en utilisant une valeur epsilon (voir, par exemple, Comparaison des flottants et des doublons IEEE pour l’égalité ). Cependant, je ne suis pas tout à fait sûr de savoir comment implémenter equals aide de cette méthode, tout en ayant un hashCode qui est composé d’ equals .

Mon idée est de définir le nombre de chiffres significatifs à comparer, puis de toujours arrondir à ce nombre de chiffres à la fois equals et hashCode :

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

Ensuite, si je remplace toutes les utilisations de floatValue par floatValue dans equals et hashCode , je devrais obtenir une comparaison “tolérante”, qui est cohérente avec hashCode .

  • Est-ce que ça va marcher?
  • Voyez-vous des problèmes avec cette approche?
  • Y a-t-il une meilleure manière de faire cela? Cela semble plutôt compliqué.

Le gros problème, c’est que deux valeurs de type float peuvent toujours être très proches mais toujours comparables. En gros, vous divisez la plage de valeurs en virgule flottante en compartiments – deux valeurs peuvent être très proches sans être dans le même compartiment. Imaginez que vous utilisiez deux chiffres significatifs, appliquant la troncature pour obtenir le compartiment, par exemple … alors 11.999999 et 12.000001 seraient inégaux, mais 12.000001 et 12.9999999 seraient égaux bien qu’ils soient beaucoup plus éloignés l’un de l’autre.

Malheureusement, si vous ne définissez pas de valeurs telles que celle-ci, vous ne pouvez pas implémenter les égaux de manière appropriée à cause de la transitivité: x et y peuvent être proches, y et z peuvent être proches, mais cela ne veut pas dire que x et z sont rapprochées.