404 votes

Comment est-il dangereux pour comparer les valeurs à virgule flottante?

Je sais UIKit utilise CGFloat en raison de la résolution indépendante du système de coordonnées.

Mais chaque fois que je veux vérifier si, par exemple, frame.origin.x est 0 il me fait me sentir malade:

if (theView.frame.origin.x == 0) {
    // do important operation
}

N'est-ce pas CGFloat vulnérables à des faux positifs lors de la comparaison avec ==, <=, >=, <, >? C'est un point flottant et ils ont unprecision problèmes: 0.0000000000041 par exemple.

Objective-C de la manipulation de ce en interne lors de la comparaison ou peut-il arriver qu'un origin.x qui se lit comme zéro ne pas comparer 0 comme vrai?

489voto

R.. Points 93718

Tout d'abord, les valeurs à virgule flottante ne sont pas "aléatoire" dans leur comportement. Exact, la comparaison et n'a de sens que dans beaucoup d'usages. Mais si vous allez utiliser de virgule flottante, vous devez être conscient de la façon dont il fonctionne. Axées sur la supposant à virgule flottante fonctionne comme les nombres réels, vous obtiendrez un code qui rompt rapidement. Axées sur la supposant à virgule flottante résultats ont de grandes aléatoire fuzz qui y sont associés (comme la plupart des réponses suggèrent ici) vous obtiendrez le code qui s'affiche à travailler au début, mais finit par avoir de grande ampleur des erreurs et brisé cas du coin.

Tout d'abord, si vous souhaitez programmer avec virgule flottante, vous devriez lire ceci:

Ce Que Tout Informaticien Devez Savoir À Propos De L'Arithmétique À Virgule Flottante

Oui, lire tout ça. Si c'est un fardeau trop lourd, vous devez utiliser des entiers/point fixe pour vos calculs jusqu'à ce que vous ayez le temps de le lire. :-)

Maintenant, avec cela dit, le plus gros soucis avec l'exacte virgule flottante comparaisons descendre à:

  1. Le fait que beaucoup de valeurs que vous pouvez écrire dans la source, ou de lire scanf ou strtod, n'existent pas en tant que valeurs à virgule flottante et obtenir silencieusement converti à la plus proche approximation. C'est ce que demon9733 réponse était en train de parler.

  2. Le fait que de nombreux résultats sont arrondis à cause de ne pas avoir assez de précision pour représenter le résultat réel. Un exemple simple où vous pouvez le voir c'est l'ajout d' x = 0x1fffffe et y = 1 flotteurs. Ici, x a 24 bits de précision dans la mantisse (ok) et y a juste 1 peu, mais quand vous les ajoutez, de leurs morceaux ne sont pas dans le chevauchement des lieux, et le résultat aurait besoin de 25 bits de précision. Au lieu de cela, il devient arrondi (à 0x2000000 " dans le mode d'arrondi par défaut).

  3. Le fait que de nombreux résultats sont arrondis à cause de besoin d'un nombre infini de lieux pour la valeur correcte. Cela comprend à la fois des résultats rationnels comme 1/3 (que vous êtes familier avec de virgule où il prend une infinité de lieux) mais aussi 1/10 (qui prend également en une infinité de lieux en binaire, depuis le 5 n'est pas une puissance de 2), ainsi que l'irrationnel résultats comme la racine carrée de tout ce qui n'est pas un carré parfait.

  4. Double arrondi. Sur certains systèmes (en particulier x86), floating point les expressions sont évaluées dans une précision plus élevée que la valeur nominale des types. Cela signifie que lorsque l'un des types ci-dessus de l'arrondi se produit, vous bénéficiez de deux arrondissement étapes, d'abord une approximation du résultat d'une plus grande précision du type, puis un arrondi pour le type final. Par exemple, considérons ce qui se passe en décimal si vous tour de 1,49 à un nombre entier (1), par rapport à ce qui se passe si vous arrondir à la décimale près (1.5) ronde alors que la résultat à un nombre entier (2). C'est en fait l'une des pires zones à traiter en virgule flottante, depuis le comportement du compilateur (surtout pour le buggy, la non-conforme compilateurs comme GCC) est imprévisible.

  5. Les fonctions transcendantes (trig, exp, log, etc.) ne sont pas spécifiés correctement arrondie résultats; le résultat est tout simplement de corriger dans un délai d'une unité à la dernière place de la précision (généralement appelé 1ulp).

Lorsque vous écrivez du code de nombres flottants, vous devez garder à l'esprit ce que vous faites avec les numéros qui pourraient entraîner que les résultats soient inexactes, et de faire des comparaisons en conséquence. Souvent, il va donner un sens à comparer avec un "epsilon", mais que epsilon devraient être basées sur l' ampleur de l'un des numéros de la comparaison, non pas absolue constante. (Dans les cas où une absolue constante epsilon, c'est très indicatifs ce point fixe, pas de virgule flottante, c'est le bon outil pour le travail!)

Edit: En particulier, l'ampleur relative de epsilon devrait ressembler à quelque chose comme:

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))

