2009-12-03 11 views
11

J'ai eu un petit moment WTF ce matin. WTF Thessaloniciens peut se résumer avec ceci:L'ajout de flotteur promu à doubler?

float x = 0.2f; 
float y = 0.1f; 
float z = x + y; 
assert(z == x + y); //This assert is triggered! (Atleast with visual studio 2008) 

La raison semble être que l'expression x + y est promu deux fois et comparée à la version tronquée en z. (Si je change z en double l'assertion n'est pas déclenchée).

Je peux voir que pour des raisons de précision, il serait logique d'effectuer toutes les opérations arithmétiques en virgule flottante en double précision avant de convertir le résultat en simple précision. J'ai trouvé le paragraphe suivant dans la norme (que je suppose que je savais déjà, mais pas dans ce contexte):

4.6.1. « Un rvalue de type float peut être converti en un rvalue de type double. La valeur est inchangée »

Ma question est, est x + y garanti d'être promu doubler ou est à la discrétion du compilateur?

MISE À JOUR: Comme beaucoup de gens a affirmé que l'on devrait pas utiliser == pour virgule flottante, je voulais juste dire que dans le cas particulier, je travaille avec une comparaison exacte est justifiée.

La comparaison de point flottant est difficile, voici un intéressant link sur le sujet qui je pense n'a pas été mentionné.

+0

Vous pouvez également essayer "juste pour le plaisir de celui-ci" pour forcer les calculs de précision simples sur FPU par appeler: _controlfp (_PC_24, MCW_PC); – MaR

Répondre

14

Vous ne pouvez généralement pas supposer que == fonctionnera comme prévu pour les types virgule flottante. Comparer les valeurs arrondies ou utiliser des constructions comme abs(a-b) < tolerance à la place.

La promotion est entièrement à la discrétion du compilateur (et dépendra du matériel cible, du niveau d'optimisation, etc.).Ce qui se passe dans ce cas particulier est presque certainement que les valeurs sont stockées dans les registres FPU avec une plus grande précision qu'en mémoire - en général, le matériel FPU moderne fonctionne avec une précision double ou supérieure en interne quelle que soit la précision demandée par le programmeur. le code de génération du compilateur pour effectuer les conversions appropriées lorsque les valeurs sont stockées dans la mémoire; dans une construction non optimisée, le résultat de x+y est toujours dans un registre au moment où la comparaison est faite mais z aura été stockée dans la mémoire et récupérée, et ainsi tronquée pour flotter avec précision.

+2

Dans le cas général, je n'utiliserais pas == pour les flotteurs non plus, mais dans le cas où j'ai eu des problèmes, je ne fais que l'addition dans un ordre très précis. Si je change le code pour flotter (x + y) il n'y a pas de problème avec l'opérateur ==. –

+0

8087 <- 80387 n'a pas beaucoup évolué depuis 8087. – jsbueno

+1

@Andreas: oui, alors vous comparez les valeurs arrondies :) – moonshadow

8

La section Working draft for the next standard C++0x 5 point 11 dit

Les valeurs des opérandes variables et les résultats des expressions flottantes peuvent être représentés dans une plus grande précision et la gamme que celle requise par le type; les types ne sont pas modifiés ainsi

Donc à la discrétion du compilateur.

0

Je pense que ce serait à la discrétion du compilateur, mais vous pourriez toujours le forcer avec une distribution si c'était votre pensée?

1

L'utilisation 4.3.2 gcc, l'affirmation est pas déclenché, et en effet, le rvalue retourné de x + y est un float, plutôt que d'un double.

Donc c'est au compilateur. C'est pourquoi il n'est jamais sage de s'appuyer sur l'égalité exacte entre deux valeurs à virgule flottante.

-1

Encore une autre raison de ne jamais comparer directement les flotteurs.

if (fabs(result - expectedResult) < 0.00001) 
1

C'est le problème car le nombre de virgules vers la conversion binaire ne donne pas une précision précise.

Et au sein de sizeof(float) octets il ne peut pas accepter la valeur précise du nombre flottant et l'opération arithmétique peut conduire à une approximation et donc l'égalité échoue.

Voir ci-dessous par ex.

float x = 0.25f; //both fits within 4 bytes with precision 
float y = 0.50f; 
float z = x + y; 
assert(z == x + y); // it would work fine and no assert