.NET 上的双精度问题
Posted
技术标签:
【中文标题】.NET 上的双精度问题【英文标题】:Double precision problems on .NET 【发布时间】:2010-10-08 16:38:14 【问题描述】:我有一个简单的 C# 函数:
public static double Floor(double value, double step)
return Math.Floor(value / step) * step;
即计算较大的数,小于等于“value”,即“step”的倍数。但它缺乏精确度,如以下测试所示:
[TestMethod()]
public void FloorTest()
int decimals = 6;
double value = 5F;
double step = 2F;
double expected = 4F;
double actual = Class.Floor(value, step);
Assert.AreEqual(expected, actual);
value = -11.5F;
step = 1.1F;
expected = -12.1F;
actual = Class.Floor(value, step);
Assert.AreEqual(Math.Round(expected, decimals),Math.Round(actual, decimals));
Assert.AreEqual(expected, actual);
第一个和第二个断言都可以,但第三个失败,因为结果只相等到小数点后 6 位。这是为什么?有什么办法可以改正吗?
更新如果我调试测试,我发现直到小数点后第 8 位而不是第 6 位的值都是相等的,这可能是因为 Math.Round 引入了一些不精确性。
注意在我的测试代码中,我写了“F”后缀(显式浮点常量),我的意思是“D”(双精度),所以如果我改变它,我可以有更高的精度。
【问题讨论】:
【参考方案1】:我实际上有点希望他们没有为浮点数和双精度数实现 == 运算符。询问 double 或 float 是否等于任何其他值几乎总是错误的做法。
【讨论】:
是的!是的!是的!我已经说了一段时间了。这就像整个 0.999... = 1.0 问题。 (1.0 - 0.000... = 1.0)。浮点数与整数完全不同。【参考方案2】:计算机上的浮点运算不是精确科学:)。
如果您想要精确到预定义的小数位数,请使用 Decimal 而不是 double 或接受较小的间隔。
【讨论】:
这是一门符合 IEEE 定义的有效数字位数的精确科学。 为了强化上述观点:浮点数是精确的。您想要的数字可能无法表示为 IEEE 浮点数,这意味着您必须将该数字别名为下一个最接近的数字,这会导致错误,但这并不意味着您可以 表示其中有错误。 此外,小数可能会遇到与双精度相同的问题,因为它们也是浮点数。他们可能会遇到相同的表示问题,但这并不出人意料,因为它们的底数为 10,而底数为 2,我们习惯于处理以 10 为底的表示问题(例如 1/3 是 0.333333.. . 以 10 为底)。【参考方案3】:如果您想要精确,请使用 System.Decimal。如果您想要速度,请使用 System.Double(或 System.Float)。浮点数不是“无限精度”数,因此断言相等必须包含容差。只要您的数字具有合理数量的有效数字,就可以了。
如果您要对非常大和非常小的数字进行数学运算,请不要使用浮点数或双精度数。 如果您需要无限精度,请不要使用 float 或 double。 如果要聚合大量值,请不要使用 float 或 double(错误会自行加剧)。 如果您需要速度和大小,请使用 float 或 double。请参阅this 回答(也是我的回答),详细分析精度如何影响数学运算的结果。
【讨论】:
没有“无限精度”。 float/double 的问题在于它们精确到二进制数字的数量而不是十进制数字的数量。 有无限精度这种东西。整数是无限精确的类型。它在数学运算期间不会失去精度。可以实现无限精确(尽管效率很低)的十进制类型,但它在 .Net 中并不是“开箱即用”的。 迈克尔,你的评论很愚蠢。以这种方式定义,因此整数可以说具有“无限精度”,只是因为它们在数学运算期间不会改变,那么每个数字都有“无限精度”,(浮点数、双精度和小数在数学运算期间也不会改变) .以这种方式定义,这个概念就失去了所有意义。哎呀,即使只是一个只有两个值的符号(正或负)也具有“无限精度”,因为根据您的定义,它在使用它的任何数学运算中都不会丢失精度。【参考方案4】:如果您省略所有 F 后缀(即 -12.1
而不是 -12.1F
),您将获得更多位数的相等性。由于F
,您的常量(尤其是预期值)现在是浮动的。如果您是故意这样做的,请解释一下。
但对于其余部分,我同意其他关于比较双精度或浮点值是否相等的答案,它只是不可靠。
【讨论】:
但大写的 F 表示双精度,而不是浮点数,对吗?是表示浮动的小写 f。 不,我刚刚检查过:float x=1.0;给出错误,float x=1.0F;没关系。 F 不区分大小写。 并在 Ecmea334 中查找:双精度为 1.0D,十进制为 1.0M。 不幸的是,Java 的创建者在其扩大/缩小的转换规则中使用了向后逻辑,用于 float 和 double 向后,而 .net 紧随其后。如果对于源集中的每个值在目标中都存在一个可能的值(可能由源集中的其他值共享),则应考虑从更具体的类型到不太具体的类型的转换扩大。0.1f
的值并不意味着“13421773/134217728”。它的意思是“介于 13421772.5/134217728 和 13421773.5/134217728 之间的东西”。许多double
值可以符合该描述;随意...
...选择范围中间的确切值对于编译器来说可能是一件方便的事情,但它应该被认为足够危险,编译器不应该在没有警告。相比之下,将双精度转换为单精度是安全的;转换后,该值的具体程度不如转换前,但该值将精确到其声称的精度(单到双转换后不会出现这种情况)。【参考方案5】:
http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems
例如,0.1 和 0.01(二进制)的不可表示性意味着尝试将 0.1 平方的结果既不是 0.01,也不是最接近它的可表示数。
如果您想要机器对数字系统的解释(二进制),请仅使用浮点数。你不能代表 10 美分。
【讨论】:
【参考方案6】:查看此问题的答案:Is it safe to check floating point values for equality to 0?
真的,只需检查“在...的容差范围内”
【讨论】:
【参考方案7】:浮点数和双精度数无法准确存储所有数字。这是 IEEE 浮点系统的限制。为了获得忠实的精度,您需要使用更高级的数学库。
如果您不需要超过某个点的精度,那么十进制可能对您更有效。它具有比 double 更高的精度。
【讨论】:
【参考方案8】:对于类似的问题,我最终使用了以下实现,这似乎在我的大部分测试用例中都成功了(最高 5 位精度):
public static double roundValue(double rawValue, double valueTick)
if (valueTick <= 0.0) return 0.0;
Decimal val = new Decimal(rawValue);
Decimal step = new Decimal(valueTick);
Decimal modulo = Decimal.Round(Decimal.Divide(val,step));
return Decimal.ToDouble(Decimal.Multiply(modulo, step));
【讨论】:
【参考方案9】:有时结果比您对 strict:FP IEEE 754 所期望的更精确。 那是因为硬件使用更多位进行计算。 见C# specification和this article
Java 有 strictfp 关键字,C++ 有编译器开关。我想念 .NET 中的那个选项
【讨论】:
以上是关于.NET 上的双精度问题的主要内容,如果未能解决你的问题,请参考以下文章
如何格式化数据库中的双精度数据并在 Android 上使用逗号作为小数分隔符设置到 Textview