C双打是否与.NET双打不同?

时间:2012-06-05 12:09:22

标签: .net c floating-point

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

重新编写代码,我发现即使存在差异 - 尽管很小。

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

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

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

但在C:

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

打印出71.94979858400000700000

那7来自哪里?

差别很小,但令我困扰,因为我不知道为什么。 (这也困扰我,因为它更难以追踪我的两个版本的代码分歧的位置)

5 个答案:

答案 0 :(得分:7)

这是印刷中的差异。将该值转换为IEEE754 double会产生

Prelude Text.FShow.RealFloat> FD 71.9497985840
71.94979858400000694018672220408916473388671875

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

答案 1 :(得分:3)

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

>>> '%.44f' % 71.94979858400000000000
'71.94979858400000694018672220408916473388671875'
>>> '%.44f' % 71.94979858400000700000
'71.94979858400000694018672220408916473388671875'

答案 2 :(得分:3)

这是.NET System.Double.ToString()方法的不同之处,即将double转换为字符串的方法。您可以通过下载SSCLI20中提供的CLR源来查看相关代码。转换由clr / src / vm / comnumber.cpp,COMNumber :: FormatDouble()函数完成。看起来like this,代码中的注释最能描述正在发生的事情:

//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运行时库没有该功能。

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:0)

对于任何绊脚石的人的进一步信息。

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

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

//(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

更新 - 我接着,发现在矩阵求逆期间引入了差异,因为每个系统都调用了不同的库,以不同的方式实现反转...