1
votes

Comparez deux variables flottantes en C ++

J'ai lu plusieurs articles concernant la comparaison des variables en virgule flottante, mais je n'ai pas réussi à comprendre et à obtenir les connaissances requises à partir de ces articles. Alors, ici, je poste cette question.

Quelle est la bonne façon de comparer deux variables flottantes? Voici l'extrait de code:

#define EPSILON_VALUE 0.0000000000000001

bool cmpf(float A, float B)
{
  return (fabs(A - B) < EPSILON_VALUE);
}

int main()
{
  float a = 1.012345679, b = 1.012345678;

  if(cmpf(a, b))cout<<"same"<<endl;
  else cout<<"different"<<endl;

  return 0;
}

La sortie: same bien que les deux variables flottantes aient des valeurs différentes.


19 commentaires

tout votre code sert uniquement à vous assurer que deux flottants qui ne sont pas exactement les mêmes sont considérés comme identiques, donc je ne comprends pas à quoi vous attendiez-vous d'autre ... Quelle est la question? Celui que vous avez demandé vous a déjà répondu


@ user463035818 car vous pouvez voir que les deux variables flottantes (a et b) contiennent des valeurs différentes, mais la sortie imprimée par le code est «identique» au lieu de «différente».


eh bien, c'est tout le but de cette fonction cmpf . peut-être que votre malentendu est le suivant: quand comparez-vous des flottants qui sont exactement les mêmes, vous obtiendrez true en les comparant via == , il n'arrive presque jamais que deux virgules flottantes les valeurs résultant des calculs sont exactement les mêmes. Ensuite, souvent, vous devez toujours avoir un moyen de dire "ok, ils ne sont pas exactement les mêmes, mais avec une erreur acceptable, je peux les considérer comme les mêmes"


Cela dépend entièrement du contexte. Parfois, une comparaison avec une tolérance additive est appropriée, parfois une tolérance multiplicative est préférable. Parfois, == fera le travail. Vous devez étudier votre algorithme et comment les erreurs, le cas échéant, vont s'accumuler. Le duplicata lié est un bon point de départ.


@ user463035818: Un autre mythe que j'ai peur surtout sous IEEE754.


Vous avez écrit une fonction qui renvoie true , puis vous vous demandez pourquoi elle renvoie true au lieu de false .


@ user463035818: 1 - 1.0 / 3 - 1.0 / 3 - 1.0 / 3 est peut-être meilleur.


@ L.F. Où avez-vous vu que la fonction retourne toujours vrai?


@PankajKumarThapa Nulle part je n'ai vu que la fonction retourne toujours vrai ;-) Je veux dire que vous avez écrit une fonction cmpf pour que cmpf (a, b) renvoie true , puis vous demandez pourquoi il renvoie true au lieu de false .


@ L.F. si vous voyez les valeurs des variables flottantes, vous voyez des valeurs différentes. Avec ces informations, «différent» devrait être imprimé et non «identique».


@PankajKumarThapa Si vous ne comprenez toujours pas, vous avez écrit une fonction cmpf pour que cmpf (a, b) renvoie true lorsque a sont b sont proches mais des valeurs différentes, puis vous demandez pourquoi il renvoie true au lieu de false . Quel est le prochain point que je dois clarifier? ;-)


@ L.F. bien. Je vois ce que tu veux dire. Ma question est de savoir comment puis-je obtenir «différent» avec les mêmes valeurs d'entrée (a = 1.012345679, b = 1.012345678)?


@PankajKumarThapa Mais vous venez de dire que a et b sont des valeurs différentes vous-même dans votre dernier commentaire ;-)


@ L.F. si je donne a = 1.01234567, b = 1.01234564, alors je deviens différent. Veuillez vérifier qu'il y a 8 chiffres après la virgule. Si j'augmente un chiffre de plus après la décimale, alors la comparaison entière va pour un tirage au sort.


@ L.F. ne soyez pas confus. par même je voulais dire les mêmes données d'entrée avec des valeurs différentes dans les variables. :RÉ


@ πάνταῥεῖ: Cette question n'est pas un doublon de cette question . Veuillez ne pas marquer les questions comme des doublons.


Vous utilisez le type float. Il n'enregistre que 7 chiffres. Donc, a et b sont les mêmes. Vous pouvez le voir si vous cout << a-b . Vous pouvez voir plus de différence si vous définissez un b et un cmpf avec var comme double.


La chose la plus importante à garder à l'esprit lorsque vous pensez aux opérations en virgule flottante est que les nombres à virgule flottante ne sont pas comme des nombres réels , et toute l'intuition sur les nombres décimaux que vous avez développée au cours de votre vie vous induira en erreur.


FWIW, les valeurs exactes 1.012345679 et 1.012345678 , lorsqu'elles sont stockées sous forme de flottants, sont toutes deux stockées en tant que valeur approximative 1.01234567165374755859375 , donc lorsqu'elles sont stockées en tant que flottants, ce sont exactement les mêmes valeurs . Ils devraient renvoyer "same" , même sans EPSILON_VALUE . Notez que votre epsilon est en dessous de la précision qu'un flotteur peut donner de toute façon. De plus, lorsque vous soustrayez des valeurs presque égales, vous devez vous attendre à une annulation catastrophique , ce qui peut également poser problème.


