【问题标题】:What's wrong with using == to compare floats in Java?在 Java 中使用 == 比较浮点数有什么问题?
【发布时间】:2010-11-08 11:05:09
【问题描述】:

根据this java.sun page==是Java中浮点数的相等比较运算符。

但是,当我输入此代码时:

if(sectionID == currentSectionID)

进入我的编辑器并运行静态分析,我得到:“JAVA0078 与 =="

比较的浮点值

使用== 比较浮点值有什么问题?正确的方法是什么?

【问题讨论】:

  • 因为将浮点数与 == 进行比较是有问题的,所以将它们用作 ID 是不明智的;您的示例代码中的名称表明您正在执行此操作;长整数(longs)是首选,也是 ID 的事实标准。
  • 是的,这只是一个随机示例,还是您实际上使用浮点数作为 ID?有什么原因吗?
  • "对于float字段,使用Float.compare方法;对于double字段,使用Double.compare。float的存在使得对float和double字段的特殊处理是必要的。 NaN、-0.0f 和类似的双精度常量;有关详细信息,请参阅 Float.equals 文档。”(Joshua Bloch:Effective Java)

标签: java equality floating-accuracy


【解决方案1】:

测试浮点数是否相等的正确方法是:

if(Math.abs(sectionID - currentSectionID) < epsilon)

其中 epsilon 是一个非常小的数字,例如 0.00000001,具体取决于所需的精度。

【讨论】:

  • 查看已接受答案 (cygnus-software.com/papers/comparingfloats/comparingfloats.htm) 中的链接,了解为什么固定 epsilon 并不总是一个好主意。具体来说,随着被比较的浮点数变大(或变小),epsilon 不再合适。 (不过,如果你知道你的浮点值都相对合理,那么使用 epsilon 就可以了。)
  • @P.T 他能否将 epsilon 与一个数字相乘并将函数更改为 if(Math.abs(sectionID - currentSectionID) &lt; epsilon*sectionID 以解决该问题?
  • 这甚至可能是迄今为止最好的答案,但它仍然存在缺陷。你从哪里得到epsilon?
  • @MichaelPiefel 它已经说:“取决于所需的精度”。浮点数本质上有点像物理值:您只对有限数量的位置感兴趣,具体取决于总不准确度,超出此范围的任何差异都被认为是没有实际意义的。
  • 但是 OP 真的只想测试相等性,并且由于已知这是不可靠的,因此必须使用不同的方法。尽管如此,我还是不明白他甚至知道他“期望的精度”是什么。所以如果你想要的只是一个更可靠的相等性测试,那么问题仍然存在:你从哪里得到 epsilon?我建议在回答这个问题时使用Math.ulp()
【解决方案2】:

浮点值可能有一点偏差,因此它们可能不会报告完全相等。例如,将浮点数设置为“6.1”,然后再次打印出来,您可能会得到类似“6.099999904632568359375”的报告值。这是浮动工作方式的基础;因此,您不想使用相等来比较它们,而是在一个范围内进行比较,也就是说,如果浮点数与您要比较的数字的差异小于某个绝对值。

This 登记册上的文章很好地概述了为什么会出现这种情况;有用和有趣的阅读。

【讨论】:

  • @kevindtimm :所以你会像这样进行相等性测试,那么如果 (number == 6.099999904632568359375) 任何时候你想知道 number 等于 6.1 ......是的,你是正确的......计算机中的一切都是严格确定的,只是在做数学问题时,用于浮点数的近似值是违反直觉的。
  • 浮点值仅在 very specific hardware 上具有不确定性。
  • @Stuart 我可能弄错了,但我不认为 FDIV 错误是不确定的。硬件给出的答案不符合规范,但它们是确定性的,因为相同的计算总是产生相同的错误结果
  • @Gravity 你可以争辩说任何行为都是确定性的,给出了一组特定的警告。
  • 浮点 并非不精确。每个浮点值都是它的本来面目。浮点计算的结果可能不精确。但要小心!当您在程序中看到类似 0.1 的内容时,这不是浮点值。那是一个浮点literal---编译器通过计算转换成浮点值的字符串。
【解决方案3】:

只是为了说明其他人所说的背后的原因。

浮点数的二进制表示有点烦人。

在二进制中,大多数程序员都知道 1b=1d, 10b=2d, 100b=4d, 1000b=8d 之间的相关性

好吧,它也适用于另一种方式。

.1b=.5d, .01b=.25d, .001b=.125, ...

问题在于没有精确的方法来表示大多数十进制数,如 .1、.2、.3 等。您所能做的就是用二进制近似。当数字打印时,系统会进行一些四舍五入,以便显示 .1 而不是 .10000000000001 或 .999999999999(它们可能与存储的表示形式一样接近 .1)

编辑评论:这是一个问题的原因是我们的期望。当我们将 2/3 转换为十进制时,我们完全期望 2/3 会在某个时候被伪造,无论是 0.7 或 .67 还是 .666667.. 但我们不会自动期望 0.1 以与 2/3 相同的方式四舍五入——这正是正在发生的事情。

顺便说一句,如果您好奇,它内部存储的数字是使用二进制“科学记数法”的纯二进制表示。因此,如果您告诉它存储十进制数 10.75d,它将为 10 存储 1010b,为十进制存储 .11b。所以它会存储 .101011 然后它在最后保存几位说:将小数点向右移动四位。

(虽然从技术上讲它不再是小数点,但现在是二进制点,但对于大多数会发现此答案有用的人来说,这个术语不会让事情变得更容易理解。)

【讨论】:

  • @Matt K - 嗯,不是固定点;如果您“在末尾保存几位以将小数点 [N] 位向右移动”,那就是浮点数。不动点将小数点的位置固定好。此外,一般来说,由于总是可以移动二叉树 (?) 点,从而在最左边的位置留下一个“1”,你会发现一些系统省略了前导“1”,从而释放了空间(1位!)来扩展指数的范围。
  • 这个问题与二进制与十进制表示无关。使用十进制浮点,您仍然可以得到 (1 / 3) * 3 == 0.9999999999999999999999999999。
  • @dan04 是的,因为 1/3 没有十进制或二进制表示,它确实有一个三进制表示,并且会以这种方式正确转换:)。我列出的数字(.1、.25 等)都有完美的十进制表示,但没有二进制表示——人们已经习惯了那些具有“精确”表示的数字。 BCD 将完美地处理它们。这就是区别。
  • 这应该有更多的支持,因为它描述了问题背后的真正问题。
