【问题标题】:Is it safe to check floating point values for equality to 0?检查浮点值是否等于 0 是否安全?
【发布时间】:2022-01-13 22:43:02
【问题描述】:

我知道您通常不能依赖 double 或 decimal 类型值之间的相等性,但我想知道 0 是否是一种特殊情况。

虽然我可以理解 0.00000000000001 和 0.00000000000002 之间的不精确性,但 0 本身似乎很难搞砸,因为它什么都不是。如果你什么都不精确,那它就不再是什么了。

但我对这个话题了解不多,所以不由我说。

double x = 0.0;
return (x == 0.0) ? true : false;

那总是返回 true 吗?

【问题讨论】:

  • 该代码中的三元运算符是多余的 :)
  • 大声笑你是对的。走我
  • 我不会这样做,因为你不知道 x 是如何被设置为零的。如果您仍想这样做,您可能需要舍入或取整 x 以摆脱 1e-12 或可能在末尾标记的类似内容。

标签: c# .net floating-point precision zero


【解决方案1】:

安全期望比较将返回 true 当且仅当 double 变量的值恰好为 0.0(在您的原始代码 sn-p 中是当然,情况)。这与== 运算符的语义一致。 a == b 表示“a 等于 b”。

不安全(因为它不正确)期望某些计算的结果在双精度(或更一般地,浮点)算术中为零每当纯数学中相同计算的结果为零时。这是因为当计算落地时,会出现浮点精度误差——这个概念在数学中的实数算术中是不存在的。