3 Réponses :


6
votes

Il n'y a pas de solution générale pour comparer des nombres à virgule flottante contenant des erreurs d'opérations précédentes. Le code à utiliser est spécifique à l'application. Donc, pour obtenir une réponse correcte, vous devez décrire votre situation plus précisément.

Le problème sous-jacent est qu'il est en général impossible d'effectuer un calcul correct en utilisant des données incorrectes. Si vous voulez calculer une fonction de deux valeurs mathématiques exactes x et y mais que les seules données dont vous disposez sont des valeurs inexactement calculées x et < code> y , il est généralement impossible de calculer le résultat exactement correct. Par exemple, supposons que vous vouliez savoir quelle est la somme, x + y , mais vous savez seulement que x vaut 3 et y vaut 4, mais vous ne savez pas ce que sont exactement les x et y . Ensuite, vous ne pouvez pas calculer x + y .

Si vous savez que x et y sont approximativement x et y , vous pouvez alors calculer une approximation de x + y en ajoutant x et y . Cela fonctionne lorsque la fonction en cours de calcul ( + dans cet exemple) a une dérivée raisonnable: modifier légèrement les entrées d'une fonction avec une dérivée raisonnable modifie légèrement ses sorties. Cela échoue lorsque la fonction que vous souhaitez calculer a une discontinuité ou une grande dérivée. Par exemple, si vous souhaitez calculer la racine carrée de x (dans le domaine réel) en utilisant une approximation x mais x peut être négatif en raison aux erreurs d'arrondi précédentes, alors le calcul de sqrt (x) peut produire une exception. De même, comparer pour l'inégalité ou l'ordre est une fonction discontinue: un léger changement dans les entrées peut changer complètement la réponse (de faux à vrai ou vice-versa).

Le mauvais conseil commun est de comparer avec une «tolérance». Cette méthode échange les faux négatifs (rejets incorrects de nombres qui satisferaient la comparaison si les valeurs mathématiques exactes étaient comparées) contre de faux positifs (acceptation incorrecte de nombres qui ne satisferaient pas la comparaison).

Le fait qu'un candidat puisse ou non tolérer une fausse acceptation dépend de l'application. Par conséquent, il n'y a pas de solution générale.

Le niveau de tolérance à définir, et même la nature selon laquelle il est calculé, dépendent des données, des erreurs et des calculs précédents. Ainsi, même lorsqu'il est acceptable de comparer avec une tolérance, la quantité de tolérance à utiliser et la manière de la calculer dépendent de l'application. Il n'y a pas de solution générale.


21 commentaires

Le plus précis est de ne pas utiliser du tout de nombres flottants. Utilisez des entiers et des fractions.


@MichaelChourdakis: Comment calculer une racine carrée ou un cosinus avec des entiers ou des fractions?


Vous ne le faites pas. Si vos calculs incluent des nombres réels, vous avez quand même perdu de la précision. L'idée est d'éviter les variables à virgule flottante lorsque vous pouvez, par exemple dans l'un de mes projets, la durée de la musique est exprimée avec une fraction, pas un nombre à virgule flottante. Btw le commentaire était pour la question mais j'ai mal cliqué. Votre réponse est correcte.


@EricPostpischil - il existe des techniques pour calculer la "racine carrée entière" (pour une valeur intégrale positive x , c'est la plus grande valeur intégrale positive y telle que y * y <= x ) en utilisant uniquement des opérations intégrales - pas de virgule flottante. Les fractions (aka valeurs rationnelles) peuvent être représentées à l'aide d'une paire d'entiers et d'opérations sur la paire. Cela dit, je ne suis pas d'accord avec les conseils de ne pas utiliser du tout la virgule flottante - comme dans le développement de logiciels, la virgule flottante a ses utilisations.


@MichaelChourdakis: «Vous n’êtes pas» n’est pas une solution acceptable pour de nombreux algorithmes. Toute modélisation VR, AR ou physique nécessite des racines carrées et des fonctions trigonométriques. Beaucoup de travaux scientifiques et mathématiques ont besoin de ces fonctions et d'autres fonctions mathématiques qui ne sont pas suffisamment servies par l'arithmétique entière ou rationnelle. Ignorer la virgule flottante n'est pas pratique.


@Eric: "Comment calculer une racine carrée ou un cosinus avec des entiers ou des fractions?" Hein? C'est possible. C'est lent, mais mon BigDecimal ainsi que mon BigRational peuvent le faire. Et comment? De la même manière (ou similaire) pour FP, mais pas dans le matériel.


@RudyVelthuis: Avec l'arithmétique entière, quelle est la racine carrée de deux? En quoi est-ce plus précis qu'avec la virgule flottante? Avec l'arithmétique rationnelle, quel est le cosinus de 4/5?


@Eric: Je n'ai pas dit arithmétique entière, j'ai dit BigRational ou Bigdecimal. Avec une précision de 128, BigDecimal produira: 1,4142135623730950488016887242096980785696718753769480731766‌ 797379 . Code source pour BigDecimal.Sqrt: github.com/rvelthuis / DelphiBigNumbers / blob / master / Source /… , un simple algorithme de Newton – Raphson. BigRational (pas entièrement fonctionnel pour le moment) utilise un algorithme de fraction continue. Tous ces éléments utilisent (fractions de) entiers.


@RudyVelthuis: Souvenez-vous du contexte ici - le commentaire de Michael Chourdakis pour utiliser des entiers ou des fractions. L'utilisation de BigDecimal est une arithmétique à virgule flottante, qu'elle soit implémentée ou non avec des entiers. Le problème est que l'arithmétique entière ou rationnelle est inadéquate pour de nombreuses tâches pour lesquelles la virgule flottante convient.


Avec des entiers: BigDecimal se compose d'un BigInteger et d'une échelle. Le BigInteger est mis à l'échelle si nécessaire (par puissances de 10), la racine carrée entière est calculée et le résultat est mis à l'échelle (arrondi le cas échéant).


@RudyVelthuis: Vous voyez cette «échelle» dont vous parlez en BigDecimal? C'est le «flottant» de la virgule flottante. Ce n'est pas de l'arithmétique entière. C'est l'arithmétique à virgule flottante.


@Eric: il est possible d'utiliser des fractions (suite) (qui sont des fractions d'entiers - généralement petits -). Des nombres entiers sont utilisés le cas échéant. Ce n'est pas rapide, cependant.


