Tout d'abord, les valeurs à virgule flottante ne sont pas "aléatoires" dans leur comportement. Une comparaison exacte peut et a du sens dans de nombreux usages du monde réel. Mais si vous souhaitez utiliser la virgule flottante, vous devez savoir comment cela fonctionne. Se tromper en supposant que les virgules flottantes fonctionnent comme des nombres réels vous obtiendrez un code qui se casse rapidement. Se tromper du côté de l'hypothèse que les résultats en virgule flottante sont associés à de grandes fuzz aléatoires (comme la plupart des réponses suggèrent ici) vous obtiendra un code qui semble fonctionner au premier abord mais finit par avoir des erreurs de grande ampleur et des cas de coin brisés.
Tout d'abord, si vous souhaitez programmer avec virgule flottante, vous devez lire ceci:
Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante
Oui, lisez tout. Si c'est trop lourd, vous devez utiliser des entiers / point fixe pour vos calculs jusqu'à ce que vous ayez le temps de le lire. :-)
Maintenant, cela dit, les plus gros problèmes avec les comparaisons exactes en virgule flottante se résument à:
Le fait que de nombreuses valeurs que vous pouvez écrire dans la source, ou lire avec scanf
ou strtod
, n'existent pas en tant que valeurs à virgule flottante et sont converties en silence à l'approximation la plus proche. C'est ce dont parlait la réponse de demon9733.
Le fait que de nombreux résultats soient arrondis en raison du manque de précision pour représenter le résultat réel. Un exemple simple où vous pouvez le voir est l'ajout x = 0x1fffffe
et y = 1
les flottants. Ici, x
a 24 bits de précision dans la mantisse (ok) et y
n'a que 1 bit, mais lorsque vous les ajoutez, leurs bits ne sont pas à des endroits qui se chevauchent, et le résultat aurait besoin de 25 bits de précision. Au lieu de cela, il est arrondi ( 0x2000000
dans le mode d'arrondi par défaut).
Le fait que de nombreux résultats soient arrondis en raison de la nécessité d'avoir infiniment de places pour la valeur correcte. Cela inclut à la fois des résultats rationnels comme 1/3 (que vous connaissez depuis la décimale où il prend infiniment de places) mais aussi 1/10 (qui prend également infiniment de places en binaire, car 5 n'est pas une puissance de 2), ainsi que des résultats irrationnels comme la racine carrée de tout ce qui n'est pas un carré parfait.
Double arrondi. Sur certains systèmes (en particulier x86), les expressions à virgule flottante sont évaluées avec une précision supérieure à leurs types nominaux. Cela signifie que lorsque l'un des types d'arrondi ci-dessus se produit, vous obtiendrez deux étapes d'arrondi, d'abord un arrondi du résultat au type plus précis, puis un arrondi au type final. Par exemple, considérez ce qui se passe en décimal si vous arrondissez 1,49 à un entier (1), par rapport à ce qui se passe si vous l'arrondissez d'abord à une décimale (1,5) puis arrondissez ce résultat à un entier (2). C'est en fait l'un des domaines les plus désagréables à traiter en virgule flottante, car le comportement du compilateur (en particulier pour les compilateurs non conformes, comme GCC) est imprévisible.
Fonctions transcendantes ( trig
, exp
, log
, etc.) ne sont pas spécifiés pour avoir des résultats correctement arrondis; le résultat est juste spécifié pour être correct dans une unité à la dernière place de précision (généralement appelé 1ulp ).
Lorsque vous écrivez du code à virgule flottante, vous devez garder à l'esprit ce que vous faites avec les nombres qui pourraient rendre les résultats inexacts et effectuer des comparaisons en conséquence. Souvent, il sera judicieux de comparer avec un "epsilon", mais cet epsilon doit être basé sur l' ampleur des nombres que vous comparez , et non sur une constante absolue. (Dans les cas où un epsilon constant absolu fonctionnerait, cela indique fortement que le point fixe, et non le point flottant, est le bon outil pour le travail!)
Edit: En particulier, une vérification epsilon relative à la magnitude devrait ressembler à:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
D'où FLT_EPSILON
vient la constante float.h
(remplacez-la par DBL_EPSILON
pour double
s ou LDBL_EPSILON
pour long double
s) et K
est une constante que vous choisissez de telle sorte que l'erreur cumulée de vos calculs soit définitivement limitée par des K
unités en dernier lieu (et si vous n'êtes pas sûr d'avoir obtenu l'erreur calcul lié à droite, faire K
quelques fois plus grand que ce que vos calculs disent qu'il devrait être).
Enfin, notez que si vous utilisez ceci, des précautions particulières peuvent être nécessaires près de zéro, car cela FLT_EPSILON
n'a pas de sens pour les dénormals. Une solution rapide serait de le faire:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
et également remplacer DBL_MIN
si vous utilisez des doubles.
fabs(x+y)
est problématique six
ety
(peut) avoir un signe différent. Pourtant, une bonne réponse contre la vague de comparaisons fret-culte.x
ety
ont un signe différent, ce n'est pas un problème. Le côté droit sera « trop petit », mais depuisx
ety
ont signe différent, ils ne doivent pas être égaux de toute façon. (À moins qu'ils ne soient si petits qu'ils soient dénormaux, mais le deuxième cas le rattrape)Étant donné que 0 est exactement représentable comme un nombre à virgule flottante IEEE754 (ou en utilisant toute autre implémentation de nombres fp avec laquelle j'ai déjà travaillé), la comparaison avec 0 est probablement sûre. Vous pourriez toutefois être mordu si votre programme calcule une valeur (telle que
theView.frame.origin.x
) que vous avez des raisons de croire qu'elle devrait être 0 mais que votre calcul ne peut garantir à 0.Pour clarifier un peu, un calcul tel que:
va (à moins que votre langue ou votre système ne soit cassé) créer une valeur telle que (areal == 0.0) renvoie vrai mais un autre calcul tel que
Peut-être pas.
Si vous pouvez vous assurer que vos calculs produisent des valeurs qui sont 0 (et pas seulement qu'ils produisent des valeurs qui devraient être 0), alors vous pouvez continuer et comparer les valeurs fp à 0. Si vous ne pouvez pas vous assurer au degré requis , il vaut mieux s'en tenir à l'approche habituelle de «l'égalité tolérée».
Dans le pire des cas, la comparaison négligente des valeurs fp peut être extrêmement dangereuse: pensez à l'avionique, au guidage des armes, aux opérations de la centrale électrique, à la navigation dans les véhicules, à presque toutes les applications dans lesquelles le calcul rencontre le monde réel.
Pour Angry Birds, pas si dangereux.
la source
1.30 - 2*(0.65)
est un exemple parfait d'une expression qui est évidemment évaluée à 0,0 si votre compilateur implémente IEEE 754, car les doubles représentés comme0.65
et1.30
ont les mêmes significations, et la multiplication par deux est évidemment exacte.Je veux donner une réponse un peu différente des autres. Ils sont parfaits pour répondre à votre question comme indiqué, mais probablement pas pour ce que vous devez savoir ou quel est votre véritable problème.
La virgule flottante dans les graphiques est très bien! Mais il n'est presque pas nécessaire de comparer directement les flotteurs. Pourquoi auriez-vous besoin de faire ça? Les graphiques utilisent des flottants pour définir les intervalles. Et comparer si un flotteur est dans un intervalle également défini par des flotteurs est toujours bien défini et doit simplement être cohérent, pas exact ou précis! Tant qu'un pixel (qui est également un intervalle!) Peut être attribué, c'est tous les besoins graphiques.
Donc, si vous voulez tester si votre point est en dehors d'une plage de [0..largeur], c'est très bien. Assurez-vous simplement de définir l'inclusion de manière cohérente. Par exemple, définissez toujours inside is (x> = 0 && x <width). Il en va de même pour les tests d'intersection ou de hit.
Cependant, si vous abusez d'une coordonnée graphique comme une sorte d'indicateur, comme par exemple pour voir si une fenêtre est ancrée ou non, vous ne devriez pas le faire. Utilisez plutôt un indicateur booléen distinct de la couche de présentation graphique.
la source
Comparé à zéro peut être une opération sûre, tant que le zéro n'est pas une valeur calculée (comme indiqué dans une réponse ci-dessus). La raison en est que zéro est un nombre parfaitement représentable en virgule flottante.
En parlant de valeurs parfaitement représentables, vous obtenez 24 bits de plage dans une notion de puissance de deux (simple précision). Donc, 1, 2, 4 sont parfaitement représentables, tout comme 0,5, 0,25 et 0,125. Tant que tous vos bits importants sont en 24 bits, vous êtes en or. 10.625 peut donc être représenté avec précision.
C'est très bien, mais va rapidement s'effondrer sous la pression. Deux scénarios viennent à l'esprit: 1) Lorsqu'un calcul est impliqué. Ne faites pas confiance à sqrt (3) * sqrt (3) == 3. Il n'en sera tout simplement pas ainsi. Et ce ne sera probablement pas dans un epsilon, comme le suggèrent certaines des autres réponses. 2) Lorsqu'une non-puissance de 2 (NPOT) est impliquée. Cela peut donc sembler étrange, mais 0,1 est une série infinie en binaire et donc tout calcul impliquant un nombre comme celui-ci sera imprécis dès le départ.
(Oh et la question d'origine mentionnait des comparaisons à zéro. N'oubliez pas que -0,0 est également une valeur à virgule flottante parfaitement valide.)
la source
[La «bonne réponse» passe sous silence la sélection
K
. La sélectionK
finit par être aussi ponctuelle que la sélection,VISIBLE_SHIFT
mais la sélectionK
est moins évidente car, contrairement àVISIBLE_SHIFT
elle, elle n'est basée sur aucune propriété d'affichage. Choisissez donc votre poison - sélectionnezK
ou sélectionnezVISIBLE_SHIFT
. Cette réponse préconise la sélectionVISIBLE_SHIFT
, puis démontre la difficulté de sélectionnerK
]Précisément en raison d'erreurs rondes, vous ne devez pas utiliser la comparaison des valeurs «exactes» pour les opérations logiques. Dans votre cas spécifique d'une position sur un affichage visuel, il peut ne pas avoir d'importance si la position est de 0,0 ou 0,000000000003 - la différence est invisible à l'œil. Donc, votre logique devrait être quelque chose comme:
Cependant, à la fin, «invisible à l'œil» dépendra de vos propriétés d'affichage. Si vous pouvez limiter l'affichage (vous devriez pouvoir); puis choisissez
VISIBLE_SHIFT
d'être une fraction de cette limite supérieure.Maintenant, la «bonne réponse» repose sur
K
nous allons donc explorer la sélectionK
. La «bonne réponse» ci-dessus dit:Nous avons donc besoin
K
. Si obtenirK
est plus difficile, moins intuitif que de sélectionner my,VISIBLE_SHIFT
vous déciderez de ce qui vous convient . Pour trouver,K
nous allons écrire un programme de test qui examine un tas deK
valeurs afin que nous puissions voir comment il se comporte. Doit être évident comment choisirK
, si la «bonne réponse» est utilisable. Non?Nous allons utiliser, comme «bonne réponse» les détails:
Essayons simplement toutes les valeurs de K:
Ah, donc K devrait être 1e16 ou plus si je veux que 1e-13 soit «zéro».
Donc, je dirais que vous avez deux options:
K
.la source
K
qui sont difficiles et non intuitives à sélectionner.La bonne question: comment comparer les points dans Cocoa Touch?
La bonne réponse: CGPointEqualToPoint ().
Une question différente: deux valeurs calculées sont-elles identiques?
La réponse affichée ici: ils ne le sont pas.
Comment vérifier s'ils sont proches? Si vous souhaitez vérifier s'ils sont proches, n'utilisez pas CGPointEqualToPoint (). Mais ne vérifiez pas s'ils sont proches. Faites quelque chose qui a du sens dans le monde réel, comme vérifier pour voir si un point est au-delà d'une ligne ou si un point est à l'intérieur d'une sphère.
la source
La dernière fois que j'ai vérifié la norme C, il n'était pas nécessaire que les opérations en virgule flottante sur les doubles (64 bits au total, mantisse 53 bits) soient plus précises que cette précision. Cependant, certains matériels peuvent effectuer les opérations dans des registres d'une plus grande précision, et l'exigence a été interprétée comme signifiant qu'il n'est pas nécessaire d'effacer les bits d'ordre inférieur (au-delà de la précision des nombres chargés dans les registres). Ainsi, vous pouvez obtenir des résultats inattendus de comparaisons comme celle-ci en fonction de ce qui reste dans les registres de celui qui y a dormi en dernier.
Cela dit, et malgré mes efforts pour l'effacer chaque fois que je le vois, la tenue où je travaille a beaucoup de code C qui est compilé en utilisant gcc et exécuté sur linux, et nous n'avons remarqué aucun de ces résultats inattendus depuis très longtemps. . Je ne sais pas si c'est parce que gcc efface les bits de poids faible pour nous, les registres 80 bits ne sont pas utilisés pour ces opérations sur les ordinateurs modernes, la norme a été modifiée ou quoi. J'aimerais savoir si quelqu'un peut citer un chapitre et un verset.
la source
Vous pouvez utiliser un tel code pour comparer float avec zéro:
Cela se comparera avec une précision de 0,1, suffisante pour CGFloat dans ce cas.
la source
int
sans assurertheView.frame.origin.x
est dans / près de cette plage deint
conduits à un comportement indéfini (UB) - ou dans ce cas, 1 / 100e de la plage deint
.}
la source
J'utilise la fonction de comparaison suivante pour comparer un certain nombre de décimales:
la source
Je dirais que la bonne chose est de déclarer chaque nombre comme un objet, puis de définir trois choses dans cet objet: 1) un opérateur d'égalité. 2) une méthode setAcceptableDifference. 3) la valeur elle-même. L'opérateur d'égalité renvoie vrai si la différence absolue de deux valeurs est inférieure à la valeur définie comme acceptable.
Vous pouvez sous-classer l'objet en fonction du problème. Par exemple, des barres rondes en métal entre 1 et 2 pouces peuvent être considérées comme de diamètre égal si leurs diamètres diffèrent de moins de 0,0001 pouces. Vous appelez donc setAcceptableDifference avec le paramètre 0.0001, puis utilisez l'opérateur d'égalité en toute confiance.
la source