Microsoft ACE驱动程序在我的程序的其余部分更改了浮点精度

在使用Microsoft ACE驱动程序打开Excel电子表格之后,我遇到了某些计算结果更改的问题。

下面的代码重现了这个问题。

前两个DoCalculation调用产生相同的结果。 然后我调用OpenSpreadSheet函数,它使用ACE驱动程序打开和closuresExcel 2003电子表格。 您不希望OpenSpreadSheet对最后一次调用OpenSpreadSheet有任何影响,但事实certificate结果实际上发生了变化。 这是程序生成的输出:

 1,59142713593566 1,59142713593566 1,59142713593495 

请注意最近3位小数的差异。 这似乎没有什么太大的差别,但是在我们的生产代码中,计算是复杂的,所得到的差异是相当大的。

如果我使用JET驱动程序而不是ACE驱动程序,则没有区别。 如果我将types从double改为decimal,错误就会消失。 但是在我们的生产代码中这不是一个选项。

我在Windows 7 64位上运行,程序集是为.NET 4.5 x86编译的。 我们正在运行32位Office,因此使用64位ACE驱动程序不是一个选项。

有谁知道为什么会发生这种情况,我该如何解决?

以下代码重现了我的问题:

 static void Main(string[] args) { DoCalculation(); DoCalculation(); OpenSpreadSheet(); DoCalculation(); } static void DoCalculation() { // Multiply two randomly chosen number 10.000 times. var d1 = 1.0003123132; var d3 = 0.999734234; double res = 1; for (int i = 0; i < 10000; i++) { res *= d1 * d3; } Console.WriteLine(res); } public static void OpenSpreadSheet() { var cn = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;data source=c:\temp\workbook1.xls;Extended Properties=Excel 8.0"); var cmd = new OleDbCommand("SELECT [Column1] FROM [Sheet1$]", cn); cn.Open(); using (cn) { using (OleDbDataReader reader = cmd.ExecuteReader()) { // Do nothing } } } 

这在技术上是可行的,非托pipe代码可能会修补FPU控制字,并改变它的计算方式。 众所周知的麻烦制造者是使用Borland工具编译的DLL,他们的运行时支持代码没有掩盖可能使托pipe代码崩溃的exception。 和DirectX一样,它是用FPU控制字修补来获得 精度浮点运算来加速graphics运算的。

看起来在这里出现的FPU控制字变化的具体types是舍入模式,当需要将具有80位精度的内部寄存器值写入到64位存储单元时由FPU使用。 它有四个选项来进行转换:四舍五入,四舍五入,四舍五入(银行家四舍五入)。 非常小的差异,但你做了努力迅速积累他们。 如果你的数值模型不稳定,那么你肯定会看到最终结果的差异。 这并没有使它更加准确,只是不同而已。

托pipe代码对于执行此操作的代码来说是非常不自然的,您不能直接访问FPU控制字。 它需要编写汇编代码。 你有一个可用的技巧,非常无证,但非常有效。 当处理exception时,CLR将重置 FPU。 所以你可以这样做:

 public static void ResetMathProcessor() { if (IntPtr.Size != 4) return; // No need in 64-bit code, it uses SSE try { throw new Exception("Please ignore, resetting the FPU"); } catch (Exception ex) {} } 

要小心,这是昂贵的,所以尽可能less使用。 当你debugging代码的时候,它是一个主要的皮塔,所以你可能想要在Debug版本中禁用它。

我应该提到一个替代方法,你可以在msvcrt.dll中调用_fpreset()函数。 然而,如果你在一个也执行浮点math的方法中使用它,那么抖动优化器不知道这个函数会跳动地板垫。 您需要彻底testingRelease版本:

  [System.Runtime.InteropServices.DllImport("msvcrt.dll")] public static extern void _fpreset(); 

请记住,这不会使您的计算结果更准确。 只是不同。 就像在没有debugging器的情况下运行代码的Release版本一样,将产生与Debug版本不同的结果。 由于抖动优化器努力以80位精度保持FPU内部的中间结果,因此发布构build代码将不太经常地执行这种舍入。 从Debug版本生成一个不同的结果,但实际上更准确。 给或拿。 这个80位中间格式是英特尔的十亿美元错误,在SSE2指令集中没有重复。