bug de précision gcc?

voix
8

Je ne peux que supposer que c'est un bug. La première assertion passe tandis que le second échoue:

double sum_1 =  4.0 + 6.3;
assert(sum_1 == 4.0 + 6.3);

double t1 = 4.0, t2 = 6.3;

double sum_2 =  t1 + t2;
assert(sum_2 == t1 + t2);

Sinon un bug, pourquoi?

Créé 27/08/2009 à 00:03
source utilisateur
Dans d'autres langues...                            


7 réponses

voix
13

Vous comparez nombres à virgule flottante. Ne pas le faire, nombres à virgule flottante ont erreur de précision inhérente à certaines circonstances. Au lieu de cela, prendre la valeur absolue de la différence des deux valeurs et affirmer que la valeur est inférieure à un petit nombre (epsilon).

void CompareFloats( double d1, double d2, double epsilon )
{
    assert( abs( d1 - d2 ) < epsilon );
} 

Cela n'a rien à voir avec le compilateur et tout à voir avec la façon dont les nombres à virgule flottante sont mises en œuvre. voici la spécification IEEE:

http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

Créé 27/08/2009 à 00:06
source utilisateur

voix
1

La comparaison des nombres double précision sont intrinsèquement inexactes. Par exemple, vous pouvez souvent trouver 0.0 == 0.0retour faux . Cela est dû à la façon dont les magasins FPU et les numéros de pistes.

Wikipedia dit :

Test pour l'égalité est problématique. Deux séquences de calcul qui sont mathématiquement égale peut ainsi produire différentes valeurs à virgule flottante.

Vous aurez besoin d'utiliser un delta pour donner une tolérance pour vos comparaisons, plutôt que d'une valeur exacte.

Créé 27/08/2009 à 00:08
source utilisateur

voix
2

Il se peut que dans l'un des cas, vous finissez par la comparaison d'un 64 bits double pour un registre interne de 80 bits. Il peut être instructif de regarder les instructions d'assemblage GCC émet pour les deux cas ...

Créé 27/08/2009 à 00:12
source utilisateur

voix
12

C'est quelque chose qui m'a mordu, aussi.

Oui, les nombres à virgule flottante ne doivent jamais être comparés pour l'égalité en raison de l'erreur d'arrondi, et vous le saviez probablement.

Mais dans ce cas, vous calcul t1+t2, puis calculer à nouveau. Certes , cela doit produire un résultat identique?

Voici ce qui se passe probablement. Je parie que vous êtes en cours d'exécution sur ce un processeur x86, correct? La FPU x86 utilise 80 bits pour ses registres internes, mais des valeurs dans la mémoire sont stockées en tant que doubles de 64 bits.

Ainsi , on t1+t2calcule d' abord avec 80 bits de précision, puis - je présume - stocké sur la mémoire à sum_264 bits de précision - et certains arrondi se produit. Pour assert, il est chargé de nouveau dans un registre à virgule flottante, et t1+t2est calculé à nouveau, encore une fois avec 80 bits de précision. Alors maintenant , vous comparez sum_2, qui était auparavant arrondi à une valeur en virgule flottante 64 bits, avec t1+t2, qui a été calculé avec une précision plus élevée (80 bits) - et c'est la raison pour laquelle les valeurs ne sont pas exactement identiques.

Modifier Alors pourquoi ne passe le premier test? Dans ce cas, le compilateur évalue probablement 4.0+6.3au moment de la compilation et le stocke en tant que quantité 64 bits - à la fois pour l'attribution et l'assert. Les valeurs Donc identiques sont comparées, et l'assert passe.

Second Edition est ici le code assembleur généré pour la deuxième partie du code (gcc, x86), avec des commentaires - à peu près suit le scénario décrit ci - dessus:

// t1 = 4.0
fldl    LC3
fstpl   -16(%ebp)

// t2 = 6.3
fldl    LC4
fstpl   -24(%ebp)

// sum_2 =  t1+t2
fldl    -16(%ebp)
faddl   -24(%ebp)
fstpl   -32(%ebp)