【解决方案4】:

使用 == 比较浮点值有什么问题?

因为0.1 + 0.2 == 0.3不是真的

【讨论】:

  • Float.compare(0.1f+0.2f, 0.3f) == 0 呢?
  • 0.1f + 0.2f == 0.3f 但 0.1d + 0.2d != 0.3d。默认情况下,0.1 + 0.2 是双精度数。 0.3 也是双精度数。
【解决方案5】:

我认为浮点数(和双精度数)有很多混淆,最好把它弄清楚。

  1. 在符合标准的 JVM [*] 中使用浮点数作为 ID 并没有本质上的错误。如果您只是将浮点 ID 设置为 x,则不对其执行任何操作(即不进行算术运算),然后再测试 y == x,您会没事的。将它们用作 HashMap 中的键也没有任何问题。你不能做的是假设x == (x - y) + y等等式。这就是说,人们通常使用整数类型作为ID,你可以观察到这里的大多数人都被这段代码推迟了,所以出于实际原因,最好是遵守约定。请注意,double 的不同值与长 values 的数量一样多,因此使用 double 将一无所获。此外,使用双精度生成“下一个可用 ID”可能会很棘手,并且需要一些浮点运算知识。不值得麻烦。

  2. 另一方面,依赖两个数学等效计算结果的数值相等是有风险的。这是因为从十进制转换为二进制表示时会出现舍入误差和精度损失。这在 SO 上已经讨论到死了。

[*] 当我说“符合标准的 JVM”时,我想排除某些大脑受损的 JVM 实现。见this

【讨论】:

  • 当使用浮点数作为 ID 时,必须小心确保它们使用== 而不是equals 进行比较,或者确保没有与自身比较不相等的浮点数存储在桌子。否则,一个程序试图例如计算一个表达式可以产生多少个独特的结果,当输入不同的输入时,可能会将每个 NaN 值视为唯一的。
  • 以上指的是Float,不是float
  • Float 在说什么?如果尝试构建一个包含唯一float 值的表并将它们与== 进行比较,可怕的IEEE-754 比较规则将导致该表被NaN 值淹没。
  • float 类型没有equals 方法。
  • 啊——我不是指equals 实例方法,而是比较float 类型的两个值的静态方法(我认为是在Float 类中)。跨度>
【解决方案6】:

截至今天,快速简便的方法是:

if (Float.compare(sectionID, currentSectionID) == 0) {...}

但是,docs 没有明确指定在浮点数计算中始终存在的边距差异的值(@Victor 的答案中的 epsilon),但它应该是合理,因为它是标准语言库的一部分。

然而,如果需要更高的或定制的精度,那么

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

是另一种解决方案。

