我已经编写了一个可以从 VBA 调用的 C++.NET 组件。该组件计算了一个非常大的数字数组(给定一些输入)并将该数组写入二进制文件。
我注意到,如果我使用通过 VBA 调用组件的完全相同的设置生成文件,我每次都会得到不同的结果。在十六进制编辑器中打开二进制文件时,我可以看到字节数的差异。但是,如果我做同样的事情,但通过在 C++.NET 或 C#.NET 可执行文件中调用组件,我根本看不到任何差异——使用相同设置生成的文件每次以这种方式生成时都是相同的。
差异很小,只是 float 差异。但是,我想知道为什么会这样。为什么通过 VBA 而不是通过 C++.NET 或 C#.NET 调用它时会出现差异?
郑重声明 - 我在此处的 64 位计算机上运行 32 位 Excel(用于 VBA)(如果这有任何区别...)
如有任何想法,我们将不胜感激。
最佳答案
@Terry - 我想我能够证明这件事正在发生。首先让我解释一下问题是什么(或者至少我认为是对问题的合理解释)。
为什么 C# 可执行文件给出的结果与 Excel 不同?
任何应用程序都可以将浮点状态设置为它需要的任何值。事实上,这是一种很好的做法,因此您可以确保应用程序的特定精度(如果重要的话)。
当您从 C# 可执行文件运行时,使用的浮点设置是编译器决定的。但是,当您在 Excel 中运行它时,Excel 会将浮点设置更改为它需要的任何值,这可能与可执行文件不同。这就是您与众不同的原因。
为什么每次在 Excel 中运行可能会给出不同的结果?
Excel 根据需要更改浮点设置。但是,它只在需要使用的线程上执行此操作。因此,如果 Excel 需要 N 个线程,它将在这 N 个线程上设置浮点设置。任何新生成的线程上的浮点设置都不会从“主”线程复制。因此,如果您的加载项中的进程需要 M > N 个线程,则将产生 M-N 个线程,这些线程使用与 Excel 设置的 N 不同的浮点设置生成。所以现在你有 M 个线程在运行,它们有两组不同的浮点设置。由于并行编程中使用的线程是不确定的,因此每次在 Excel 中运行时可能会得到不同的结果。
我是如何解决的?
我通过在用于确保它始终相同的每个线程上显式设置浮点设置来解决这个问题。我介绍了以下例程。它是用 C++ 编写的,但您也应该能够让它在 C# 中运行。
[DllImport("msvcrt.dll")] int _controlfp(int IN_New, int IN_Mask);
void FixFPU()
{
_controlfp(_MCW_DN, _DN_SAVE);
_controlfp(_MCW_EM, _EM_INVALID);
_controlfp(_MCW_RC, _RC_CHOP);
_controlfp(_MCW_PC, _PC_53);
}
我在 Parallel.For 循环中调用了这个例程。这确保了 Parallel.For 循环使用的每个线程都具有相同的浮点设置。通过显式指定浮点状态,您可以在您的例程从加载项运行时覆盖这些线程上的 Excel 浮点设置。我还在我的进程开始时调用了这个例程,以确保在开始时使用的单个线程也运行相同的浮点设置。这可确保在进入 Parallel.For 循环之前完成的任何计算都以与 Parallel.For 循环内的计算相同的精度执行。
这是该例程中的最后一条语句,因为它设置了精度,所以差异最大。
希望本文能帮助您解决问题。
关于VBA 浮点差异中的 C++ .NET COM 组件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33667555/