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


177

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

但是,当我键入以下代码时:

if(sectionID == currentSectionID)

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

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


29
因为比较浮点数与==是有问题的,所以将它们用作ID是不明智的;示例代码中的名称表明您正在执行此操作;首选长整数(longs),以及ID的实际标准。
卡尔·马纳斯特


4
是的,那只是一个随机示例,还是您实际使用浮点数作为ID?有什么理由吗?
Per Wiklander'2

5
“对于float字段,请使用Float.compare方法;对于double字段,请使用Double.compare。由于存在Float.NaN,-0.0f和类似的double常数,因此需要对float和double字段进行特殊处理;有关详细信息,请参见Float.equals文档。” (Joshua Bloch:有效的Java)
lbalazscs 2014年

Answers:


211

测试浮标“相等”的正确方法是:

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

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


26
有关为什么固定的epsilon并非总是一个好主意,请参见已接受的答案(cygnus-software.com/papers/comparingfloats/comparingfloats.htm)中的链接。具体来说,随着要比较的浮标中的值变大(或变小),ε不再适用。(不过,如果您知道浮点值都相对合理,则可以使用epsilon。)
PT

1
@PT他可以将epsilon与一个数字相乘并更改函数if(Math.abs(sectionID - currentSectionID) < epsilon*sectionID以解决该问题吗?
狂热爱好者

3
到目前为止,这甚至可能是最好的答案,但是仍然存在缺陷。你从哪里得到ε的?
Michael Piefel 2015年

1
@MichaelPiefel已经说过:“取决于所需的精度”。浮动的性质类似于物理值:您只对有限数量的职位感兴趣,具体取决于总的不准确性,超出范围的任何差异都将被视为无意义。
ivan_pozdeev,2016年

但是OP确实只想测试是否相等,并且由于众所周知这是不可靠的,因此必须使用其他方法。不过,我不理解他甚至不知道自己的“期望精度”是什么。因此,如果您只想进行更可靠的相等性测试,那么问题仍然存在:您从何处获得ε?我建议Math.ulp()在回答这个问题时使用。
Michael Piefel

53

浮点值可能会稍微偏离一点,因此它们的报告可能并不完全相等。例如,将浮点数设置为“ 6.1”,然后再次打印出来,则可能会得到报告值,例如“ 6.099999904632568359375”。这是浮动工作方式的基础。因此,您不希望使用相等性来比较它们,而是要在一定范围内进行比较,即,如果float与要比较的数字之间的差异小于某个绝对值。

寄存器上的这篇文章很好地概述了为什么会这样。有用和有趣的阅读。


@kevindtimm:这样您就可以进行相等性测试,例如,如果(number == 6.099999904632568359375)在任何时候您想知道数字等于6.1 ...是的,您是正确的...计算机中的所有信息都是严格确定性的,只是在做数学问题时,用于浮点数的近似值是违反直觉的。
Newtopian

非常特定的硬件上,浮点值只是不确定性不精确。
Stuart P. Bentley

1
@Stuart我可能会误会,但是我不认为FDIV错误不是确定性的。硬件给出的答案不符合规范,但是是确定性的,因为相同的计算始终会产生相同的错误结果
Gravity

@Gravity您可以说,在特定的警告条件下,任何行为都是确定性的。
斯图尔特·本特利

浮点并不精确。每个浮点值就是它的确切值。不精确的是浮点计算的结果。但是要当心!当您在程序中看到类似0.1的值时,这不是浮点值。那是一个浮点文字 -一个编译器通过计算将其转换为浮点值的字符串。
所罗门慢

22

只是为了说明其他所有人在说什么的原因。

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

以二进制形式,大多数程序员都知道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)和0.11b(代表十进制)。因此,它将存储.101011,然后在末尾节省一些内容,即:将小数点右移四位。

(尽管从技术上讲,它不再是小数点,而现在是二进制点,但是对于大多数会发现任何用例的人来说,该术语都不会使事情更容易理解。)


1
@Matt K-嗯,不是固定点;如果“在末尾节省几位,说将小数点[N]位向右移”,则为浮点数。定点取基数点的位置为固定点。同样,通常,由于总是可以通过移动双斜点(?)来使您在最左边的位置留下“ 1”,因此您会发现一些系统省略了前导“ 1”,从而释放了这样释放的空间(1位!)以扩展指数范围。
JustJeff

该问题与二进制和十进制表示无关。使用十进制浮点数,您仍然会遇到(1/3)* 3 == 0.9999999999999999999999999999。
dan04