【讨论】:

  • 您链接的文档指出“如果 f1 在数值上等于 f2,则值为 0”,这与 (sectionId == currentSectionId) 相同,这对于浮点数不准确。 epsilon 方法是更好的方法,在这个答案中:stackoverflow.com/a/1088271/4212710
【解决方案7】:

由于舍入误差,浮点值不可靠。

因此,它们可能不应该用作键值,例如 sectionID。请改用整数,如果int 不包含足够的可能值,请使用long

【讨论】:

  • 同意。鉴于这些是 ID,没有理由使浮点运算复杂化。
  • 或者很长。根据未来生成的唯一 ID 的数量,int 可能不够大。
  • double 与 float 相比有多精确?
  • @ArvindhMani doubles 要精确得多,但它们也是浮点值,所以我的答案是要包括 floatdouble
【解决方案8】:

这不是 java 特有的问题。使用 == 比较两个浮点数/双精度数/任何十进制类型数可能会由于它们的存储方式而导致问题。 单精度浮点数(根据 IEEE 标准 754)有 32 位,分布如下:

1 位 - 符号(0 = 正,1 = 负)
8 位 - 指数(2^x 中 x 的特殊 (bias-127) 表示)
23 位 - 尾数。存储的实际数字。

尾数是导致问题的原因。这有点像科学记数法,只有以 2 为底的数字(二进制​​)看起来像 1.110011 x 2^5 或类似的东西。 但是在二进制中,第一个1总是1(0的表示除外)

因此,为了节省一点内存空间(双关语),IEEE 决定应假定为 1。例如,尾数 1011 实际上是 1.1011。

这可能会导致一些比较问题,尤其是 0,因为 0 不可能在浮点数中精确表示。 除了其他答案描述的浮点数学问题之外,这也是不鼓励使用 == 的主要原因。

Java 有一个独特的问题,即该语言在许多不同的平台上都是通用的,每个平台都可以有自己独特的浮点格式。这使得避免 == 变得更加重要。

比较两个浮点数的正确方法(不是特定语言请注意)是否相等如下:

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

其中 ACCEPTABLE_ERROR 是 #defined 或其他等于 0.000000001 的常量或所需的任何精度,正如 Victor 已经提到的那样。

某些语言具有此功能或内置此常量,但通常这是一个好习惯。

【讨论】:

  • Java 定义了浮点行为。它不依赖于平台。
  • IEEE-754 标准中使用的术语是“有效位”,而不是“尾数”。仅当指数字段为 1-254 时,有效数的前导位为 1。如果指数字段为 0,则有效数的前导位为 0。“0 不可能精确地表示为浮点数”的说法是错误的; 0 表示为所有位为零(并且前导位设置为 1,可区分为 -0,等于 +0)。这不会导致任何比较问题,也不是“不鼓励 == 的主要原因”。
【解决方案9】:

除了之前的答案,你应该知道-0.0f+0.0f(它们是==但不是equals)和Float.NaN(它是equals但是不是==)(希望我没看错 - 啊,别这样!)。

编辑:让我们检查一下!

import static java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

欢迎来到 IEEE/754。

【讨论】:

  • 如果某物是 ==,那么它们在本质上是相同的。他们怎么可能不等于()?也许你把它倒过来了?
  • @Matt NaN 很特别。 Java 中的 Double.isNaN(double x) 实际上实现为 { return x != x; }...
  • 对于浮点数,== 并不意味着数字“与位相同”(相同的数字可以用不同的位模式表示,尽管其中只有一个是规范化的形式)。同样,-0.0f0.0f 由不同的位模式表示(符号位不同),但与== 比较相等(但与equals 不同)。您认为== 是按位比较的假设通常是错误的。
【解决方案10】:

这里有一个很长(但希望有用)的关于这个问题以及您可能遇到的许多其他浮点问题的讨论:What Every Computer Scientist Should Know About Floating-Point Arithmetic

