Question

Considérons le code C #:

double result1 = 1.0 + 1.1 + 1.2;
double result2 = 1.2 + 1.0 + 1.1;

if (result1 == result2)
{
    ...
}

result1 doit toujours être égal droit result2? La chose est, il ne fonctionne pas. result1 est 3.3 et result2 est 3,3000000000000003. La seule différence est l'ordre des constantes.

Je sais que les doubles sont mises en œuvre de telle sorte que les problèmes d'arrondi peuvent se produire. Je suis conscient que je peux utiliser à la place décimaux si je besoin d'une précision absolue. Ou que je peux utiliser Math.Round () dans mon instruction if. Je suis juste un nerd qui veut comprendre ce que le compilateur C # est en train de faire. Quelqu'un peut-il me dire?

Modifier

Merci à tous ceux qui ont à ce jour sur la lecture suggéraient arithmétique à virgule flottante et / ou a parlé de l'imprécision inhérente de la façon dont le processeur gère double. Mais je sens que l'objectif principal de ma question est toujours sans réponse. Ce qui est de ma faute pour ne pas le phrasé correctement. Permettez-moi comme ceci:

Briser le code ci-dessus, j'attendre les opérations suivantes se produire:

double r1 = 1.1 + 1.2;
double r2 = 1.0 + r1
double r3 = 1.0 + 1.1
double r4 = 1.2 + r3

Supposons que chacun des ajouts ci-dessus avait une erreur d'arrondi (de e1..e4 numérotée). Donc r1 contient une erreur d'arrondi e1, comprend des erreurs d'arrondi r2 + e2 e1, r3 contient e3 et r4 contient e3 + e4.

Maintenant, je ne sais pas comment exactement comment les erreurs d'arrondi se produisent, mais je me serais attendu à + e2 à e1 égale e3 + e4. Il est clair que ce ne est pas, mais qui semble en quelque sorte de mal à moi. Une autre chose est que lorsque je lance le code ci-dessus, je ne reçois pas d'erreurs d'arrondi. Voilà ce qui me fait penser qu'il est le compilateur C # qui fait quelque chose de bizarre plutôt que la CPU.

Je sais que je demande beaucoup et peut-être le meilleur tout le monde de réponse peut donner est d'aller faire un PHD dans la conception du processeur, mais je pensais juste que je demande.

Edit 2

En regardant l'IL de mon exemple de code d'origine, il est clair que c'est le compilateur de ne pas l'unité centrale de traitement qui est fait ceci:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
    .maxstack 1
    .locals init (
        [0] float64 result1,
        [1] float64 result2)
    L_0000: nop 
    L_0001: ldc.r8 3.3
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003
    L_0014: stloc.1 
    L_0015: ret 
}

Le compilateur ajoute les chiffres pour moi!

Était-ce utile?

La solution

  

Je me serais attendu à + e2 e1 à l'égalité e3 + e4.

Ce n'est pas tout à fait la différence attend

 floor( 5/3 ) + floor( 2/3 + 1 )

à égale

 floor( 5/3 + 2/3 ) + floor( 1 )

sauf que vous êtes en multipliant par 2 ^ 53 avant de prendre la parole.

En utilisant 12 bits à virgule flottante de précision et troncature avec vos valeurs:

1.0            =  1.00000000000
1.1            =  1.00011001100
1.2            =  1.00110011001

1.0 + 1.1      = 10.00011001100 // extended during sum
r1 = 1.0 + 1.1 = 10.0001100110  // truncated to 12 bit
r1  + 1.2      = 11.01001100101 // extended during sum
r2 = r1  + 1.2 = 11.0100110010  // truncated to 12 bit

1.1 + 1.2      = 10.01001100110 // extended during sum
r3 = 1.1 + 1.2 = 10.0100110011  // truncated to 12 bit
r3 + 1.0       = 11.01001100110 // extended during sum
r4 = r3  + 1.0 = 11.0100110011  // truncated to 12 bit

Donc changer l'ordre des opérations / troncature provoque l'erreur de changer, et r4! = R2. Si vous ajoutez 1.1 et 1.2 dans ce système, le dernier bit porte, donc à ne pas perdre sur troncature. Si vous ajoutez 1,0 à 1,1, le dernier bit de 1,1 est perdu et donc le résultat est le même.

Dans un ordre, l'arrondissement (par troncature) supprime un 1 de fuite.

Dans l'autre ordre, l'arrondissement supprime un 0 arrière deux fois.

On ne correspond pas à zéro; de sorte que les erreurs ne sont pas les mêmes.

Doubles ont beaucoup plus de bits de précision et C # utilise probablement arrondi plutôt que troncature, mais nous espérons que ce modèle simple, vous montre différentes erreurs peuvent se produire avec différents ordonnancements des mêmes valeurs.

La différence entre fp et mathématiques est que + est un raccourci pour « ajouter ensuite tour » plutôt que d'ajouter.

Autres conseils

Le compilateur C # ne fait rien. La CPU est.

si vous avez un dans un registre de CPU, et vous ajoutez B, le résultat stocké dans ce registre est A + B, approximée à la précision flottante utilisée

Si vous ajoutez C, l'erreur additionne. Cet ajout d'erreur est pas une opération transitive, donc la différence finale.

Voir le papier classique (Ce que tout informaticien doit savoir sur-virgule flottante arithmétique) sur le sujet. Ce genre de choses est ce qui se passe avec l'arithmétique en virgule flottante. Il faut un informaticien pour vous dire que 1/3 + 1/3 + 1/3 is'nt égale à 1 ...

Ordre des opérations à virgule flottante est important. Ne répond pas directement à votre question, mais vous devez toujours être prudent comparer les nombres à virgule flottante. Il est d'habitude d'inclure une tolérance:

double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
    ...
}

Cela peut intéresser: Ce que tout informaticien doit savoir sur FLOATING- arithmétique point

  

result1 doit toujours être égal result2   droit?

Mauvais . Cela est vrai en mathématiques, mais pas dans virgule flottante Arithmétique.

Vous aurez besoin de lire certains primaire numérique Analyse .

Pourquoi les erreurs ne sont pas les mêmes selon l'ordre peut être expliqué avec un autre exemple.

Disons que pour les nombres inférieurs à 10, il peut stocker tous les numéros, de sorte qu'il peut stocker 1, 2, 3, et ainsi de suite jusqu'au 10, mais après 10, il ne peut stocker chaque deuxième numéro, en raison à la perte interne de précision, en d'autres termes, il ne peut stocker 10, 12, 14, etc.

Maintenant, avec cet exemple, vous comprendrez pourquoi ce qui suit produit des résultats différents:

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10

Le problème avec les nombres à virgule flottante est qu'ils ne peuvent pas être représentés avec précision, et l'erreur ne vont pas toujours de la même façon, si l'ordre importera.

Par exemple, 3,00000000003 + 3,00000000003 pourrait finir par être 6,00000000005 (avis non 6 à la fin), mais 3,00000000003 + 2,99999999997 pourrait finir par être 6,00000000001, et que:

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002

mais, changer l'ordre:

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004

Il importera.

Maintenant, bien sûr, vous pourriez avoir de la chance en ce que les exemples ci-dessus s'équilibrent, en ce que le premier se balancer par .xxx1 et l'autre par .xxx1, vous donnant .xxx3 à la fois, mais il n'y a pas garantie.

Vous êtes en train de ne pas utiliser les mêmes valeurs parce que les résultats intermédiaires sont différents:

double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;

Parce que double ne peut pas représenter les valeurs décimales exactement vous obtenez des résultats différents.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top