c# precision .net-4.5.2

c# - Cambios en Math.Exp o doble implementación en.net 4.5.2



precision .net-4.5.2 (4)

Si ejecuto la sentencia

Math.Exp(113.62826122038274).ToString("R")

en una máquina con .net 4.5.1 instalado, entonces obtengo la respuesta

2.2290860617259248E+49

Sin embargo, si ejecuto el mismo comando en una máquina con .net framework 4.5.2 instalado, obtengo la respuesta

2.2290860617259246E+49

(es decir, el dígito final cambia)

Me doy cuenta de que esto es bastante insignificante en términos puramente numéricos, pero ¿alguien sabe de algún cambio que se haya realizado en .net 4.5.2 que explique el cambio?

(No prefiero un resultado al otro, solo me interesa entender por qué ha cambiado)

Si salgo

The input in roundtrip format The input converted to a long via BitConverter.DoubleToInt64Bits Math.Exp in roundtrip format Math.Exp converted to a long via BitConverter.DoubleToInt64Bits

luego en 4.5.1 me sale

113.62826122038274 4637696294982039780 2.2290860617259248E+49 5345351685623826106

y en 4.5.2 me sale:

113.62826122038274 4637696294982039780 2.2290860617259246E+49 5345351685623826105

Entonces, para la misma entrada exacta, obtengo una salida diferente (como se puede ver en los bits, por lo que no se trata de un formato de ida y vuelta)

Más detalles:

Compilado una vez usando VS2015

Ambas máquinas en las que estoy ejecutando los binarios son de 64 bits

Uno tiene .net 4.5.1 instalado, el otro 4.5.2

Solo por claridad: la conversión de cadena es irrelevante ... Obtengo el cambio en los resultados, independientemente de si la conversión de cadena está involucrada. Mencioné eso únicamente para demostrar el cambio.


.NET usa las funciones de lib de matemáticas del CRT para hacer estos cálculos. El CRT utilizado por .NET a menudo se actualiza con cada lanzamiento, por lo que puede esperar que los resultados cambien entre los lanzamientos de .NET, sin embargo, siempre estarán dentro del + / 1ulp prometido.


Me tropecé con el mismo problema, sin embargo, solo lo consigo después de instalar .Net 4.6.

La instalación de .Net 4.6 actualizó c: / windows / system32 / msvcr120_clr0400.dll y c: / windows / syswow64 / msvc120_clr0400.dll.

Antes de la instalación, estos archivos DLL tenían propiedades de archivo-> detalles: "Versión del archivo 12.0.51689.34249" y "Nombre del producto Microsoft Visual Studio 12 CTP"

Después de instalar .net 4.6, éstos tenían "Versión del archivo 12.0.52512.0" y "Nombre del producto Microsoft Visual Studio 2013"

Ajusté mi prueba para incluir su ejemplo y veo los mismos números de antes / después que usted al alternar entre las versiones de esta DLL.

(Nuestro conjunto de pruebas no mostró ningún resultado modificado al ejecutarse en las versiones .net 4, 4.5, 4.5.1 o 4.5.2 hasta que se actualizaron estos DLL).


Para mostrar cómo los proyectos que apuntan a diferentes versiones de .NET afectan la doble conversión de la cadena, construí 4 proyectos dirigidos a diferentes versiones que se ejecutan en la misma máquina dev con .NET 4.6.

Aquí está el código

double foo = Convert.ToDouble("33.94140881672595");

Y aquí está la salida.

33.941408816725954 (.NET 4)

33.941408816725946 (.NET 4.5)

33.941408816725946 (.NET 4.5.2)

33.941408816725946 (.NET 4.6)

Entonces, definitivamente hubo un cambio en el método de conversión después de .NET 4


Suspiro, los misterios de las matemáticas de punto flotante continúan afectando a los programadores para siempre. No tiene nada que ver con la versión framework. La configuración relevante es Proyecto> Propiedades> pestaña Generar.

Objetivo de la plataforma = x86: 2.2290860617259248E+49
Objetivo de la plataforma = AnyCPU o x64: 2.2290860617259246E+49

Si ejecuta el programa en un sistema operativo de 32 bits, siempre obtendrá el primer resultado. Tenga en cuenta que el formato de ida y vuelta está sobre especificado, contiene más dígitos de los que puede almacenar un doble. Que es 15. Cuéntelos, obtendrás 16. Esto asegura que la representación binaria del doble, el 1 y el 0 sean iguales. La diferencia entre los dos valores es el bit menos significativo en la mantisa.

La razón por la que el LSB no es el mismo es porque el jitter x86 está gravado con el código de generación para la FPU . Lo que tiene la muy indeseable propiedad de usar más bits de precisión de lo que puede almacenar un doble. 80 bits en lugar de 64. Teóricamente para generar resultados de cálculo más precisos. Lo que hace, pero rara vez de forma reproducible . Pequeños cambios en el código pueden producir grandes cambios en el resultado del cálculo. Solo ejecutando el código con un depurador adjunto puede cambiar el resultado ya que deshabilita el optimizador.

Intel corrigió este error con el conjunto de instrucciones SSE2, reemplazando completamente las instrucciones matemáticas de punto flotante de la FPU. No utiliza precisión extra, un doble siempre tiene 64 bits. Con la propiedad altamente deseable de que el resultado del cálculo ya no depende del almacenamiento intermedio, ahora es mucho más consistente. Pero menos precisa.

Que el jitter x86 use las instrucciones de FPU es un accidente histórico. Lanzado en 2002, no había suficientes procesadores alrededor de ese SSE2 compatible. Ese accidente ya no se puede arreglar ya que cambia el comportamiento observable de un programa. No fue un problema para el jitter x64, se garantiza que un procesador de 64 bits también es compatible con SSE2.

Un proceso de 32 bits usa la función exp () que usa el código FPU. Un proceso de 64 bits usa la función exp () que usa el código SSE. El resultado puede ser diferente por un LSB. Pero aún con una precisión de 15 dígitos significativos, es 2.229086061725925E + 49. Todo lo que puedes esperar de las matemáticas con doble .