web-dev-qa-db-fra.com

Comment implémenter le hachage flottant avec une égalité approximative

Supposons que nous ayons la classe Python (le problème existe dans Java identique avec equals et hashCode)

class Temperature:
    def __init__(self, degrees):
        self.degrees = degrees

degrees est la température en Kelvin sous forme de flotteur. Maintenant, je voudrais implémenter les tests d'égalité et le hachage pour Temperature d'une manière qui

  • compare les flottants à une différence epsilon au lieu des tests d'égalité directs,
  • et honore le contrat que a == b implique hash(a) == hash(b).
def __eq__(self, other):
    return abs(self.degrees - other.degrees) < EPSILON

def __hash__(self):
    return # What goes here?

La documentation Python parle un peu de nombres de hachage pour assurer que hash(2) == hash(2.0) mais ce n'est pas tout à fait le même problème.

Suis-je même sur la bonne voie? Et si c'est le cas, quelle est la méthode standard pour implémenter le hachage dans cette situation?

Mise à jour : Je comprends maintenant que ce type de test d'égalité pour les flottants élimine la transitivité de == Et equals. Mais comment cela va-t-il de pair avec la "connaissance commune" selon laquelle les flotteurs ne devraient pas être comparés directement? Si vous implémentez un opérateur d'égalité en comparant les flottants, les outils d'analyse statique se plaindront. Ont-ils raison de le faire?

15
Marten

implémenter les tests d'égalité et le hachage de la température d'une manière qui compare les flottants à une différence epsilon au lieu des tests d'égalité directs,

L'égalité floue viole les exigences que Java place sur la méthode equals, à savoir transitivité, c'est-à-dire que si x == y et y == z, puis x == z. Mais si vous faites une égalité floue avec, par exemple, un epsilon de 0,1, alors 0.1 == 0.2 et 0.2 == 0.3, mais 0.1 == 0.3 ne tient pas.

Alors que Python ne documente pas une telle exigence, les implications d'avoir une égalité non-transitive en font une très mauvaise idée; raisonner sur de tels types induit des maux de tête.

Je vous recommande donc fortement de ne pas le faire.

Soit fournir une égalité exacte et baser votre hachage sur cela de manière évidente, et fournir une méthode distincte pour faire la correspondance floue, soit opter pour l'approche de classe d'équivalence suggérée par Kain. Bien que dans ce dernier cas, je vous recommande de fixer votre valeur à un membre représentatif de la classe d'équivalence dans le constructeur, puis de choisir une égalité exacte simple et un hachage pour le reste; il est beaucoup plus facile de raisonner sur les types de cette façon.