// Compute t1+t2 again
fldl    -16(%ebp)
faddl   -24(%ebp)

// Load sum_2 from memory and compare
fldl    -32(%ebp)
fxch    %st(1)
fucompp

Note intéressante: Cela a été compilé sans optimisation. Quand il est compilé avec -O3, le compilateur optimise l' ensemble du code loin.

Créé 27/08/2009 à 00:16
source utilisateur

voix
3

Je suis votre problème sur dupliqué mon processeur Intel Core 2 Duo, et je regardais le code assembleur. Voici ce qui se passe: lorsque votre compilateur évalue t1 + t2, il fait

load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum

Quand il stocke en sum_2elle le fait

round the 80-bit sum to a 64-bit number and store it

Ensuite , la ==comparaison compare la somme de 80 bits à une somme de 64 bits, et ils sont différents, principalement parce que la partie décimale 0,3 ne peut pas être représenté exactement en utilisant un numéro de binaire à virgule flottante, de sorte que vous comparez une « décimale répéter » ( répétition effectivement binaire) qui a été tronqué à deux longueurs différentes.

Ce qui est vraiment énervant est que si vous compilateur avec gcc -O1ou gcc -O2, gcc fait le mauvais calcul au moment de la compilation, et le problème disparaît. Peut-être cela est correct selon la norme, mais il est juste une raison de plus que gcc est pas mon compilateur préféré.


PS Quand je dis que ==compare une somme de 80 bits avec une somme de 64 bits, bien sûr je veux dire vraiment compare la version étendue de la somme de 64 bits. Vous pourriez bien penser

sum_2 == t1 + t2

décide de

extend(sum_2) == extend(t1) + extend(t2)

et

sum_2 = t1 + t2

décide de

sum_2 = round(extend(t1) + extend(t2))

Bienvenue dans le monde merveilleux de la virgule flottante!

Créé 27/08/2009 à 00:46
source utilisateur

voix
3

Lorsque l'on compare les nombres à virgule flottante pour la proximité que vous voulez généralement de mesurer leur différence relative, qui est définie comme

if (abs(x) != 0 || abs(y) != 0) 
   rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
   rel_diff(x,y) = max(abs(x),abs(y))

Par exemple,

rel_diff(1.12345, 1.12367)   = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019

L'idée est de mesurer le nombre de premiers chiffres significatifs les chiffres ont en commun; si vous prenez le log10 de 0,000195787019 vous obtenez 3,70821611, qui est le nombre de 10 chiffres de premier plan de base tous les exemples ont en commun.

Si vous devez déterminer si deux nombres à virgule flottante sont égaux, vous devez faire quelque chose comme

if (rel_diff(x,y) < error_factor * machine_epsilon()) then
  print "equal\n";

où la machine est epsilon le plus petit nombre qui peut être maintenu dans la mantisse du matériel à virgule flottante utilisé. La plupart des langages informatiques ont un appel de fonction pour obtenir cette valeur. error_factor devrait être basé sur le nombre de chiffres significatifs que vous pensez être consommé par des erreurs d'arrondi (et d'autres) dans le calcul des nombres x et y. Par exemple, si je savais que x et y sont le résultat d'environ 1000 et sommations ne savais pas de limites sur le nombre étant sommés, je mettrais error_factor à environ 100.

J'ai essayé d'ajouter ces liens comme mais ne pouvait pas puisque c'est mon premier post:

  • en.wikipedia.org/wiki/Relative_difference
  • en.wikipedia.org/wiki/Machine_epsilon
  • en.wikipedia.org/wiki/Significand (mantisse)
  • en.wikipedia.org/wiki/Rounding_error
Créé 27/08/2009 à 03:09
source utilisateur

voix
0

Ce « problème » peut être « fixé » en utilisant les options suivantes:

-msse2 -mfpmath = sse

comme expliqué sur cette page:

http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html

Une fois que je ces options, à la fois AFFIRME passé.

Créé 27/08/2009 à 22:52
source utilisateur

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more