gpt4 book ai didi

.net - Microsoft ACE 驱动程序更改了程序其余部分的浮点精度

转载 作者:太空狗 更新时间:2023-10-29 23:50:32 25 4
gpt4 key购买 nike

我遇到一个问题,在使用 Microsoft ACE driver 之后,某些计算的结果似乎发生了变化。打开 Excel 电子表格。

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

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

1,59142713593566
1,59142713593566
1,59142713593495

请注意最后 3 位小数的差异。这看起来差别不大,但在我们的生产代码中,计算很复杂,结果差别很大。

如果我使用 JET 驱动程序而不是 ACE 驱动程序,这没有什么区别。如果我将类型从 double 更改为十进制,错误就会消失。但这不是我们生产代码中的一个选项。

我在 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
}
}
}

最佳答案

这在技术上是可行的,非托管代码可能会修改 FPU 控制字并更改其计算方式。众所周知的麻烦制造者是使用 Borland 工具编译的 DLL,它们的运行时支持代码会揭露可能导致托管代码崩溃的异常。而 DirectX,它以修补 FPU 控制字而闻名,以使用 double 进行计算,以 float 的形式执行以加速图形数学。

此处出现的 FPU 控制字更改的特定类型是舍入模式,当 FPU 需要将 80 位精度的内部寄存器值写入 64 位内存位置时使用该模式。它有 4 个选项来进行转换:向上舍入、向下舍入、截断和四舍五入(银行四舍五入)。差异非常小,但您确实努力迅速积累它们。如果您的数值模型不稳定,那么您肯定会看到最终结果有所不同。这不会使它或多或少准确,只是有所不同。

托管代码对执行此操作的代码毫无防备,您无法直接访问 FPU 控制字。它需要编写汇编代码。你有一个可用的技巧,高度未记录但非常有效。 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) {}
}

请注意,这很昂贵,因此请尽量少用。当您调试代码时,它是一个主要的 pita,因此您可能希望在调试版本中禁用它。

我应该提一个替代方案,您可以调用 msvcrt.dll 中的 _fpreset() 函数。但是,如果您在同时执行 float 学运算的方法内部使用它是有风险的,抖动优化器不知道此函数会抖动地垫。您需要彻底测试发布版本:

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

请记住,这不会以任何方式使您的计算结果更加准确。只是不同。就像在没有调试器的情况下运行代码的发布版本会产生与调试版本不同的结果。 Release 构建代码将不那么频繁地执行这种舍入,因为抖动优化器努力将 FPU 中的中间结果保持在 80 位精度。生成与调试版本不同但实际上更准确的结果。给或拿。这种 80 位中间格式是英特尔数十亿美元的错误,在 SSE2 指令集中没有重复。

关于.net - Microsoft ACE 驱动程序更改了程序其余部分的浮点精度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31296402/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com