Changes to Math.Exp or double implementation in .net 4.5.2

北城余情 提交于 2021-02-16 09:40:10

问题


If I run the statement

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

on a machine with .net 4.5.1 installed, then I get the answer

2.2290860617259248E+49

However, if I run the same command on a machine with .net framework 4.5.2 installed, then I get the answer

2.2290860617259246E+49

(i.e. the final digit changes)

I realise that this is broadly insignificant in pure numeric terms, but does anyone know of any changes that have been made in .net 4.5.2 that would explain the change?

(I don't prefer one result to the other, I am just interested to understand why it has changed)

If I output

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

then on 4.5.1 I get

113.62826122038274
4637696294982039780
2.2290860617259248E+49
5345351685623826106

and on 4.5.2 I get:

113.62826122038274
4637696294982039780
2.2290860617259246E+49
5345351685623826105

So for the exact same input, I get a different output (as can be seen from the bits so no roundtrip formatting is involved)

More details:

Compiled once using VS2015

Both machines that I am running the binaries on are 64bit

One has .net 4.5.1 installed, the other 4.5.2

Just for clarity: the string conversion is irrelevant... I get the change in results regardless of whether string conversion is involved. I mentioned that purely to demonstrate the change.


回答1:


Sigh, the mysteries of floating point math continue to stump programmers forever. It does not have anything to do with the framework version. The relevant setting is Project > Properties > Build tab.

Platform target = x86: 2.2290860617259248E+49
Platform target = AnyCPU or x64: 2.2290860617259246E+49

If you run the program on a 32-bit operating system then you always get the first result. Note that the roundtrip format is overspecified, it contains more digits than a double can store. Which is 15. Count them off, you get 16. This ensures that the binary representation of the double, the 1s and 0s are the same. The difference between the two values is the least significant bit in the mantissa.

The reason that the LSB is not the same is because the x86 jitter is encumbered with generating code for the FPU. Which has the very undesirable property of using more bits of precision than a double can store. 80 bits instead of 64. Theoretically to generate more accurate calculation results. Which it does, but rarely in a reproducible way. Small changes to the code can produce large changes in the calculation result. Just running the code with a debugger attached can change the result since that disables the optimizer.

Intel fixed this mistake with the SSE2 instruction set, completely replacing the floating point math instructions of the FPU. It does not use extra precision, a double always has 64 bits. With the highly desirable property that the calculation result now no longer depends on intermediate storage, it is now much more consistent. But less accurate.

That the x86 jitter uses FPU instructions is a historical accident. Released in 2002, there were not enough processors around that supported SSE2. That accident cannot be fixed anymore since it changes the observable behavior of a program. It was not a problem for the x64 jitter, a 64-bit processor is guaranteed to also support SSE2.

A 32-bit process uses the exp() function that uses FPU code. A 64-bit process uses the exp() function that uses SSE code. The result may be different by one LSB. But still accurate to 15 significant digits, it is 2.229086061725925E+49. All you can ever expect out of math with double.




回答2:


.NET uses the math lib functions from the CRT to do these calculations. The CRT used by .NET is often updated with each release, so you can expect the results to change between .NET releases, however, they will always be within the promised +/1ulp.




回答3:


I tripped over the same problem however I only get it after installing .Net 4.6.

The .Net 4.6 installation upgraded c:\windows\system32\msvcr120_clr0400.dll and c:\windows\syswow64\msvc120_clr0400.dll.

Before the install these DLLs had file properties->details: "File version 12.0.51689.34249" and "Product name Microsoft Visual Studio 12 CTP"

After installing .net 4.6 these had "File version 12.0.52512.0" and "Product name Microsoft Visual Studio 2013"

I tweaked my test to include your example and I see the same before/after numbers as you by flipping between versions of this DLL.

(Our test suite didn't show any changed results when running on .net versions 4, 4.5, 4.5.1, or 4.5.2 until these DLLs were updated.)




回答4:


To show how projects targeting different .NET versions affect the double conversion from string I built 4 projects targeting different versions all running on the same dev machine with .NET 4.6.

Here's the code

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

And here's the output

33.941408816725954 (.NET 4)

33.941408816725946 (.NET 4.5)

33.941408816725946 (.NET 4.5.2)

33.941408816725946 (.NET 4.6)

So, there was definitely a change in conversion method after .NET 4



来源:https://stackoverflow.com/questions/33153445/changes-to-math-exp-or-double-implementation-in-net-4-5-2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!