@Eric: en gros, tout est arithmétique entier. Mais vous avez également exclu les fractions (d'entiers). C'est possible avec eux.


FWIW, BigDecimals pourrait fonctionner avec une échelle fixe. Mais ils seraient assez inutiles. L'algorithme de racine carrée utilisé est l'algorithme de racine carrée entière tel qu'implémenté dans BigInteger.


@RudyVelthuis: Qu'est-ce possible? Souvenez-vous du contexte - le commentaire de Michael Chourdakis d'utiliser des entiers ou des fractions, pour être le «plus précis». Mais une approximation avec des fractions continues n'est pas intrinsèquement plus précise qu'une virgule flottante. Et si ce n'est pas plus rapide ou plus efficace, à quoi ça sert? Mes questions sur l'arithmétique des nombres entiers et rationnels illustrent qu'il existe certaines limites mathématiques à toute arithmétique à taille fixe, et que les entiers et les fractions ne conviennent ni à divers travaux ni aux «plus précis».


@Eric: Les fractions peuvent être beaucoup plus précises que IEEE-754 FP. Et les fractions continues sont très précises, en fait, surtout lorsqu'elles sont continuellement utilisées comme fractions, et non converties en FP.


@RudyVelthuis: Le contenu informationnel de 32 bits ne peut pas dépasser 32 bits.


@Eric: duh! Mais plusieurs entiers 32 bits peuvent contenir beaucoup plus d'informations. Qui a limité cela à 32 bits?


Continuons cette discussion dans le chat .


@Eric: Je vais me coucher. Mais merci pour l'invitation.


Donc, avec toutes les discussions ci-dessus, je peux conclure qu'il n'y a pas de moyen simple de résoudre ce problème. Nous devons faire face à de tels problèmes lorsque nous travaillons avec float, double. Grrrrr !!!!



-3
votes

C'est parce que les flottants ont une précision de 7 chiffres. Si vous voulez une meilleure précision, vous devez utiliser un double ou un long double.


4 commentaires

Lancer aveuglément plus de bits sur le problème n'est pas une solution


Pourquoi devrais-je utiliser double quand ce n'est pas nécessaire. Je peux aussi obtenir l'exécution en utilisant float, mais en raison de la précision, j'ai du mal. Il en sera de même avec le double lors du stockage plus grand. Y a-t-il une solution simple à cela?


Votre problème vient du fait que vous avez besoin de types de précision plus élevés. Il est logique d'utiliser un type de données qui utilise plus de bits. Voulez-vous expliquer pourquoi vous êtes réticent à utiliser un type différent?


"parce que les flottants ont une précision de 7 chiffres" -> pas toujours. Exemple de compteur d'exemple: 8589973, 8589974 et dissimulé à 8589974.0f



1
votes

La sortie: identique bien que les deux variables flottantes aient des valeurs différentes.

"Les variables flottantes contiennent des valeurs différentes." est sans fondement.

same a été imprimé car les valeurs a, b sont les mêmes même si les constantes d'initialisation diffèrent.


float typique est de 32 bits et peut représenter environ 2 32 valeurs différentes telles que 1.0, 1024.0, 0.5, 0.125. Ces valeurs sont toutes de la forme: +/- some_integer * 2 some_integer

1.012345679 et 1.012345678 sont pas dans cet ensemble de float . @Rudy Velthuis .

flaot b = 1.012345678;
// 'b' has the value of 1.01234567165374755859375

0 commentaires