【讨论】:

    【解决方案2】:

    如果您需要进行大量“相等”比较,最好在 .NET 3.5 中编写一些辅助函数或扩展方法进行比较:

    public static bool AlmostEquals(this double double1, double double2, double precision)
    {
        return (Math.Abs(double1 - double2) <= precision);
    }
    

    这可以通过以下方式使用:

    double d1 = 10.0 * .1;
    bool equals = d1.AlmostEquals(0.0, 0.0000001);
    

    【讨论】:

    • 通过比较 double1 和 double2,您可能会遇到减法取消错误,以防这些数字的值非常接近。我会删除 Math.Abs​​ 并单独检查每个分支 d1 >= d2 - e 和 d1
    • "因为 Epsilon 定义了一个范围接近于零的正值的最小表达式,所以两个相似值之间的差异幅度必须大于 Epsilon。通常,它比 Epsilon 大很多倍。因为其中,我们建议您在比较 Double 值是否相等时不要使用 Epsilon。” - msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx
    【解决方案3】:

    对于您的简单示例,该测试是可以的。但是这个呢:

    bool b = ( 10.0 * .1 - 1.0 == 0.0 );
    

    请记住,.1 是二进制重复的十进制数,不能精确表示,这与尝试将 1/3 写为以 10 为底的十进制数相同。现在将其与以下代码进行比较:

    double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
    bool b = ( d1 - 1.0 == 0.0 );
    

    我会让您进行测试以查看实际结果:您更有可能以这种方式记住它。

    【讨论】:

    • 实际上,由于某种原因返回 true(至少在 LINQPad 中)。
    • 你说的“.1问题”是什么?
    • “请记住,.1 是二进制的重复小数,不能精确表示。”谢谢你提醒我一开始我不知道的事情?
    【解决方案4】:

    来自Double.Equals 的 MSDN 条目:

    比较精确

    Equals 方法应该与 小心,因为两个显然 等效值可能不相等 由于两者的精度不同 价值观。以下示例报告 Double 值 .3333 和 通过将 1 除以 3 返回的双精度是 不平等。

    ...

    与其比较是否相等, 一种推荐的技术涉及 定义一个可接受的余量 两个值之间的差异(例如 值之一的 0.01%)。如果 差值的绝对值 两个值之间小于或 等于那个边距,差 很可能是由于存在差异 精度,因此,值 很可能是相等的。下列 示例使用此技术进行比较 .33333 和 1/3,两个 Double 值 前面的代码示例发现 是不平等的。

    另外,请参阅Double.Epsilon

    【讨论】:

    • 不完全等价的值也可以比较为相等。人们会期望如果x.Equals(y),那么(1/x).Equals(1/y),但如果x0 并且y1/Double.NegativeInfinity,情况并非如此。这些值声明为相等,即使它们的倒数不相等。
    • @supercat:它们是等价的。而且他们没有倒数。您可以使用x = 0y = 0 再次运行测试,您仍然会发现1/x != 1/y
    • @BenVoigt:使用xy 作为类型double?您如何比较结果以使它们报告不相等?请注意,1/0.0 不是 NaN。
    • @supercat:好的,这是 IEEE-754 出错的地方之一。 (首先,1.0/0.0 不是应有的 NaN,因为限制不是唯一的。其次,无穷大相互比较,不考虑无穷大的度数)
    • @BenVoigt:如果 0 是两个非常小的数字相乘的结果,那么将 1.0 除以 1.0 应该会产生一个值,该值大于任何具有相同符号的小数,小于如果其中一个小数字有相反的符号,则比任何数字都好。恕我直言,如果 IEEE-754 有一个无符号零,但正负无穷小,它会更好。
    【解决方案5】:

    当您比较不同类型的浮点值实现时会出现问题,例如将浮点数与双精度数进行比较。但是同类型的应该问题不大。

    float f = 0.1F;
    bool b1 = (f == 0.1); //returns false
    bool b2 = (f == 0.1F); //returns true
    

    问题是,程序员有时会忘记隐式类型转换(double to float)是为了比较而发生的,这会导致错误。

    【讨论】:

      【解决方案6】:

      如果数字直接分配给浮点数或双精度数,则可以安全地测试零或任何可以用 53 位表示双精度数或 24 位浮点数的整数。

      或者换一种说法,你总是可以将整数值赋给一个双精度值,然后将双精度值与同一个整数进行比较,并保证它是相等的。

      您也可以从分配一个整数开始,然后通过坚持加、减或乘以整数来继续进行简单的比较(假设结果小于 24 位浮点数和 53 位双精度数) .因此,您可以在某些受控条件下将浮点数和双精度数视为整数。

      【讨论】:

      • 我总体上同意您的陈述(并赞成),但我相信这真的取决于是否使用 IEEE 754 浮点实现。而且我相信每台“现代”计算机都使用 IEEE 754,至少用于存储浮点数(存在不同的奇怪舍入规则)。
      【解决方案7】:

      不,不行。所谓的非规格化值(次正规),当比较等于 0.0 时,会比较为假(非零),但在等式中使用时会被标准化(变为 0.0)。因此,将其用作避免被零除的机制是不安全的。相反,添加 1.0 并与 1.0 进行比较。这将确保所有次正规都被视为零。

      【讨论】:

      • 次正规也称为非正规
      • 次正规在使用时不会等于零,尽管它们可能会或可能不会产生相同的结果,具体取决于确切的操作。
      【解决方案8】:

      试试这个,你会发现 == 对于 double/float 是不可靠的。
      double d = 0.1 + 0.2; bool b = d == 0.3;

      这是来自 Quora 的answer

      【讨论】:

        【解决方案9】:

        实际上,我认为最好使用以下代码将 double 值与 0.0 进行比较:

        double x = 0.0;
        return (Math.Abs(x) < double.Epsilon) ? true : false;
        

        浮动也一样:

        float x = 0.0f;
        return (Math.Abs(x) < float.Epsilon) ? true : false;
        

        【讨论】:

        • 没有。来自 double.Epsilon 的文档:“如果您创建一个自定义算法来确定两个浮点数是否可以被视为相等,则必须使用大于 Epsilon 常数的值来确定可接受的绝对差值两个值被认为是相等的。(通常,差异幅度是 Epsilon 的许多倍。)"
        • @AlastairMaw 这适用于检查两个任意大小的双精度数是否相等。为了检查相等性为零,double.Epsilon 很好。
        • 不,它不是。您通过某种计算得出的值很可能与零相差很多倍,但仍应视为零。你不会神奇地从某个地方在中间结果中获得一大堆额外的精度,只是因为它恰好接近于零。
        • 例如:(1.0/5.0 + 1.0/5.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0)
        • 那么,如果 double.Epsilon 不行,推荐的精度是多少? 10倍的epsilon可以吗? 100 次?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多