32 votes

.NET Math.Log10 () se comporte différemment sur différentes machines

J'ai trouvé que l'exécution de

Math.Log10(double.Epsilon) 

sera de retour sur -324 sur Une machine, mais sera de retour -Infinity sur une machine B.

À l'origine se comportaient de la même façon, en les retournant -324.

Les deux machines commencé à l'aide du même système d'exploitation (windows xp SP3) et .Version NET (3.5 SP1). Il peut y avoir des mises à jour de Windows sur la machine B, mais sinon, pas de changements sont connus pour avoir passé.

Ce qui pourrait expliquer la différence de comportement?

Plus de détails à partir de discussions dans les commentaires:

  • Machine d'UN PROCESSEUR est un 32 bits Intel Core Duo T2500 2 GHz
  • La Machine B PROCESSEUR est un 32-bit Intel P4 2.4 GHz
  • Les résultats recueillis à partir de code qui s'exécute dans une grande application à l'aide de plusieurs 3ème partie des composants. Cependant, même .exe et versions de composants en cours d'exécution sur les deux machines.
  • L'impression Math.Log10(double.Epsilon) en une simple application console sur la machine B imprime -324, PAS -Infinity
  • La FPU mot de contrôle sur les deux machines est toujours 0x9001F (lire avec l' _controlfp()).

Mise à JOUR: Le dernier point (FPU mot de contrôle) n'est plus vrai: à l'Aide d'une version plus récente de _controlfp() a révélé les différents mots de contrôle, ce qui explique le comportement incohérent. (Voir rsbarro la réponse ci-dessous pour plus de détails.)

22voto

rsbarro Points 12575

Sur la base des observations par @CodeInChaos et @Alexandre C, j'ai été en mesure de jeter ensemble un peu de code pour reproduire le problème sur mon PC (Win7 x64, .NET 4.0). Il apparaît que ce problème est dû à des nombres dénormalisés de contrôle qui peut être défini à l'aide de _controlfp_s. La valeur de la double.Epsilon est le même dans les deux cas, mais la façon dont il est évalué les changements lorsque les nombres dénormalisés de contrôle est activé à partir de l'ENREGISTRER pour le RINCER.

Voici un exemple de code:

using System;
using System.Runtime.InteropServices;

namespace fpuconsole
{
    class Program
    {
        [DllImport("msvcrt.dll", EntryPoint = "_controlfp_s",
            CallingConvention = CallingConvention.Cdecl)]
        public static extern int ControlFPS(IntPtr currentControl, 
            uint newControl, uint mask);

        public const int MCW_DN= 0x03000000;
        public const int _DN_SAVE = 0x00000000;
        public const int _DN_FLUSH = 0x01000000;

        static void PrintLog10()
        {
            //Display original values
            Console.WriteLine("_controlfp_s Denormal Control untouched");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}",
                Math.Log10(double.Epsilon));
            Console.WriteLine("");

            //Set Denormal to Save, calculate Math.Log10(double.Epsilon)
            var controlWord = new UIntPtr();
            var err = ControlFPS(controlWord, _DN_SAVE, MCW_DN);
            if (err != 0)
            {
                Console.WriteLine("Error setting _controlfp_s: {0}", err);
                return;
            }
            Console.WriteLine("_controlfp_s Denormal Control set to SAVE");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}", 
                Math.Log10(double.Epsilon));
            Console.WriteLine("");

            //Set Denormal to Flush, calculate Math.Log10(double.Epsilon)
            err = ControlFPS(controlWord, _DN_FLUSH, MCW_DN);
            if (err != 0)
            {
                Console.WriteLine("Error setting _controlfp_s: {0}", err);
                return;
            }
            Console.WriteLine("_controlfp_s Denormal Control set to FLUSH");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}", 
                Math.Log10(double.Epsilon));
            Console.WriteLine("");
        }

        static int GetCurrentControlWord()
        {
            unsafe
            {
                var controlWord = 0;
                var controlWordPtr = &controlWord;
                ControlFPS((IntPtr)controlWordPtr, 0, 0);
                return controlWord;
            }
        }

        static void Main(string[] args)
        {
            PrintLog10();
        }
    }
}

Un couple de choses à noter. Tout d'abord, j'ai eu de spécifier CallingConvention = CallingConvention.Cdecl sur le ControlFPS déclaration pour éviter un déséquilibre de la pile d'exception pendant le débogage. Deuxièmement, j'ai dû recourir à la contamination de code pour récupérer la valeur du mot de contrôle en GetCurrentControlWord(). Si quelqu'un connaît une meilleure façon d'écrire la méthode, s'il vous plaît laissez-moi savoir.

Voici le résultat:

_controlfp_s Denormal Control untouched
        Current _controlfp_s control word: 0x0009001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -323.306215343116

_controlfp_s Denormal Control set to SAVE
        Current _controlfp_s control word: 0x0009001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -323.306215343116

_controlfp_s Denormal Control set to FLUSH
        Current _controlfp_s control word: 0x0109001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -Infinity

Pour déterminer ce qui se passe avec machine et la machine B, vous pouvez prendre l'exemple d'application ci-dessus et de les exécuter sur chaque machine. Je pense que vous allez trouver que:

  1. Machine et la Machine B sont à l'aide de différents paramètres pour _controlfp_s dès le début. L'exemple d'application montrent différentes de contrôle de la valeur d'un mot dans le premier bloc de sorties sur Une Machine que sur une Machine B. Après l'application des forces de nombres dénormalisés de contrôle pour ENREGISTRER, puis la sortie doit correspondre. Si c'est le cas, alors peut-être vous pouvez simplement la force de nombres dénormalisés de contrôle pour ENREGISTRER sur la Machine B lorsque l'application démarre.
  2. Machine et la Machine B utilisez les mêmes paramètres pour _controlfp_s, et la sortie de l'exemple d'application est exactement le même sur les deux machines. Si c'est le cas, alors il doit y avoir un peu de code dans votre application (éventuellement DirectX, WPF?) c'est en feuilletant le _controlfp_s réglages sur la Machine B, mais pas sur la Machine A.

Si vous obtenez une chance d'essayer l'exemple d'application sur chaque machine, veuillez mettre à jour les commentaires avec les résultats. Je suis intéressé de voir ce qui se passe.

8voto

CodesInChaos Points 60274

Il est possible qu'une dll ait été chargée dans le processus contenant les indicateurs de virgule flottante x87. Les bibliothèques liées à DirectX / OpenGL sont notoires pour cela.

Il pourrait également y avoir des différences dans le code jitted (il n’est pas nécessaire que les points flottants se comportent de manière spécifique dans .net), mais c’est très peu probable puisque vous utilisez la même version .net et OS.

Dans le code .net, les constantes sont intégrées au code d'appel. Il ne devrait donc y avoir aucune différence entre double.Epsilons .

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