c# - logaritmo - .NET Math.Log10() se comporta de manera diferente en diferentes máquinas
c# math log (2)
Encontré ese funcionamiento
Math.Log10(double.Epsilon)
devolverá aproximadamente -324
en la máquina A, pero devolverá -Infinity
en la máquina B.
Originalmente se comportaron de la misma manera al devolver -324
.
Ambas máquinas comenzaron utilizando el mismo sistema operativo (WinXP SP3) y la versión .NET (3.5 SP1). Es posible que haya habido actualizaciones de Windows en la máquina B, pero de lo contrario no se sabe que haya ocurrido ningún cambio.
¿Qué podría explicar la diferencia en el comportamiento?
Más detalles de las discusiones en los comentarios:
- La CPU de la máquina A es un procesador Intel Core Duo T2500 a 2 GHz de 32 bits
- La CPU de la máquina B es una Intel P4 de 2,4 bits a 2,4 GHz
- Resultados recopilados del código que se ejecuta en una aplicación grande que utiliza varios componentes de terceros. Sin embargo, las mismas versiones .exe y componente se ejecutan en ambas máquinas.
- Imprimir
Math.Log10(double.Epsilon)
en una aplicación de consola simple en impresiones de la máquina B-324
, NOT-Infinity
- La palabra de control FPU en ambas máquinas siempre es
0x9001F
(leída con_controlfp()
).
ACTUALIZACIÓN: el último punto (palabra de control FPU) ya no es verdadero: el uso de una versión más reciente de _controlfp () reveló palabras de control diferentes, lo que explica el comportamiento inconsistente. (Consulte la respuesta de rsbarro a continuación para obtener detalles).
En base a los comentarios de @CodeInChaos y @Alexandre C, pude juntar algunos códigos para reproducir el problema en mi PC (Win7 x64, .NET 4.0). Parece que este problema se debe al control _controlfp_s que se puede establecer usando _controlfp_s . El valor de double.Epsilon es el mismo en ambos casos, pero la forma en que se evalúa cambia cuando el control negativo se cambia de SAVE a FLUSH.
Aquí está el código de ejemplo:
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 par de cosas para tener en cuenta. En primer lugar, tuve que especificar CallingConvention = CallingConvention.Cdecl
en la declaración ControlFPS
para evitar obtener una excepción de pila desbalanceada durante la depuración. En segundo lugar, tuve que recurrir a un código inseguro para recuperar el valor de la palabra de control en GetCurrentControlWord()
. Si alguien sabe de una mejor manera de escribir ese método, házmelo saber.
Aquí está el resultado:
_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
Para determinar qué está pasando con la máquina A y la máquina B, puede tomar la aplicación de ejemplo que se muestra arriba y ejecutarla en cada máquina. Creo que vas a encontrar eso:
- La máquina A y la máquina B están usando diferentes configuraciones para _controlfp_s desde el principio. La aplicación de muestra mostrará diferentes valores de palabra de control en el primer bloque de salidas en la Máquina A que en la Máquina B. Después de que la aplicación fuerce el control Denormal a GUARDAR, la salida debería coincidir. Si este es el caso, entonces tal vez solo pueda forzar el control negativo a SAVE en la Máquina B cuando se inicie su aplicación.
- La máquina A y la máquina B están usando la misma configuración para _controlfp_s, y la salida de la aplicación de muestra es exactamente la misma en ambas máquinas. Si ese es el caso, entonces debe haber algún código en su aplicación (¿posiblemente DirectX, WPF?) Que está volteando la configuración de _controlfp_s en la Máquina B pero no en la Máquina A.
Si tiene la oportunidad de probar la aplicación de muestra en cada máquina, actualice los comentarios con los resultados. Estoy interesado en ver qué pasa.
Es posible que se haya cargado un dll en el proceso que alteró las banderas de punto flotante x87. Las bibliotecas relacionadas con DirectX / OpenGL son conocidas por esto.
También podría haber diferencias en el código jitted (No hay ningún requisito para que los puntos flotantes se comporten de una manera específica en .net), pero eso es muy poco probable ya que usa la misma versión .net y OS.
En las constantes .net se hornea en el código de llamada, por lo que no debe haber diferencias entre los double.Epsilons
.