【问题标题】:Double precision problems on .NET.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”(双精度),所以如果我改变它,我可以有更高的精度。

【问题讨论】:

    标签: c# .net math double


    【解决方案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# specificationthis article

            Java 有 strictfp 关键字,C++ 有编译器开关。我想念 .NET 中的那个选项

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-06-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多