在 Java 中使用 == 比较浮点数有什么问题?

What's wrong with using == to compare floats in Java?

提问人:user128807 提问时间:7/7/2009 最后编辑:Morgothuser128807 更新时间:7/11/2017 访问量:193151

问:

根据这个 java.sun 页面是 Java 中浮点数的相等比较运算符。==

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

if(sectionID == currentSectionID)

进入我的编辑器并运行静态分析,我得到:“JAVA0078浮点值与 == 相比”

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

java 相等 浮动精度

评论

32赞 Carl Manaster 7/7/2009
因为将浮点数与 == 进行比较是有问题的,所以将它们用作 ID 是不明智的;示例代码中的名称表明这就是您正在执行的操作;长整数 (longs) 是首选,并且是 ID 的事实标准。
4赞 Per Wiklander 2/14/2010
是的,这只是一个随机的例子,还是您实际上使用浮点数作为 ID?有原因吗?
12赞 nos 7/7/2009
必填链接 :-)每个计算机科学家都应该了解的浮点运算知识
7赞 lbalazscs 11/3/2014
“对于浮点型字段,请使用 Float.compare 方法;对于双精度字段,请使用 Double.compare。由于存在 Float.NaN、-0.0f 和类似的双精度常数,因此需要对浮点场和双精度场进行特殊处理;有关详细信息,请参阅 Float.equals 文档。(Joshua Bloch:有效的 Java)
0赞 Per Lundberg 6/7/2022
@nos 我知道的旧评论,但现在链接似乎已损坏(至少在我的浏览器中)。更新后的链接应该是这样的:docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

答:

57赞 Paul Sonier 7/7/2009 #1

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

这篇关于登记册的文章很好地概述了为什么会这样;有用且有趣的阅读。

评论

0赞 Newtopian 7/22/2009
@kevindtimm : 所以你会像这样做你的相等性测试,那么如果 (number == 6.099999904632568359375) 任何时候你想知道的数字等于 6.1...是的,你是对的......计算机中的所有内容都是严格确定性的,只是在做数学问题时用于浮点数的近似值是违反直觉的。
0赞 Stuart P. Bentley 6/4/2011
浮点值仅在非常特定的硬件上不确定地不精确。
1赞 Gravity 7/27/2011
@Stuart我可能弄错了,但我不认为 FDIV 错误是不确定的。硬件给出的答案不符合规范,但它们是确定性的,因为相同的计算总是产生相同的错误结果
0赞 Stuart P. Bentley 8/21/2011
@Gravity 你可以争辩说,只要有一组特定的警告,任何行为都是确定性的。
1赞 Solomon Slow 11/7/2015
浮点并非不精确。每个浮点值都是它的本质。浮点计算的结果可能不精确。但要小心!当您在程序中看到类似 0.1 的内容时,这不是浮点值。这是一个浮点文字---编译器通过执行计算将其转换为浮点值的字符串。
4赞 omerkudat 7/7/2009 #2

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

8赞 Eric Wilson 7/7/2009 #3

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

因此,它们可能不应用作键值,例如 sectionID。请改用整数,或者如果未包含足够的可能值。longint

评论

2赞 Yohnny 7/7/2009
同意。鉴于这些是 ID,没有理由使用浮点运算使事情复杂化。
2赞 Wayne Hartman 7/7/2009
或者很长。根据将来生成的唯一 ID 数量,int 可能不够大。
0赞 Arvindh Mani 3/18/2017
与浮点数相比,double 的精度如何?
1赞 Eric Wilson 3/20/2017
@ArvindhMani s 要精确得多,但它们也是浮点值,所以我的答案应该包括 和 。doublefloatdouble
-2赞 Eric 7/7/2009 #4

正确的方法是

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

评论

7赞 quant_dev 7/7/2009
Float.compare(float1, float2) 返回一个 int,因此在 if 条件中不能用它来代替 float1 == float2。此外,它并没有真正解决这个警告所指的根本问题——如果浮点数是数值计算的结果,float1 != float2 可能只是由于舍入错误而发生。
1赞 Eric 7/7/2009
对了,你不能复制粘贴,你必须先检查文档。
2赞 deterb 7/20/2009
你可以做的不是 float1 == float2 是 Float.compare(float1,float2) == 0。
29赞 Pavel Minaev 7/22/2009
这不会给你买任何东西——你仍然会得到Float.compare(1.1 + 2.2, 3.3) != 0
220赞 Victor 7/7/2009 #5

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

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

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

评论