【讨论】:

    【解决方案11】:

    首先,它们是浮动的还是浮动的?如果其中之一是浮点数,则应使用 equals() 方法。另外,最好使用静态 Float.compare 方法。

    【讨论】:

      【解决方案12】:

      您可以使用 Float.floatToIntBits()。

      Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
      

      【讨论】:

      • 你在正确的轨道上。 floatToIntBits() 是正确的方法,但使用 Float 的内置 equals() 函数会更容易。见这里:stackoverflow.com/a/3668105/2066079。你可以看到默认的 equals() 在内部使用了 floatToIntBits。
      • 是的,如果它们是 Float 对象。您可以将上述方程用于基元。
      【解决方案13】:

      您可能希望它是 ==,但是 123.4444444444443 != 123.4444444444442

      【讨论】:

        【解决方案14】:

        如果你*必须*使用浮点数,strictfp 关键字可能有用。

        http://en.wikipedia.org/wiki/strictfp

        【讨论】:

        • 或者可能对不同的架构更有用。
        【解决方案15】:

        以下自动使用最佳精度:

        /**
         * Compare to floats for (almost) equality. Will check whether they are
         * at most 5 ULP apart.
         */
        public static boolean isFloatingEqual(float v1, float v2) {
            if (v1 == v2)
                return true;
            float absoluteDifference = Math.abs(v1 - v2);
            float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
            return absoluteDifference < 5 * maxUlp;
        }
        

        当然,您可以选择多于或少于 5 个 ULP(“最后一个单元”)。

        如果您使用 Apache Commons 库,Precision 类具有 compareTo()equals() 以及 epsilon 和 ULP。

        【讨论】:

        • 将float改为double时,此方法不起作用,因为isDoubleEqual(0.1+0.2-0.3, 0.0) == false
        • 看来你需要更多像 10_000_000_000_000_000L 作为double 的因子来覆盖这个。
        【解决方案16】:

        产生相等实数的两种不同计算不一定产生相等的浮点数。使用 == 来比较计算结果的人通常会对此感到惊讶,因此该警告有助于标记否则可能是一个微妙且难以重现的错误。

        【讨论】:

          【解决方案17】:

          您是否正在处理将浮点数用于名为 sectionID 和 currentSectionID 的东西的外包代码?只是好奇。

          @Bill K:“浮点数的二进制表示有点烦人。”为何如此?你会如何做得更好?有些数字不能以任何基数正确表示,因为它们永远不会结束。 Pi 就是一个很好的例子。你只能近似它。如果您有更好的解决方案,请联系英特尔。

          【讨论】:

            【解决方案18】:

            正如其他答案中提到的,双打可以有小的偏差。您可以编写自己的方法来使用“可接受的”偏差来比较它们。不过……

            有一个用于比较双精度的 apache 类:org.apache.commons.math3.util.Precision

            它包含一些有趣的常量:SAFE_MINEPSILON,它们是简单算术运算的最大可能偏差。

            它还提供了比较、相等或舍入双精度数的必要方法。 (使用 ulps 或绝对偏差)

            【讨论】:

              【解决方案19】:

              我可以说,在一个答案中,您应该使用:

              Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
              

              为了让您更多地了解正确使用相关运算符,我在这里详细说明一些案例: 通常,在 Java 中测试字符串有三种方法。您可以使用 ==、.equals () 或 Objects.equals ()。

              它们有何不同? == 测试字符串中的参考质量,这意味着找出两个对象是否相同。另一方面, .equals () 测试两个字符串在逻辑上是否相等。最后,Objects.equals() 测试两个字符串中的任何空值,然后确定是否调用 .equals()。

              理想的操作符

              这已经引起了很多争论,因为这三个运营商中的每一个都有自己独特的优势和劣势。例如,在比较对象引用时,== 通常是首选选项,但在某些情况下,它似乎也可以比较字符串值。

              但是,您得到的是一个下降值,因为 Java 会产生一种错觉,即您正在比较值,但实际上您不是。考虑以下两种情况:

              案例一:

              String a="Test";
              String b="Test";
              if(a==b) ===> true
              

              案例2:

              String nullString1 = null;
              String nullString2 = null;
              //evaluates to true
              nullString1 == nullString2;
              //throws an exception
              nullString1.equals(nullString2);
              

              因此,在测试其设计的特定属性时,最好使用每个运算符。但几乎在所有情况下,Objects.equals() 都是一个更通用的运算符,因此经验丰富的 Web 开发人员会选择它。

              在这里您可以了解更多详情:http://fluentthemes.com/use-compare-strings-java/

              【讨论】:

                【解决方案20】:

                正确的方法是

                java.lang.Float.compare(float1, float2)
                

                【讨论】:

                • Float.compare(float1, float2) 返回一个 int,因此不能在 if 条件中使用它来代替 float1 == float2。此外,它并没有真正解决此警告所指的根本问题——如果浮点数是数值计算的结果,则可能会由于舍入错误而发生 float1 != float2。
                • 对,不能复制粘贴,必须先查看文档。
                • 你可以做的不是 float1 == float2 是 Fl​​oat.compare(float1,float2) == 0。
                • 这对你没有任何好处——你仍然会得到Float.compare(1.1 + 2.2, 3.3) != 0
                【解决方案21】:

                减少舍入误差的一种方法是使用 double 而不是 float。这不会使问题消失,但它确实减少了程序中的错误数量,并且 float 几乎从来都不是最佳选择。恕我直言。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2011-02-03
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-02-23
                  • 2011-08-20
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多