I have a system that starts producing incorrect values after running for several hours. I reproduced it while under a debugger and found that the problem is System.Math.Round begins returning incorrect values.
I have two instances of the same version of Visual Studio, running side by side on the same machine, with the same project, the same code, at the same part of the stack trace -- everything is identical -- except one has been running for hours and has begun failing, the other hasn't.
I execute a constant expression in their respective Immediate windows, and get different values.
In the good run:

In the bad run:

This small discrepancy has significant implications for my app.
.NET version, dumped from the running code:
System.Environment.Version => 4.0.30319.42000
(typeof(string).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false))[0] => 4.8.4644.0
Has anyone seen this before? Is it a known bug? Is there a way I can workaround it?
EDIT: @Kit doesn't trust the Immediate Window so here's a bit more info. I showed the Immediate Window result because it lets you see that the same constant expression is producing different results from Math.Round. Below is the line in the actual code where it's relevant, and you can see that Math.Round is producing the wrong value in the actual code, too:

@HansPassant identified the problem in the comments:
Hans: "The bitness of your process matters a lot, I'll guess at 32-bit (aka x86). In which case Round() is implemented by the FRNDINT fpu instruction. Whose behavior is affected by the rounding mode selected in the FPU control register. Use Debug > Windows > Registers, right-click that tool window and tick "Floating point". A .NET program must always be operating with CTRL = 027F. Step through your program and when you see it change then you found the Evil Code."
This is exactly right. It's a 32 bit .NET app, which apparently means it uses the FPU rather than SSE instructions. These were the floating point registers in the good instance vs the bad:


Hans: "067F means it changed from 'round to nearest' to 'round down'."
This code base only needed to run successfully once before being retired, so I didn't try to find which unmanaged dependency was changing this flag and when. Instead, I just added something like this to my app and called it before doing important bits of work:
[DllImport("msvcrt.dll")]
private static extern int _controlfp(int IN_New, int IN_Mask);
public static void VerifyFpuRoundingMode()
{
const int _MCW_RC = 0x00000300;
const int _RC_NEAR = 0x00000000;
int ctrl = _controlfp(0, 0);
if ((ctrl & _MCW_RC) != 0)
{
_controlfp(_RC_NEAR, _MCW_RC);
}
}
This fixed our rounding issues.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With