La comparaison relationnelle entre int et float est-elle directement possible en C?

J’utilise Visual Studio 6 avec un ancien code temporel écrit en c. J’ai trouvé un problème où le code ressemble à ceci.

int x = 3; float y = 3.0; if(x == y){ do some crazy stuff } 

est-ce une comparaison valide? est-il possible au moment de l’exécution que l’allocation pour le float soit 3.0000001 et que cela échouerait?

Eh bien, je suppose que vous ne serez pas trop surpris d’apprendre que comparer des flottants pour l’égalité est alors une erreur de la part du recrue.

Le problème est que beaucoup d’incréments inférieurs à un entier ne peuvent pas être représentés exactement en virgule flottante IEEE. Donc, si vous arrivez au float en essayant de “l’indexer” jusqu’à la valeur de 3.0 (par exemple, par incréments de 0.1), il est fort possible que votre comparaison d’égalité ne puisse jamais être vraie.

C’est également une mauvaise idée, du sharepoint vue de la force de frappe. Vous devriez soit convertir le float en int, vérifier si votre int est “assez proche” (par exemple <3.1 et> 2.9 ou plus), ou mieux encore si vous essayez de faire en sorte que ce float fasse double emploi pour un compteur. , évite toute l’idée.

C’est généralement (c’est toujours) une mauvaise idée. Comme vous le soupçonniez, la comparaison de 3 à 3.0000001 échouera effectivement.

Ce que la plupart des gens font, si une comparaison int float est vraiment nécessaire, c’est de choisir un seuil de tolérance et de l’accepter, comme suit:

 int x = 3; float y = 3.0; // some code here float difference = (float) x - y; float tolerableDifference = 0.001; if ((-tolerableDifference <= difference) && (difference <= tolerableDifference)) { // more code } 

Je vais inverser un peu la tendance ici. En ce qui concerne la première question sur la validité de la comparaison, la réponse est oui. C’est parfaitement valide. Si vous voulez savoir si une valeur à virgule flottante est exactement égale à 3, la comparaison à un entier est correcte. L’entier est implicitement converti en une valeur à virgule flottante pour la comparaison. En fait, le code suivant (au moins avec le compilateur que j’ai utilisé) produisait des instructions d’assemblage identiques.

 if ( 3 == f ) printf( "equal\n" ); 

et

 if ( 3.0 == f ) printf( "equal\n" ); 

Cela dépend donc de la logique et du but recherché. Il n’y a rien de fondamentalement faux avec la syntaxe.

Personne d’autre ne l’a encore mentionné, et je n’y ai pas fait référence depuis un moment. Voici donc le document classique sur les contours effrayants de la représentation en virgule flottante et de l’arithmétique: Ce que tout informaticien devrait savoir à propos de la virgule flottante .

Le document est une lecture difficile pour un non-mathématicien, mais les points clés sont bien énoncés entre les tâches lourdes des maths les sauvegardant.

Pour cette discussion, les points soulevés par les autres réponses ici sont tous valables. L’arithmétique en virgule flottante est inexacte et, par conséquent, les comparaisons pour une égalité exacte sont généralement une mauvaise idée. Par conséquent, epsilon est votre ami.

Une exception à la règle de comparaison exacte est un test pour exactement zéro. Il est parfaitement légal et souvent judicieux de tester exactement zéro avant une division ou un logarithme puisque la réponse est bien définie pour toute valeur autre que zéro. Bien sûr, en présence de règles IEEE et de NaN, vous pouvez laisser cette diapositive et tester ultérieurement NaN ou Inf.

Pour votre exemple spécifique, “faire des trucs fous” va s’exécuter. 3.0 ne sera pas 3.0000001 au moment de l’exécution.

Les autres réponses concernent davantage les cas généraux, mais même un epsilon codé en dur n’est pas la plus grande idée du monde. Un epsilon dynamic basé sur les nombres réels impliqués est bien meilleur car plus les chiffres sont positifs et négatifs, moins l’épsilon codé en dur sera vraisemblablement pertinent.

Non, il n’y a pas de problème dans votre cas d’utilisation, car les entiers sont mappés exactement à des flottants (il n’y a pas de problème de troncature décimale, comme par exemple avec 0.3; mais 3 est 1.1E10 en notation scientifique binary).

Dans le pire des cas, des nombres entiers peuvent ne pas être représentés dans float car il existe des “espaces” supérieurs à 1 entre deux nombres flottants consécutifs, mais même dans ce cas, lorsque l’entier est défini sur float faire la comparaison, il sera tronqué au float le plus proche, de la même manière que le littéral float.

Ainsi, tant que vos flottants proviendront de littéraux non décimaux, la comparaison avec l’entier équivalent sera la même, car l’entier sera converti dans le même flottant avant que la comparaison ne puisse être effectuée.

Le nœud du problème est que les nombres à virgule flottante qui ont une représentation finie en base 10 décimale n’ont pas toujours une représentation finie en base 2, binary.

Si le code ressemble littéralement à ce que vous avez publié (sans calculs intermédiaires), il faut se demander si 3.0 et (float)3 (puisque l’entier est automatiquement converti en float) sont identiques. Je pense qu’ils sont garantis d’être les mêmes dans ce cas, parce que 3 est exactement représentable en tant que float .

De plus: et même si l’entier n’est pas exactement représentable sous forme de float (c’est-à-dire s’il est vraiment gros), j’imaginerais que dans la plupart des implémentations, x.0 et (float)x seraient identiques, x.0 en premier lieu, sinon faire quelque chose comme (float)x ? Cependant, je suppose que cela n’est pas garanti par la norme.

Vous pouvez être intéressé par la conférence sur la Game Developers Conference intitulée La robustesse numérique pour les calculs géomésortingques (EPSILON n’est PAS 0.00001!) . Il explique en détail comment choisir de bonnes valeurs de seuil / epsilon pour une variété de tâches.

(+1 sur la mention de “Ce que tout informaticien devrait savoir sur le point flottant” dans une autre réponse également.)

C’est effrayant. (Je me demande quoi d’autre vous allez trouver.)

x sera promu à flotter, mais cela ne va pas vous aider. En raison de la façon dont les flottants sont représentés, utiliser == pour les comparer n’est pas fiable.

Je pourrais suggérer quelque chose comme ceci (vérifier l’erreur absolue / la différence) à la place:

 #define EPSILON 0.0001 if (fabs((float)x - y) < EPSILON) { /* Do stuff. */ } 

ce qui est une approche commune et peut suffire à vos fins, si vos valeurs de x et y sont "sympas". Si vous voulez vraiment approfondir le sujet de la comparaison des flottants, cet article contient probablement plus d'informations que vous ne le souhaitez. Cela dit à propos de la méthode epsilon:

Si la plage de expectResult est connue, la recherche d'erreur absolue est simple et efficace. Assurez-vous simplement que votre valeur d'erreur absolue est supérieure à la différence représentable minimale pour la plage et le type de flottant avec lesquels vous traitez.

Modifier :

La bonne façon consiste à utiliser la méthode epsilon :

 #include  int x = 3; int y = 3.0; if (fabs((float) x - y) < 0.0001) { // Adjust the epsilon // Do stuff }