FLT_EPSILON est la constante de float.h (remplacer par DBL_EPSILON pour les doubles ou LDBL_EPSILON pour les long double) et K est une constante vous de choisir de tels que l'intégration de l'erreur de calcul est définitivement délimitée par K les unités dans la dernière place (et si vous n'êtes pas sûr que vous avez l'erreur liée calcul de la droite, font K quelques fois plus grand que ce que vos calculs dire qu'il devrait être).

Enfin, notez que si vous utilisez cette, certains soins spéciaux peuvent être nécessaires proche de zéro, depuis FLT_EPSILON n'a pas de sens pour denormals. Une solution rapide serait de faire:

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)

et de même substitut DBL_MIN si l'utilisation de doubles.

39voto

Puisque 0 est exactement représentable comme un IEEE754 nombre à virgule flottante (ou en utilisant de toute autre mise en œuvre de la f-p nombre avec qui j'ai travaillé), de la comparaison avec 0 est probablement plus sûr. Vous risquez de vous faire piquer, cependant, si votre programme calcule une valeur (par exemple theView.frame.origin.x) qui vous ont des raisons de croire que devrait être 0, mais dont le calcul ne peut pas garantir à 0.

Pour clarifier un peu, un calcul tels que :

areal = 0.0

va (à moins que votre langue ou le système est cassé) créez une valeur telle que (surfacique==0.0) renvoie vrai, mais un autre calcul telles que

areal = 1.386 - 2.1*(0.66)

ne peut pas.

Si vous pouvez vous assurer que vos calculs produire des valeurs qui sont à 0 (et pas seulement qu'ils produisent des valeurs qui devraient être égal à 0), alors vous pouvez aller de l'avant et de comparer les f-p les valeurs à 0. Si vous ne pouvez pas vous assurer à un degré, mieux s'en tenir à l'approche habituelle de la "tolérés de l'égalité".

Dans le pire des cas, l'abus de comparaison de f-p valeurs peuvent être extrêmement dangereux: pensez à l'avionique, les armes de l'orientation, de la puissance-l'exploitation de l'usine, de navigation de véhicule, presque toute application dans laquelle le calcul se réunit le monde réel.

Pour les Oiseaux en Colère, pas si dangereux.

24voto

starmole Points 2160

Je veux donner un peu une réponse différente de celle des autres. Ils sont parfaits pour répondre à votre question comme indiqué, mais probablement pas pour ce que vous devez savoir ou ce que votre véritable problème.

Virgule flottante dans le graphisme est très bien! Mais il n'y a presque pas besoin de toujours comparer les flotteurs directement. Pourquoi auriez-vous besoin de faire cela? Graphique utilise des flotteurs de définir des intervalles. Et la comparaison si un flotteur à l'intérieur d'un intervalle défini par des flotteurs est toujours bien définis et se doit d'être cohérent, ne sont pas exacts et précis! Aussi longtemps qu'un pixel (qui est aussi un intervalle!) peut être affecté que de tous les graphiques.

Donc, si vous voulez tester si le point est en dehors de [0..largeur[ cette gamme est tout simplement parfait. Assurez-vous de définir l'inclusion de manière cohérente. Par exemple, définissez toujours à l'intérieur est (x>=0 && x < largeur). Il en va de même pour l'intersection ou de frapper des tests.

Toutefois, si vous abusez un graphique à coordonner, dans une sorte de drapeau, comme par exemple pour voir si une fenêtre est connecté ou pas, vous ne devriez pas le faire. L'utilisation d'un indicateur booléen qui est séparé de la présentation d'images de couche à la place.

14voto

JHumphrey Points 174

La comparaison à zéro peut être un fonctionnement sûr, tant que le zéro n'est pas une valeur calculée (comme indiqué dans la réponse ci-dessus). La raison pour cela est que le zéro est parfaitement représentable nombre en virgule flottante.

Parlant parfaitement représentable valeurs, vous obtenez 24 bits de gamme dans une puissance de deux notion (simple précision). Donc 1, 2, 4 sont parfaitement représentable, comme le sont .5, .25, et .125. Aussi longtemps que tous vos choses importantes sont en 24 bits, vous êtes de l'or. Donc 10.625 peut être repsented précisément.

C'est super, mais va rapidement tomber en morceaux sous la pression. Deux scénarios viennent à l'esprit: 1) Lorsqu'un calcul est impliqué. Ne croyez pas qu'sqrt(3)*sqrt(3) == 3. Il ne peut pas être de cette façon. Et il ne sera probablement pas dans un epsilon, comme certains des autres réponses suggèrent. 2) Lorsqu'une non-puissance de 2 (NPOT) est impliqué. Si cela peut sembler étrange, mais de 0,1 est une série infinie en binaire et par conséquent, le calcul impliquant un certain nombre de ce genre de précision, depuis le début.

(Oh, et la question d'origine mentionné comparaisons à zéro. N'oubliez pas que -0.0 est parfaitement valide valeur à virgule flottante.)

8voto

Vitor Almeida Points 88

Il y a une série de messages de la part de Bruce Dawson posté sur AltDevBlogADay expliquer beaucoup de problèmes à l'aide de nombres en virgule flottante. Des informations très utiles, il y a quelques précieux conseils qui peuvent aider à l'aide d'un ou d'essayer de mieux comprendre comment les nombres flottants œuvres.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X