Quelle serait la façon la plus efficace de comparer deux double
ou deux float
valeurs?
Faire cela n'est pas correct:
bool CompareDoubles1 (double A, double B)
{
return A == B;
}
Mais quelque chose comme:
bool CompareDoubles2 (double A, double B)
{
diff = A - B;
return (diff < EPSILON) && (-diff < EPSILON);
}
Semble au traitement des déchets.
Quelqu'un connaît-il un comparateur de flotteurs plus intelligent?
<invoke Knuth>
L'optimisation prématurée est la racine de tout Mal.</invoke Knuth>
Allez simplement avec abs (ab) <EPS comme indiqué ci-dessus, c'est clair et facile à comprendre.==
peut être parfaitement correcte, mais cela dépend entièrement du contexte non donné dans la question. Jusqu'à ce que ce contexte soit connu,==
il reste le "moyen le plus efficace" .Réponses:
Soyez extrêmement prudent en utilisant l'une des autres suggestions. Tout dépend du contexte.
J'ai passé beaucoup de temps à retracer un bogue dans un système qui présumait
a==b
si|a-b|<epsilon
. Les problèmes sous-jacents étaient les suivants:La présomption implicite dans un algorithme que si
a==b
etb==c
alorsa==c
.Utiliser le même epsilon pour les lignes mesurées en pouces et les lignes mesurées en mils (0,001 pouce). Ce n'est
a==b
que1000a!=1000b
. (C'est pourquoi AlmostEqual2sComplement demande le epsilon ou le max ULPS).L'utilisation du même epsilon pour le cosinus des angles et la longueur des lignes!
Utiliser une telle fonction de comparaison pour trier les éléments d'une collection. (Dans ce cas, l'utilisation de l'opérateur C ++ intégré == pour les doubles a produit des résultats corrects.)
Comme je l'ai dit: tout dépend du contexte et de la taille attendue de
a
etb
.BTW,
std::numeric_limits<double>::epsilon()
c'est la "machine epsilon". C'est la différence entre 1.0 et la valeur suivante représentable par un double. Je suppose que cela pourrait être utilisé dans la fonction de comparaison, mais uniquement si les valeurs attendues sont inférieures à 1. (C'est en réponse à la réponse de @ cdv ...)De plus, si vous avez essentiellement de l'
int
arithmétiquedoubles
(ici, nous utilisons des doubles pour contenir des valeurs int dans certains cas), votre arithmétique sera correcte. Par exemple, 4.0 / 2.0 sera identique à 1.0 + 1.0. C'est aussi longtemps que vous ne faites pas de choses qui aboutissent à des fractions (4.0 / 3.0) ou ne sortez pas de la taille d'un int.la source
fabs(a)+fabs(b)
mais avec la compensation de NaN, 0 somme et débordement, cela devient assez complexe.float
/double
est MANTISSA x 2 ^ EXP .epsilon
dépendra de l'exposant. Par exemple, si la mantisse est 24 bits et l' exposant est signé 8 bits, alors1/(2^24)*2^127
ou~2^103
est unepsilon
pour certaines valeurs; ou s'agit-il d'un epsilon minimum ?|a-b|<epsilon
, n'est pas correct. Veuillez ajouter ce lien à votre réponse; si vous acceptez cygnus-software.com/papers/comparingfloats/comparingfloats.htm et que je peux supprimer mes commentaires stupides.La comparaison avec une valeur epsilon est ce que la plupart des gens font (même dans la programmation de jeux).
Vous devez cependant modifier un peu votre implémentation:
Edit: Christer a ajouté une pile d'excellentes informations sur ce sujet dans un récent article de blog . Prendre plaisir.
la source
float a = 3.4; if(a == 3.4){...}
c'est à dire lorsque vous comparez un point flottant stocké avec un littéral | Dans ce cas, les deux nombres sont stockés, donc ils auront la même représentation, s'ils sont égaux, alors quel est le mal à fairea == b
?EPSILON
est défini commeDBL_EPSILON
. Normalement, ce sera une valeur spécifique choisie en fonction de la précision requise de la comparaison.EPSILON
la comparaison ne fonctionne pas lorsque les flotteurs sont grands, car la différence entre les flotteurs consécutifs devient également grande. Voir cet article .EPSILON
est à peu près inutile. Vous devez comparer avec un seuil qui a du sens pour les unités à portée de main. Aussi, utilisez-lestd::abs
car il est surchargé pour différents types de virgule flottante.J'ai trouvé que le cadre de test Google C ++ contient une belle implémentation basée sur un modèle multiplateforme d'AlmostEqual2sComplement qui fonctionne à la fois sur les doubles et les flottants. Étant donné qu'il est publié sous la licence BSD, son utilisation dans votre propre code ne devrait pas poser de problème, tant que vous conservez la licence. J'ai extrait le code ci-dessous de
http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.hhttps://github.com/google/googletest/blob /master/googletest/include/gtest/internal/gtest-internal.h et ajouté la licence en haut.Assurez-vous de #define GTEST_OS_WINDOWS à une certaine valeur (ou de changer le code où il est utilisé pour quelque chose qui correspond à votre base de code - c'est une licence BSD après tout).
Exemple d'utilisation:
Voici le code:
EDIT: Ce poste a 4 ans. Il est probablement toujours valide et le code est agréable, mais certaines personnes ont trouvé des améliorations. Il vaut mieux obtenir la dernière version du
AlmostEquals
droit à partir du code source de Google Test, et non celle que j'ai collée ici.la source
La comparaison des nombres à virgule flottante pour dépend du contexte. Étant donné que même changer l'ordre des opérations peut produire des résultats différents, il est important de savoir dans quelle mesure vous voulez que les nombres soient «égaux».
La comparaison des nombres à virgule flottante par Bruce Dawson est un bon point de départ lorsque l'on regarde la comparaison à virgule flottante.
Les définitions suivantes sont tirées de L'art de la programmation informatique par Knuth :
Bien sûr, choisir epsilon dépend du contexte et détermine dans quelle mesure vous voulez que les nombres soient égaux.
Une autre méthode de comparaison des nombres à virgule flottante consiste à examiner les ULP (unités à la dernière place) des nombres. Bien qu'il ne traite pas spécifiquement des comparaisons, le document Ce que tout informaticien devrait savoir sur les nombres à virgule flottante est une bonne ressource pour comprendre comment fonctionne le virgule flottante et quels sont les pièges, y compris ce qu'est ULP.
la source
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
sauvé ma vie. LOL Notez que cette version (je n'ai pas vérifié si cela s'applique également aux autres) prend également en compte le changement qui pourrait se produire dans la partie intégrante du nombre à virgule flottante (exemple:2147352577.9999997616 == 2147352576.0000000000
où vous pouvez clairement voir qu'il y a presque une différence de2
entre les deux nombres) ce qui est plutôt sympa! Cela se produit lorsque l'erreur d'arrondi accumulée déborde de la partie décimale du nombre.std::max(std::abs(a), std::abs(b))
(ou avecstd::min()
);std::abs
en C ++ est surchargé avec float & double types, donc cela fonctionne très bien (vous pouvez toujours garderfabs
pour la lisibilité cependant).Pour une approche plus approfondie, lisez Comparaison des nombres à virgule flottante . Voici l'extrait de code de ce lien:
la source
*(int*)&A;
" Violera-t-il la règle stricte d'alias?Réalisant que c'est un vieux fil, mais cet article est l'un des plus simples que j'ai trouvé en comparant les nombres à virgule flottante et si vous voulez en savoir plus, il contient également des références plus détaillées et le site principal couvre une gamme complète de problèmes gestion des nombres à virgule flottante Le guide des virgules flottantes: comparaison .
Nous pouvons trouver un article un peu plus pratique dans Tolérances en virgule flottante revisité et note qu'il existe un test de tolérance absolue , qui se résume à cela en C ++:
et test de tolérance relative :
L'article note que le test absolu échoue quand
x
ety
sont grands et échoue dans le cas relatif quand ils sont petits. En supposant que la tolérance absolue et relative est la même, un test combiné ressemblerait à ceci:la source
La façon portable d'obtenir epsilon en C ++ est
Ensuite, la fonction de comparaison devient
la source
J'ai fini par passer pas mal de temps à parcourir le matériel dans ce grand fil. Je doute que tout le monde veuille passer autant de temps, je voudrais donc souligner le résumé de ce que j'ai appris et la solution que j'ai mise en œuvre.
Résumé rapide
numeric_limits::epsilon()
est le même que FLT_EPSILON dans float.h. Ceci est cependant problématique car epsilon pour comparer des valeurs comme 1.0 n'est pas identique à epsilon pour des valeurs comme 1E9. Le FLT_EPSILON est défini pour 1.0.fabs(a-b) <= epsilon
cependant que cela ne fonctionne pas car epsilon par défaut est défini pour 1.0. Nous devons augmenter ou diminuer epsilon en termes de a et b.max(a,b)
soit vous pouvez obtenir les prochains nombres représentables autour de a, puis voir si b tombe dans cette plage. La première est appelée méthode "relative" et plus tard est appelée méthode ULP.Implémentation des fonctions utilitaires (C ++ 11)
la source
isDefinitelyLessThan
vérifiediff < tolerance
, ce qui signifie que a et b sont presque égaux (et donc a n'est certainement pas inférieur à b). N'est-il pas plus logique de vérifier la tolérance diff> dans les deux cas? Ou peut-être ajoutez unorEqualTo
argument qui contrôle si la vérification d'égalité approximative doit renvoyer true ou non.Le code que vous avez écrit est buggé:
Le code correct serait:
(... et oui c'est différent)
Je me demande si les fabs ne vous feraient pas perdre l'évaluation paresseuse dans certains cas. Je dirais que cela dépend du compilateur. Vous voudrez peut-être essayer les deux. S'ils sont en moyenne équivalents, prenez l'implémentation avec des fabs.
Si vous avez des informations sur lequel des deux flotteurs est plus susceptible d'être plus grand que les autres, vous pouvez jouer dans l'ordre de la comparaison pour mieux profiter de l'évaluation paresseuse.
Enfin, vous pourriez obtenir de meilleurs résultats en intégrant cette fonction. Peu susceptible de s'améliorer cependant ...
Edit: OJ, merci d'avoir corrigé votre code. J'ai effacé mon commentaire en conséquence
la source
C'est très bien si:
Mais sinon, cela vous mènera à des ennuis. Les nombres à double précision ont une résolution d'environ 16 décimales. Si les deux nombres que vous comparez sont plus grands que EPSILON * 1.0E16, alors vous pourriez aussi bien dire:
J'examinerai une approche différente qui suppose que vous devez vous soucier du premier problème et que le second convient à votre application. Une solution serait quelque chose comme:
C'est coûteux en calcul, mais c'est parfois ce qui est demandé. C'est ce que nous devons faire dans mon entreprise car nous avons affaire à une bibliothèque d'ingénierie et les intrants peuvent varier de quelques dizaines d'ordre de grandeur.
Quoi qu'il en soit, le point est le suivant (et s'applique à pratiquement tous les problèmes de programmation): Évaluez vos besoins, puis trouvez une solution pour répondre à vos besoins - ne présumez pas que la réponse facile répondra à vos besoins. Si après votre évaluation, vous trouvez que
fabs(a-b) < EPSILON
cela suffira, parfait - utilisez-le! Mais soyez conscient de ses lacunes et des autres solutions possibles.la source
Comme d'autres l'ont souligné, l'utilisation d'un epsilon à exposant fixe (tel que 0,000000001) sera inutile pour les valeurs éloignées de la valeur epsilon. Par exemple, si vos deux valeurs sont 10000.000977 et 10000, il n'y a AUCUNE valeur à virgule flottante de 32 bits entre ces deux nombres - 10000 et 10000.000977 sont aussi proches que possible sans être identiques bit par bit. Ici, un epsilon inférieur à 0,0009 n'a pas de sens; vous pourriez aussi bien utiliser l'opérateur d'égalité directe.
De même, à mesure que les deux valeurs approchent epsilon, l'erreur relative augmente à 100%.
Ainsi, essayer de mélanger un nombre à virgule fixe tel que 0,00001 avec des valeurs à virgule flottante (où l'exposant est arbitraire) est un exercice inutile. Cela ne fonctionnera que si vous pouvez être assuré que les valeurs d'opérande se trouvent dans un domaine étroit (c'est-à-dire près d'un exposant spécifique) et si vous sélectionnez correctement une valeur epsilon pour ce test spécifique. Si vous sortez un numéro de l'air ("Hé! 0.00001 est petit, donc ça doit être bon!"), Vous êtes condamné à des erreurs numériques. J'ai passé beaucoup de temps à déboguer un mauvais code numérique où un pauvre schmuck lance des valeurs epsilon aléatoires pour faire fonctionner un autre cas de test.
Si vous effectuez une programmation numérique de quelque nature que ce soit et que vous pensez que vous devez atteindre des epsilons à virgule fixe, LISEZ L'ARTICLE DE BRUCE SUR LA COMPARAISON DES NUMÉROS À POINTS FLOTTANTS .
Comparaison des nombres à virgule flottante
la source
Qt implémente deux fonctions, vous pouvez peut-être en tirer des leçons:
Et vous pourriez avoir besoin des fonctions suivantes, puisque
la source
La comparaison à usage général des nombres à virgule flottante n'a généralement aucun sens. Comment comparer dépend vraiment d'un problème à portée de main. Dans de nombreux problèmes, les nombres sont suffisamment discrétisés pour permettre de les comparer à l'intérieur d'une tolérance donnée. Malheureusement, il y a autant de problèmes, où une telle astuce ne fonctionne pas vraiment. Par exemple, envisagez de travailler avec une fonction Heaviside (étape) d'un nombre en question (les options sur actions numériques viennent à l'esprit) lorsque vos observations sont très proches de la barrière. Effectuer une comparaison basée sur la tolérance ne serait pas très utile, car cela déplacerait efficacement le problème de la barrière d'origine à deux nouvelles. Encore une fois, il n'y a pas de solution générale à de tels problèmes et la solution particulière pourrait nécessiter d'aller jusqu'à changer la méthode numérique afin d'atteindre la stabilité.
la source
Malheureusement, même votre code "inutile" est incorrect. EPSILON est la plus petite valeur qui pourrait être ajoutée à 1.0 et changer sa valeur. La valeur 1.0 est très importante - les nombres plus importants ne changent pas lorsqu'ils sont ajoutés à EPSILON. Maintenant, vous pouvez adapter cette valeur aux nombres que vous comparez pour savoir s'ils sont différents ou non. L'expression correcte pour comparer deux doubles est:
C'est au minimum. En général, cependant, vous voudriez tenir compte du bruit dans vos calculs et ignorer quelques-uns des bits les moins significatifs, donc une comparaison plus réaliste ressemblerait à:
Si les performances de comparaison sont très importantes pour vous et que vous connaissez la plage de vos valeurs, vous devez plutôt utiliser des nombres à virgule fixe.
la source
EPSILON
dans la question estDBL_EPSILON
ouFLT_EPSILON
? Le problème est dans votre propre imagination, où vous avez substituéDBL_EPSILON
(ce qui serait en effet le mauvais choix) dans un code qui ne l'a pas utilisé.Ma classe basée sur les réponses précédemment publiées. Très similaire au code de Google mais j'utilise un biais qui pousse toutes les valeurs NaN au-dessus de 0xFF000000. Cela permet une vérification plus rapide de NaN.
Ce code est destiné à démontrer le concept, et non à être une solution générale. Le code de Google montre déjà comment calculer toutes les valeurs spécifiques à la plate-forme et je ne voulais pas dupliquer tout cela. J'ai fait des tests limités sur ce code.
la source
Voici la preuve que l'utilisation
std::numeric_limits::epsilon()
n'est pas la réponse - elle échoue pour les valeurs supérieures à un:Preuve de mon commentaire ci-dessus:
L'exécution donne cette sortie:
Notez que dans le second cas (un et juste supérieur à un), les deux valeurs d'entrée sont aussi proches que possible, et comparent toujours comme non proches. Ainsi, pour des valeurs supérieures à 1,0, vous pourriez tout aussi bien utiliser un test d'égalité. Les epsilons fixes ne vous sauveront pas lors de la comparaison de valeurs à virgule flottante.
la source
return *(reinterpret_cast<double*>(&x));
que même si cela fonctionne généralement, c'est en fait un comportement indéfini.numeric_limits<>::epsilon
point de revêtement de sol IEEE 754.Trouvé une autre implémentation intéressante sur: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
la source
Je me méfierais beaucoup de ces réponses qui impliquent une soustraction à virgule flottante (par exemple, fabs (ab) <epsilon). Premièrement, les nombres à virgule flottante deviennent plus clairsemés à de plus grandes amplitudes et à des amplitudes suffisamment élevées où l'espacement est supérieur à epsilon, vous pourriez tout aussi bien faire a == b. Deuxièmement, la soustraction de deux nombres à virgule flottante très proches (comme ceux-ci auront tendance à l'être, étant donné que vous recherchez une quasi-égalité) est exactement la façon dont vous obtenez une annulation catastrophique .
Bien que non portable, je pense que la réponse de grom fait le meilleur travail pour éviter ces problèmes.
la source
a
etb
eux - mêmes. Il n'y a absolument aucun problème à utiliser la soustraction à virgule flottante dans le cadre d'une comparaison floue (bien que comme d'autres l'ont dit, une valeur epsilon absolue peut ou non être appropriée pour un cas d'utilisation donné.)Il existe en fait des cas dans les logiciels numériques où vous voulez vérifier si deux nombres à virgule flottante sont exactement égaux. J'ai posté ceci sur une question similaire
https://stackoverflow.com/a/10973098/1447411
Vous ne pouvez donc pas dire que "CompareDoubles1" est faux en général.
la source
Cela dépend de la précision de la comparaison. Si vous souhaitez comparer exactement le même nombre, choisissez simplement ==. (Vous ne voulez presque jamais faire cela à moins que vous ne vouliez réellement exactement le même nombre.) Sur n'importe quelle plate-forme décente, vous pouvez également faire ce qui suit:
comme a
fabs
tendance à être assez rapide. Par assez rapide, je veux dire que c'est essentiellement un bit ET, donc il vaut mieux être rapide.Et les astuces entières pour comparer les doubles et les flottants sont agréables mais tendent à rendre plus difficile la gestion efficace des différents pipelines CPU. Et ce n'est certainement pas plus rapide sur certaines architectures en ordre de nos jours en raison de l'utilisation de la pile comme zone de stockage temporaire pour les valeurs fréquemment utilisées. (Load-hit-store pour ceux qui s'en soucient.)
la source
En termes d'échelle de quantités:
Si
epsilon
est la petite fraction de la grandeur de la quantité (ie valeur relative) dans une certain sens physique etA
etB
types est comparable dans le même sens, que je pense que ce qui suit est tout à fait correct:la source
J'utilise ce code:
la source
epsilon
.epsilon
est simplement la distance entre 1 et le prochain nombre représentable après 1. Au mieux, ce code essaie juste de vérifier si les deux nombres sont exactement égaux l'un à l'autre, mais parce que les non-puissances de 2 sont multipliées parepsilon
, il ne fait même pas cela correctement.std::fabs(std::min(v1, v2))
c'est incorrect - pour les entrées négatives, il choisit celle avec la plus grande ampleur.J'écris ceci pour Java, mais peut-être que vous le trouvez utile. Il utilise des longs au lieu de doubles, mais prend en charge les NaN, les sous-normales, etc.
Gardez à l'esprit qu'après un certain nombre d'opérations en virgule flottante, le nombre peut être très différent de ce que nous attendons. Il n'y a pas de code pour résoudre ce problème.
la source
Que dis-tu de ça?
J'ai vu différentes approches - mais je n'ai jamais vu cela, donc je suis curieux d'entendre des commentaires aussi!
la source
J'ai utilisé cette fonction pour mon petit projet et cela fonctionne, mais notez ce qui suit:
Une erreur de double précision peut vous surprendre. Disons que epsilon = 1.0e-6, alors 1.0 et 1.000001 ne doivent PAS être considérés comme égaux selon le code ci-dessus, mais sur ma machine, la fonction les considère comme égaux, c'est parce que 1.000001 ne peut pas être précisément traduit en format binaire, il s'agit probablement de 1.0000009xxx. Je le teste avec 1.0 et 1.0000011 et cette fois j'obtiens le résultat attendu.
la source
Voici une autre solution avec lambda:
la source
Ma manière n'est peut-être pas correcte mais utile
Convertissez les deux flottants en chaînes, puis comparez les chaînes
la superposition d'opérateurs peut également être effectuée
la source
Vous ne pouvez pas comparer deux
double
avec un fixeEPSILON
. Selon la valeur dedouble
,EPSILON
varie.Une meilleure double comparaison serait:
la source
De manière plus générique:
la source
a
etb
sont déjà plus petits queepsilon()
là, la différence peut encore être significative. Inversement, si les nombres sont très grands, même quelques bits d'erreur feront échouer la comparaison même si vous vouliez que les nombres soient considérés comme égaux. Cette réponse est exactement le type d'algorithme de comparaison "générique" que vous voulez éviter.Pourquoi ne pas effectuer XOR au niveau du bit? Deux nombres à virgule flottante sont égaux si leurs bits correspondants sont égaux. Je pense que la décision de placer les bits d'exposant avant la mantisse a été prise pour accélérer la comparaison de deux flotteurs. Je pense que de nombreuses réponses ici manquent le point de comparaison epsilon. La valeur d'Epsilon dépend uniquement de la précision avec laquelle les nombres à virgule flottante sont comparés. Par exemple, après avoir fait de l'arithmétique avec des flottants, vous obtenez deux nombres: 2,5642943554342 et 2,5642943554345. Ils ne sont pas égaux, mais pour la solution, seuls 3 chiffres décimaux comptent donc ils sont égaux: 2,564 et 2,564. Dans ce cas, vous choisissez epsilon égal à 0,001. La comparaison d'Epsilon est également possible avec XOR au niveau du bit. Corrigez-moi si je me trompe.
la source