(Mais si vous faites cela, vous pourriez aussi bien utiliser une représentation à virgule fixe au lieu de virgule flottante, c'est-à-dire que vous utilisez un entier pour compter les millièmes de degré, ou la précision que vous souhaitez.)

41
Sebastian Redl

bonne chance

Vous n'allez pas pouvoir y arriver, sans être stupide avec des hachages, ou sacrifier l'epsilon.

Exemple:

Supposons que chaque point soit haché selon sa propre valeur de hachage unique.

Comme les nombres à virgule flottante sont séquentiels, il y aura jusqu'à k nombres avant une valeur en virgule flottante donnée, et jusqu'à k nombres après une valeur en virgule flottante donnée qui se trouvent dans un epsilon du point donné.

  1. Pour chacun des deux points dans epsilon l'un de l'autre qui ne partagent pas la même valeur de hachage.

    • Ajustez le schéma de hachage afin que ces deux points de hachage aient la même valeur.
  2. Introniser pour toutes ces paires la séquence entière de nombres à virgule flottante s'effondrera vers une seule valeur.

Il y a quelques cas où cela ne sera pas vrai:

  • Infini positif/négatif
  • NaN
  • Quelques plages dénormalisées qui peuvent ne pas être liées à la plage principale pour un epsilon donné.
  • peut-être quelques autres instances spécifiques au format

Cependant> = 99% de la plage en virgule flottante hachera à une valeur unique pour toute valeur d'Epsilon qui comprend au moins une valeur en virgule flottante au-dessus ou en dessous d'une valeur en virgule flottante donnée.

Résultat

Soit> = 99% de la plage de virgule flottante entière hachée à une seule valeur, ce qui compromet sérieusement l'intention d'une valeur de hachage (et tout appareil/conteneur reposant sur un hachage à faible collision assez distribué).

Ou l'epsilon est tel que seules les correspondances exactes sont autorisées.

granulaire

Vous pouvez bien sûr opter pour une approche granulaire à la place.

Avec cette approche, vous définissez des compartiments exacts jusqu'à une résolution particulière. c'est à dire:

[0.001, 0.002)
[0.002, 0.003)
[0.003, 0.004)
...
[122.999, 123.000)
...

Chaque compartiment a un hachage unique et tout point flottant dans le compartiment est égal à n'importe quel autre flottant du même compartiment.

Malheureusement, il est toujours possible que deux flotteurs soient à une distance de epsilon et ont deux hachages séparés.

16
Kain0_0

Vous pouvez modéliser votre température sous forme d'entier sous le capot. La température a une limite inférieure naturelle (-273,15 Celsius). Donc, double (-273.15 est égal à 0 pour votre entier sous-jacent). Le deuxième élément dont vous avez besoin est la granularité de votre mappage. Vous utilisez déjà cette granularité implicitement; c'est votre EPSILON.

Divisez simplement votre température par EPSILON et prenez la parole, maintenant votre hachage et votre égal se comporteront de manière synchronisée. En Python 3 l'entier est illimité, EPSILON peut être plus petit si vous le souhaitez.

MÉFIEZ-VOUS Si vous modifiez la valeur d'EPSILON et que vous avez sérialisé l'objet, ils ne seront pas compatibles!

#Pseudo code
class Temperature:
    def __init__(self, degrees):
        #CHECK INVALID VALUES HERE
        #TRANSFORM TO KELVIN HERE
        self.degrees = Math.floor(kelvin/EPSILON)
7
Alessandro Teruzzi

L'implémentation d'une table de hachage à virgule flottante qui peut trouver des éléments "approximativement égaux" à une clé donnée nécessitera l'utilisation de deux approches ou d'une combinaison de celles-ci:

  1. Arrondissez chaque valeur à un incrément légèrement supérieur à la plage "floue" avant de la stocker dans la table de hachage, et lorsque vous essayez de trouver une valeur, vérifiez dans la table de hachage les valeurs arrondies au-dessus et en dessous de la valeur recherchée.

  2. Stockez chaque élément dans la table de hachage à l'aide de clés situées au-dessus et en dessous de la valeur recherchée.

Notez que l'utilisation de l'une ou l'autre approche nécessitera probablement que les entrées de table de hachage n'identifient pas les éléments, mais plutôt les listes, car il y aura probablement plusieurs éléments associés à chaque clé. La première approche ci-dessus minimisera la taille requise de la table de hachage, mais chaque recherche d'un élément ne figurant pas dans la table nécessitera deux recherches de table de hachage. La deuxième approche sera rapidement en mesure d'identifier que les éléments ne sont pas dans le tableau, mais nécessitera généralement que le tableau contienne environ deux fois plus d'entrées que ce qui serait autrement requis. Si l'on essaie de trouver des objets dans l'espace 2D, il peut être utile d'utiliser une approche pour la direction X et une pour la direction Y, de sorte qu'au lieu d'avoir chaque élément stocké une fois mais nécessitant quatre opérations de requête pour chaque recherche, ou d'être capable d'utiliser une recherche pour trouver un article mais devant stocker chaque article quatre fois, on stockerait chaque article deux fois et utiliserait deux opérations de recherche pour le trouver.

1
supercat

Vous pouvez bien sûr définir "presque égal" en supprimant, disons, les huit derniers bits de la mantisse, puis en comparant ou en hachant. Le problème est que les nombres très proches les uns des autres peuvent être différents.

Il y a une certaine confusion ici: si deux nombres à virgule flottante se comparent égaux, ils sont égaux. Pour vérifier si elles sont égales, vous utilisez "==". Parfois, vous ne voulez pas vérifier l'égalité, mais lorsque vous le faites, "==" est la voie à suivre.

0
gnasher729

Ce n'est pas une réponse, mais un commentaire détaillé qui peut être utile.

J'ai travaillé sur un problème similaire, en utilisant MPFR (basé sur GNU MP). L'approche "bucket" comme indiqué par @ Kain0_0 semble donner des résultats acceptables, mais soyez conscient des limites mises en évidence dans cette réponse.

Je voulais ajouter que - selon ce que vous essayez de faire - utiliser un "exact" ( caveat emptor ) système d'algèbre informatique comme Mathematica peut aider à compléter ou à vérifier un programme numérique inexact. Cela vous permettra de calculer les résultats sans vous soucier de l'arrondi, par exemple, 7*√2 - 5*√2 donnera 2 au lieu de 2.00000001 ou similaire. Bien sûr, cela entraînera des complications supplémentaires qui peuvent ou non en valoir la peine.

0
BurnsBA