10

比较一些 C 代码和我试图替换它的 F#,我观察到最终结果存在一些差异。

备份代码,我发现即使存在差异 - 尽管很小。

代码首先从文件中读取数据。第一个数字的结果不同。例如,在 F# 中(更易于编写脚本):

let a = 71.9497985840
printfn "%.20f" a

我得到了预期的(对我而言)输出71.94979858400000000000

但在 C 中:

a =  71.9497985840;
fprintf (stderr, "%.20f\n", a);

打印出来71.94979858400000700000

那个 7 是从哪里来的?

差异很小,但它困扰着我,因为我不知道为什么。(这也让我很困扰,因为它让我更难追踪我的两个版本的代码在哪里分歧)

4

5 回答 5

7

这是印刷的区别。将该值转换为 IEEE754double产生

Prelude Text.FShow.RealFloat> FD 71.9497985840
71.94979858400000694018672220408916473388671875

但该表示71.949798584足以将该数字与其邻居区分开来。C,当被要求以小数点后 20 位的精度打印时,将值正确舍入为所需的位数,显然 F# 使用最短的唯一确定表示并用所需的 0 数填充它,就像 Haskell 一样.

于 2012-06-05T12:16:52.393 回答
3

这只是不同的四舍五入。数字是相同的(至少根据 CPython):

>>> '%.44f' % 71.94979858400000000000
'71.94979858400000694018672220408916473388671875'
>>> '%.44f' % 71.94979858400000700000
'71.94979858400000694018672220408916473388671875'
于 2012-06-05T12:17:17.813 回答
3

区别在于 .NET System.Double.ToString() 方法,该方法将双精度数转换为字符串。您可以通过下载 SSCLI20 中提供的 CLR 源代码来查看相关代码。转换由 clr/src/vm/comnumber.cpp, COMNumber::FormatDouble() 函数完成。看起来像这样,代码中的注释最能描述正在发生的事情:

//In order to give numbers that are both friendly to display and round-trippable,
//we parse the number using 15 digits and then determine if it round trips to the same
//value.  If it does, we convert that NUMBER to a string, otherwise we reparse using 17 digits
//and display that.

C 运行时库没有该功能。

于 2012-06-05T21:16:02.373 回答
0

其他答案充分解释了问题的根源(双精度和舍入)。

如果您的数字通常具有中等大小并且小数精度非常重要(比计算速度更重要),那么也许可以考虑使用 .NETdecimal格式。这为您提供了 28-29 个精确的小数位精度,而没有像 double 那样的小数二进制舍入错误。限制是范围更小(没有大指数!)。

http://msdn.microsoft.com/en-us/library/364x0z75%28v=vs.100%29.aspx

于 2012-06-05T12:29:05.923 回答
0

任何绊倒此的人的更多信息。

使用此处找到的代码位,我已经确认(我相信)底层二进制表示(至少对于这个特定数字)是相同的断言。

这是代码示例 - 请注意“将零乘以零”以消除负零 - 这在转换为长时很难看。

//(C# this time)
var d = 71.9497985840;   //or other incoming double value
if(d == 0) d = d * d;    //for negative zero
var longval = System.BitConverter.DoubleToInt64Bits(d); // = 4634763433907061836

在 C 中:

double d;
long long a;
d = 71.9497985840;      //or other incoming double value
if(d == 0) d = d * d;   //for negative zero
a = *(long long*)&d;    //= 4634763433907061836

更新- 我跟进,发现在矩阵求逆过程中引入了差异,因为每个系统调用不同的库,以不同的方式实现求逆......

于 2012-06-06T11:46:33.147 回答