29赞 P.T. 12/13/2011
请参阅已接受的答案 (cygnus-software.com/papers/comparingfloats/comparingfloats.htm) 中的链接,了解为什么固定的 epsilon 并不总是一个好主意。具体来说,当被比较的浮点数中的值变大(或变小)时,epsilon 不再合适。(不过,如果您知道浮点值都相对合理,那么使用 epsilon 是可以的。
1赞 enthusiasticgeek 11/7/2014
@P.T 他能否将 epsilon 乘以一个数字并更改函数来解决这个问题?if(Math.abs(sectionID - currentSectionID) < epsilon*sectionID
3赞 Michael Piefel 4/27/2015
这甚至可能是迄今为止最好的答案,但它仍然存在缺陷。你从哪里得到 epsilon?
1赞 ivan_pozdeev 8/29/2016
@MichaelPiefel它已经说过:“取决于所需的精度”。浮动就其本质而言有点像物理值:根据总的不准确性,你只对一些有限数量的位置感兴趣,任何超出这个范围的差异都被认为是没有意义的。
0赞 Michael Piefel 8/29/2016
但是OP真的只想测试相等性,而且由于已知这是不可靠的,因此必须使用不同的方法。不过,我不明白他甚至不知道他“想要的精度”是什么;因此,如果你想要的只是一个更可靠的相等性测试,那么问题仍然存在:你从哪里得到epsilon?我建议在回答这个问题时使用。Math.ulp()
3赞 KM. 7/7/2009 #6

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

7赞 Tom Hawtin - tackline 7/7/2009 #7

除了之前的答案之外,您还应该意识到与 and(它们是但不是)和(它是但不是)相关的奇怪行为(希望我说得对 - 啊,不要这样做!-0.0f+0.0f==equalsFloat.NaNequals==

编辑:让我们检查一下!

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。

评论

0赞 Matt K 7/7/2009
如果某物是 ==,则它们在位上是相同的。它们怎么可能不是相等的()?也许你有它倒过来了?
0赞 quant_dev 7/7/2009
@Matt NaN 很特别。Double.isNaN(double x) 在 Java 中实际上是实现为 { return x != x;...
1赞 Pavel Minaev 7/22/2009
使用浮点数并不意味着数字“与位相同”(相同的数字可以用不同的位模式表示,尽管其中只有一个是规范化形式)。同样,并且由不同的位模式表示(符号位不同),但与 (但不与 ) 进行比较。一般来说,你的按位比较假设是错误的。==-0.0f0.0f==equals==
8赞 Adam Goode 7/7/2009 #8

这里有一个很长(但希望有用)的讨论,关于这个问题和你可能遇到的许多其他浮点问题: 每个计算机科学家都应该知道的关于浮点运算的知识

23赞 Bill K 7/7/2009 #9

只是为了给出其他人所说的话背后的原因。

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

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

好吧,它也可以以另一种方式工作。

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

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

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

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

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

评论

1赞 JustJeff 7/7/2009
@Matt K-嗯,不是定点;如果你“在最后保存几位,说将小数点 [N] 位向右移动”,那就是浮点数。固定点将基数点的位置固定下来。此外,一般来说,由于总是可以移动二值 (?) 点,使您在最左边的位置留下一个“1”,因此您会发现一些系统省略了前导的“1”,从而将由此释放的空间(1 位!)用于扩展指数的范围。
0赞 dan04 10/28/2010
该问题与二进制表示与十进制表示无关。使用小数点数浮点,您仍然有类似 (1 / 3) * 3 == 0.99999999999999999999999999999999 的东西。
2赞 Bill K 10/29/2010
@dan04是的,因为 1/3 没有十进制或二进制表示,所以它确实具有三进制表示,并且会以这种方式正确转换:)。我列出的数字(.1、.25 等)都有完美的十进制表示,但没有二进制表示——人们习惯于那些具有“精确”表示的数字。BCD 将完美地处理它们。这就是区别。
1赞 Levite 3/25/2015
这应该有更多的赞成票,因为它描述了问题背后的真正问题。
19赞 AakashM 7/7/2009 #10

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

因为事实并非如此0.1 + 0.2 == 0.3

评论

8赞 Aquarius Power 1/26/2016
怎么样?Float.compare(0.1f+0.2f, 0.3f) == 0
0赞 burnabyRails 6/28/2019
0.1f + 0.2f == 0.3f 但 0.1d + 0.2d != 0.3d。默认情况下,0.1 + 0.2 是双精度。0.3 也是双倍。
2赞 VoiceOfUnreason 7/7/2009 #11

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

2赞 xcramps 7/7/2009 #12

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

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

8赞 CodeFusionMobile 7/7/2009 #13

这不是一个特定于 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 已经提到的。

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

评论

4赞 Yishai 1/22/2010
Java 对浮点数定义了行为。它不依赖于平台。
1赞 Eric Postpischil 9/9/2020
IEEE-754 标准中使用的术语是“significand”,而不是“mantissa”。仅当指数字段为 1-254 时,有效位数的前导位才为 1。如果指数字段为 0,则有效位数的前导位为 0。语句“0 不可能在浮点数中精确表示”是错误的;0 表示所有位为零(前导位设置为 1,区分为 −0,等于 +0)。这不会对比较造成任何问题,也不是“不鼓励的主要原因==”。
0赞 Eric Postpischil 9/9/2020
Re “比较两个浮点数的正确方法”:没有通用的解决方案来比较包含先前操作错误的浮点数。
14赞 quant_dev 7/7/2009 #14

我认为围绕浮动(和双打)有很多混淆,最好弄清楚它。

  1. 在符合标准的 JVM 中使用浮点数作为 ID 本身并没有错 [*]。如果你只是将浮点 ID 设置为 x,什么都不用做(即不做算术),然后测试 y == x,你会没事的。此外,将它们用作 HashMap 中的键也没有错。你不能做的是假设等式,比如 等。话虽如此,人们通常使用整数类型作为 ID,您可以观察到这里的大多数人都对这段代码望而却步,因此出于实际原因,最好遵守约定。请注意,有多少个不同的值,有多少个 long ,因此使用 .此外,生成“下一个可用 ID”对于双精度计算可能很棘手,并且需要一些浮点运算知识。不值得麻烦。x == (x - y) + ydoublevaluesdouble

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

[*]当我说“符合标准的 JVM”时,我想排除某些脑损伤的 JVM 实现。看这个

评论

0赞 supercat 7/28/2014
当使用浮点数作为 ID 时,必须小心确保它们被比较 而不是 ,或者确保没有与自身相等的浮点数存储在表中。否则,一个程序试图计算在输入各种输入时可以从表达式中产生多少个唯一结果,可能会将每个 NaN 值视为唯一值。==equals
0赞 quant_dev 8/7/2017
以上指的是 ,而不是 。Floatfloat
0赞 supercat 8/7/2017
在说什么?如果有人试图构建一个唯一值的表并将它们与 进行比较,可怕的 IEEE-754 比较规则将导致该表被值淹没。Floatfloat==NaN
1赞 Teddy 9/21/2023
当我们谈论使用 float 作为 ID 列时,这似乎是正确的答案。但是,我想知道DB的“浮动行为”如何在这个等式中发挥作用。因此,这可能会将我们带回 Epsilon 方法。最后,使用 float 作为 ID 可能是个坏主意。
1赞 quant_dev 10/11/2023
@Teddy 你是对的,数据库处理浮点数的方式在这里增加了额外的复杂性,可能不值得麻烦。
-3赞 Peter Lawrey 7/7/2009 #15

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

3赞 sdcvvc 7/22/2009 #16

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

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

评论

0赞 joey rohan 4/3/2015
或者对于不同的架构可能更有用。
4赞 aamadmi 6/24/2012 #17

您可以使用 Float.floatToIntBits()。

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

评论

1赞 dberm22 4/4/2014
你走在正确的轨道上。floatToIntBits() 是正确的方法,但只使用 Float 内置的 equals() 函数会更容易。请参阅此处: stackoverflow.com/a/3668105/2066079 .您可以看到默认的 equals() 在内部利用了 floatToIntBits。
1赞 aamadmi 4/5/2014
如果它们是 Float 对象,则为。您可以将上述等式用于基元。
4赞 Michael Piefel 4/27/2015 #18

以下项会自动使用最佳精度:

/**
 * 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 库,则该类同时具有 epsilon 和 ULP。PrecisioncompareTo()equals()

评论

0赞 hychou 9/26/2017
将 float 更改为 double 时,此方法不起作用,因为 isDoubleEqual(0.1+0.2-0.3, 0.0) == false
0赞 Michael Piefel 9/26/2017
似乎您需要更多像10_000_000_000_000_000L一样的因素来涵盖这一点。double
1赞 bvdb 5/19/2015 #19

正如其他答案中提到的,双打可能会有很小的偏差。你可以编写自己的方法,使用“可接受的”偏差来比较它们。然而。。。

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

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

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

0赞 Fluent-Themes 12/4/2016 #20

在一行答案中,我可以说,你应该使用:

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

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

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

理想的操作员

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

然而,你得到的是一个下降值,因为 Java 创造了一种错觉,即你正在比较值,但实际上你不是。请考虑以下两种情况:

案例一:

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

案例二:

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/

18赞 alisa 7/11/2017 #21

到今天为止,快速简便的方法是:

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

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

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

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

是另一种解决方案选项。

评论

2赞 typoerrpr 8/22/2019
您链接的文档指出“如果 f1 在数值上等于 f2,则值为 0”,这与浮点数不准确的操作相同。epsilon 方法是更好的方法,它位于以下答案中:stackoverflow.com/a/1088271/4212710(sectionId == currentSectionId)