关于.NET上的双精度Double问题

Double precision problems on .NET

我有一个简单的C函数:

1
2
3
4
public static double Floor(double value, double step)
{
    return Math.Floor(value / step) * step;
}

它计算的是大于或等于"value"的数字,即"step"的倍数。但它缺乏精确性,如以下测试所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[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"(double),所以如果我更改了这个后缀,就可以获得更高的精度。


实际上,我希望他们没有实现float和double的==操作符。询问double或float是否等于任何其他值几乎总是错误的。


计算机上的浮点运算不是一门精确的科学。

如果要精确到预定的小数位数,请使用十进制而不是双精度,或者接受较小的间隔。


如果省略了所有的F后缀(即-12.1而不是-12.1F),您将得到更多的相等数字。由于F,您的常量(尤其是预期值)现在是浮动的。如果你是故意这样做的,请解释。

但对于其余的问题,我同意其他关于比较双精度值或浮点值是否相等的答案,这只是不可靠。


如果需要精度,请使用System.Decimal。如果需要速度,请使用System.Double(或System.Float)。浮点数不是"无限精度"数字,因此断言相等必须包含一个公差。只要你的数字有一个合理的有效位数,这是可以的。

  • 如果你想对非常大和非常小的数字进行数学运算,不要使用浮点或双精度。
  • 如果需要无限精度,不要使用浮点或双精度。
  • 如果要聚合大量的值,请不要使用float或double(错误会自行复合)。
  • 如果您需要速度和大小,请使用浮动或双精度。

有关精度如何影响数学运算结果的详细分析,请参阅此答案(我也是)。


http://en.wikipedia.org/wiki/floating_point准确性问题

For example, the non-representability of 0.1 and 0.01 (in binary) means that the result of attempting to square 0.1 is neither 0.01 nor the representable number closest to it.

只有在需要机器对数字系统进行解释(二进制)时才使用浮点。你不能代表10美分。


检查这个问题的答案:检查浮点值是否等于0安全?

真的,只要检查"在……的容忍范围内"


浮点数和双精度数不能准确存储所有数字。这是IEEE浮点系统的一个限制。为了获得可靠的精度,您需要使用更高级的数学库。

如果您不需要精度超过某一点,那么也许十进制会更好地为您工作。它比双精度高。


有时结果比您从严格的标准期望的更精确:fp ieee 754。这是因为硬件使用更多的位进行计算。参见C规范和本文

Java具有StuttFP关键字,C++具有编译器开关。我错过了.NET中的那个选项


对于类似的问题,我最终使用了以下实现,这似乎成功了我的大多数测试用例(高达5位精度):

1
2
3
4
5
6
7
8
9
10
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));
}