61 votes

La portée des variables à virgule flottante affectera-t-elle leurs valeurs?

Si nous exécutons le code C # suivant sur une application console, nous aurons un message sous la forme The sums are Not equal .

Si nous l'exécutons après avoir supprimé la mise en commentaire de la ligne System.Console.WriteLine() , nous obtiendrons un message sous la forme The sums are equal .

     static void Main(string[] args)
    {
        float f = Sum(0.1f, 0.2f);
        float g = Sum(0.1f, 0.2f);

        //System.Console.WriteLine("f = " + f + " and g = " + g);

        if (f == g)
        {
            System.Console.WriteLine("The sums are equal");
        }
        else
        {
            System.Console.WriteLine("The sums are Not equal");
        }
    }

    static float Sum(float a, float b)
    {
        System.Console.WriteLine(a + b);
        return a + b;
    }
 

Quelle est la raison réelle de ce comportement?

46voto

Mishax Points 1562

Il n'est pas lié à portée. C'est la combinaison de la pile de la dynamique de point flottant et de manutention. Une certaine connaissance de compilateurs contribuera à faire de ce comportement contre-intuitif clair.

Lorsque l' Console.WriteLine des observations, les valeurs f et g sont sur la pile d'évaluation et d'y rester jusqu'à ce que après avoir passé le test d'égalité dans votre méthode Principale.

Lors de l' Console.Writeline n'est pas commentée, les valeurs f et g sont déplacés à partir de la pile d'évaluation de la pile d'appel au moment de l'invocation, pour être restauré à l'évaluation de la pile lors de l' Console.WriteLine de rendement. Et votre comparaison, if (f == g) est fait par la suite. Certains d'arrondi peuvent se produire au cours de ce stockage de valeurs de la pile d'appel et certaines informations peuvent être perdues.

Dans le cas où vous ne invoquer Console.WriteLine, f et de la g dans le test de comparaison ne sont pas les même valeurs. Elles ont été copiées et restaurée à un format qui a des règles différentes en matière de précision et d'arrondissement, par la machine virtuelle.

Dans votre code, lors de l'invocation de l' Console.WriteLine est commenté, la pile d'évaluation n'est jamais stocké sur la pile d'appel et aucun arrondi. Car il est permis pour les implémentations de la plate-forme pour fournir une meilleure précision sur la pile d'évaluation, cette anomalie peut survenir.

MODIFIER Ce que nous sommes frapper dans ce cas, est autorisé par la spécification CLI. Dans la section I. 12.1.3 il lit:

Des emplacements de stockage pour les nombres à virgule flottante (statique, les éléments du tableau, et les champs de classes) sont de taille fixe. Pris en charge les tailles de stockage sont float32 et float64. Partout ailleurs (sur la pile d'évaluation, comme les arguments comme des types de retour, et que les variables locales) en virgule flottante les nombres sont représentés à l'aide d'un flottant interne-type de point. Dans chaque un tel cas, la valeur nominale type de la variable ou l'expression est soit float32or float64, mais sa valeur peut être représentée en interne avec d'autres offres et/ou de précision. La taille de l'interne représentation à virgule flottante est dépendant de l'implémentation, peut varier, et il doit avoir une précision au moins aussi grand que celui de la variable ou de la l'expression représentée.

Les mots clés de cette citation sont "dépendant de l'implémentation" et "peut varier". Dans le cas des OP cas, nous voyons sa mise en œuvre, en effet, varier.

Non strictfp arithmétique à virgule flottante dans la plate-forme Java dispose également d'une question connexe, pour plus d'informations, consultez également ma réponse à la Volonté d'opérations en virgule flottante sur la JVM donner les mêmes résultats sur toutes les plateformes?

24voto

Jon Skeet Points 692016

Quelle est la vraie raison de ce comportement?

Je ne peux pas fournir des détails sur ce qui se passe exactement dans ce cas précis, mais je comprends le général problème, et pourquoi l'utilisation d' Console.WriteLine peut changer les choses.

Comme nous l'avons vu dans ton post précédent, parfois, des opérations sont effectuées sur des types à virgule flottante à un degré de précision plus élevé que celui spécifié dans la variable de type. Pour les variables locales, qui peuvent inclure la valeur est stockée dans la mémoire pendant l'exécution d'une méthode.

Je soupçonne que, dans votre cas:

  • l' Sum méthode est inline (mais voir plus loin)
  • la somme elle-même est effectué avec une plus grande précision que le 32-bit à virgule flottante que vous attendez
  • la valeur d' une des variables (f - dire) est stockée dans une haute précision de registre
    • pour cette variable, le "plus précis" résultat est stocké directement
  • la valeur de l' autre variable (g) sont stockées sur la pile comme une valeur de 32 bits
    • pour cette variable, le "plus précis" résultat est réduit à 32 bits
  • lorsque la comparaison est effectuée, la variable sur la pile est en train d'être promu à une plus grande précision de la valeur et de la comparaison avec les autres de haute précision de la valeur, et la différence est due à l'un d'entre eux ayant déjà perdu de l'information et de l'autre pas

Lorsque vous supprimez l' Console.WriteLine déclaration, je devine que (pour quelque raison que ce soit) et les forces de deux variables qui doivent être stockés dans leur "propre" de 32 bits de précision, de sorte qu'ils sont tous les deux d'être traités de la même façon.

Cette hypothèse est tous un peu gâché par le fait que l'ajout de

[MethodImpl(MethodImplOptions.NoInlining)]

... ne pas changer le résultat d'aussi loin que je peux voir. J'ai peut-être fait quelque chose de mal le long de ces lignes.

Vraiment, il faut regarder le code assembleur qui est en cours d'exécution - je n'ai pas le temps de le faire maintenant, malheureusement.

14voto

Bas Brekelmans Points 13799

(Pas une vraie réponse mais j'espère de la documentation à l'appui)

Configuration: Core i7, Windows 8.1, Visual Studio 2013

Plate-forme x86:

 Version      Optimized Code?        Debugger Enabled?          Outcome
4.5.1        Yes                    No                         Not equal
4.5.1        Yes                    Yes                        Equal
4.5.1        No                     No                         Equal
4.5.1        No                     Yes                        Equal
2.0          Yes                    No                         Not Equal
2.0          Yes                    Yes                        Equal
2.0          No                     No                         Equal
2.0          No                     Yes                        Equal
 

Plateforme x64:

 Version      Optimized Code?        Debugger Enabled?          Outcome
4.5.1        Yes                    No                         Equal
4.5.1        Yes                    Yes                        Equal
4.5.1        No                     No                         Equal
4.5.1        No                     Yes                        Equal
2.0          Yes                    No                         Equal
2.0          Yes                    Yes                        Equal
2.0          No                     No                         Equal
2.0          No                     Yes                        Equal
 

La situation ne semble se produire qu'avec un code optimisé sur des configurations x86.

4voto

Voici une explication tirée d’un article de blog MSDN, d’un CLR et de virgule flottante: Quelques réponses à des questions courantes .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X