2
@ dan04是的,因为1/3没有十进制或二进制表示形式,所以它确实具有三进制表示形式,并且将以这种方式正确转换:)。我列出的数字(.1,.25等)都具有完美的十进制表示形式,但没有二进制表示形式-人们习惯于具有“精确”表示形式的人。BCD会完美地处理它们。就是这样。
Bill K

1
这应该有更多的方法,因为它描述了问题背后的实际问题。
莱维特2015年

19

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

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


7
那又如何Float.compare(0.1f+0.2f, 0.3f) == 0呢?
Aquarius Power

0.1f + 0.2f == 0.3f但0.1d + 0.2d!= 0.3d 默认情况下,0.1 + 0.2是双精度。0.3也是两倍。
burnabyRails

12

我认为彩车(和双打)周围存在很多混乱,将其清除是一件好事。

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

  2. 另一方面,依靠两个数学上等效的计算结果的数值相等是冒险的。这是由于从十进制转换为二进制表示时的舍入误差和精度损失。在SO上对此进行了讨论。

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


当使用浮点数作为ID时,必须注意确保使用==而不是进行比较equals,或者确保没有任何与自身比较不相等的浮点数存储在表中。否则,试图计算例如一个表达式在馈入各种输入后可以从一个表达式中产生多少个唯一结果的程序可能会将每个NaN值视为唯一。
超级猫

以上是指Float,而不是float
–quant_dev

在说Float什么 如果尝试建立一张唯一float值表并将其与进行比较==,那么可怕的IEEE-754比较规则将导致该表中充满NaN值。
超级猫

float类型没有equals方法。
–quant_dev

啊-我不是说equals实例方法,而是静态方法(我认为是在Float类中),该方法比较type的两个值float
超级猫

9

这不是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

如Victor所述,其中ACCEPTABLE_ERROR是#defined或其他等于0.000000001的常数或要求的精度。

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


3
Java为浮点定义了行为。它不依赖于平台。
Yishai 2010年

9

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

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

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

但是,如果需要更高或定制的精度,则

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

是另一个解决方案选项。


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

8

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

因此,可能不应将它们用作键值,例如sectionID。使用整数代替,或者long如果int没有足够的可能值。


2
同意 鉴于这些是ID,因此没有理由使浮点运算复杂化。
Yohnny

2
还是很长。根据将来生成多少个唯一ID,int可能不够大。
韦恩·哈特曼2009年

与float相比,double的精度如何?
Arvindh Mani

1
@ArvindhMani doubles更精确,但它们也是浮点值,因此我的回答是同时包含floatdouble
埃里克·威尔逊

7

除了以前的答案,你应该知道,有关联怪异的行为-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。


如果某物==,则直到位为止它们都是相同的。他们怎么可能不是equals()?也许你倒退了?
mkb

@Matt NaN很特别。Java中的Double.isNaN(double x)实际上实现为{return x!= x; } ...
quant_dev

1
对于浮点数,==并不意味着数字“与位相同”(相同的数字可以用不同的位模式表示,尽管只有其中之一是规范化形式)。同样,-0.0f0.0f由不同的比特模式(符号位是不同的)表示,但比较结果为相等同==(但不与equals)。==一般来说,您的按位比较假设是错误的。
帕维尔·米纳夫


5

您可以使用Float.floatToIntBits()。

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

1
您走在正确的轨道上。floatToIntBits()是正确的方法,但是仅使用Float内置的equals()函数会更容易。看到这里:stackoverflow.com/a/3668105/2066079。您可以看到默认的equals()内部使用floatToIntBits。
dberm22 2014年

1
是的,如果它们是Float对象。您可以将以上等式用于基本体。
aamadmi 2014年

4

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


4

以下自动使用最佳精度:

/**
 * 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

看来您需要像10_000_000_000_000_000_000L这样的double覆盖率。
Michael Piefel

3

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



2

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


2

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

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


1

如其他答案所述,双打可能会有小的偏差。您可以编写自己的方法来使用“可接受的”偏差来比较它们。但是...

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

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

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


1

我可以说一行答案,您应该使用:

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

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

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

理想的操作员

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

但是,您得到的只是一个下降值,因为Java产生了一种幻想,即您正在比较值,但实际上并非如此。请考虑以下两种情况:

情况1:

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/


-2

正确的方法是

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

7
Float.compare(float1,float2)返回一个int值,因此在if条件中不能代替float1 == float2使用它。此外,它并不能真正解决此警告所指的根本问题-如果float是数值计算的结果,则仅由于舍入误差而可能会发生float1!= float2。
–quant_dev

1
对,您不能复制粘贴,必须先检查文档。
埃里克

2
您可以代替Float1 == float2做的是Float.compare(float1,float2)==
0。–威慑

29
这不会为您带来任何Float.compare(1.1 + 2.2, 3.3) != 0
收益

-2

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.