C++和C#中float和double的区别

8
我们正在将一个C++数学库转换为C#。该库混合使用浮点数和双精度浮点数(有时进行类型转换),我们正在尝试做同样的事情,以便在C#中获得与C++中完全相同的结果,但这证明非常困难,甚至是不可能的。
我认为问题可能是以下一个或多个,但我不是专家:
  1. 将浮点数转换为双精度浮点数,以及将双精度浮点数转换为浮点数会导致不可预测的结果,并且在C++和C#中处理方式不同。
  2. C++和C#处理浮点精度的方式不同,无法相互模拟。
  3. 在.NET中有某些设置可以使其像C++一样运行,但我找不到它们(两者都是32位)。
有人能向我解释可能出现的问题,并为我提供一些微软权威文档的链接,帮助我解释情况和差异的原因吗?
编辑: 我们正在使用VC6和.NET4.0。
由于保密协议,我无法提供计算示例,但我可以展示一些数字来说明差异...这些数字本身可能没有什么用处。
 8.085004000000000 (C#) vs. 
 8.084980000000000 (C++)    

 8.848165000000000 (C#) vs. 
 8.848170000000000 (C++)   

 0.015263214111328 (C#) vs. 
 0.015263900756836 (C++)  

需要注意的是,这些数字包括复合问题。这些都是计算结果。

你能提供一个具体的例子,说明你得到了不同的结果吗? - AndersK
我认为这更多与硬件有关,而不是语言(虽然我可能错了)。浮点寄存器比内存位置大(在英特尔上)。因此,如果整个表达式在寄存器中计算,则会得到一个结果,而如果临时值存储在内存中,则会失去一些精度并获得略微不同的结果。此外,C++编译器可能正在使用通用浮点操作,而.NET JIT编译器可以利用一组更具体和准确的架构指令。 - Martin York
@Martin 谢谢。那么,是期望它是100%准确的,还是期望两者之间有差异? - Jason
3
浮点数算术中没有一件事情是100%准确的。 - Martin York
1个回答

10

C++允许程序在临时结果中保留比子表达式类型更高的精度。可能发生的一件事是,中间表达式(或其中未指定的子集)会计算为扩展80位浮点数。

我会惊讶地发现如果C#也适用于此,但即使适用,C#编译器也不必选择与80位扩展浮点数相同的子集表达式进行计算。编辑:请参见下面Eric的评论。

更多细节

同样的中间精度问题的另一个实例是当编译器对源代码中的乘法后跟加法使用fmadd指令时(如果目标体系结构具有它,例如PowerPC)。fmadd指令精确计算其中间结果,而正常的加法将四舍五入中间结果。

要防止C++编译器这样做,您只需要使用易失变量将浮点运算写成三地址代码以获得中间结果。如果该转换改变了C++程序的结果,则意味着上述问题正在发挥作用。但是,那么您已经更改了C++结果。可能没有简单的方法可以让C#在不阅读生成的程序集的情况下获得完全相同的旧C++结果。

如果您使用的是稍微有些陈旧的C++编译器,则还可能将浮点运算优化为可结合性,但实际上它们却不可结合。此问题无法解决。三地址代码转换再次可以防止编译器应用它,但同样没有简单的方法可以让C#编译器复制旧的C++结果。


谢谢。C++是VC6,而使用C#的是.NET 4.0。 - Jason
2
关于您的第二段,我建议您参考 C# 规范的 4.1.6 节,其中开始描述:浮点运算可能使用比操作结果类型更高的精度进行。例如,某些硬件架构支持具有比 double 类型更大范围和精度的“扩展”或“长双精度”浮点类型,并隐式地使用此更高精度类型执行所有浮点操作。... 请参考规范获取更多细节。 - Eric Lippert
@Eric 谢谢你提供的信息。我很惊讶微软在规范中包含了这个警告,因为我认为大多数处理器在开始开发.NET时都已经具备了SSE指令集和“真实”的双精度浮点数。例如,Java对浮点运算有更多的保证(例如,Kahan共同撰写了一篇关于此主题的文章,指出这种严格性阻止了某些优化,因此被错误地应用。我从来不知道他会站在哪一边 :) - Pascal Cuoq
1
记住,.NET可以在许多不同的平台上运行,从嵌入式设备到高端服务器,再到运行Silverlight的Mac Web浏览器。实际上,处理器没有共同的分母。欲知更多问题,请参见https://dev59.com/zkzSa4cB1Zd3GeqPkjvl。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接