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

use*_*807 170 java equality floating-accuracy

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

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

if(sectionID == currentSectionID)
Run Code Online (Sandbox Code Playgroud)

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

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

Vic*_*tor 207

测试浮动"正确"的正确方法是:

if(Math.abs(sectionID - currentSectionID) < epsilon)
Run Code Online (Sandbox Code Playgroud)

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

  • 请参阅接受的答案中的链接(http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm),了解为什么固定的epsilon并不总是一个好主意.具体而言,当被比较的浮点数中的值变大(或变小)时,epsilon不再合适.(如果你知道你的浮点值都相对合理,那么使用epsilon就可以了.) (26认同)
  • 到目前为止,这甚至可能是最好的答案,但它仍然存在缺陷.你从哪里得到epsilon? (3认同)

Pau*_*ier 53

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

登记册上的这篇文章很好地概述了为什么会这样; 有用而有趣的阅读.


Bil*_*l K 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,它将为10存储1010b,为十进制存储.11b.所以它会存储.101011然后它会在末尾保存几个位来说:将小数点向右移动四个位置.

(虽然从技术上讲它不再是小数点,但它现在是一个二进制点,但是对于大多数能够找到任何用途答案的人来说,这个术语不会让事情变得更容易理解.)

  • @ dan04是的,因为1/3没有十进制OR二进制表示,它确实有一个三进制表示,并将正确转换那种方式:).我列出的数字(.1,.25等)都有完美的十进制表示,但没有二进制表示 - 人们习惯于那些具有"精确"表示的人.BCD会完美地处理它们.这就是区别. (2认同)

Aak*_*shM 19

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

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

  • 怎么样`Float.compare(0.1f + 0.2f,0.3f)== 0`? (7认同)
  • 在哪里?这是正确的方法吗?`? (6认同)

qua*_*dev 12

我认为浮游物(和双打)之间存在很多混淆,清除它是很好的.

  1. 在标准兼容的JVM [*] 中使用浮点数作为ID没有任何内在错误.如果你只是将浮点数ID设置为x,不做任何事情(即没有算术),然后测试y == x,你会没事的.将它们用作HashMap中的键也没有错.你不能做的是假设x == (x - y) + y等等等等.这就是说,人们通常使用整数类型作为ID,你可以观察到这里的大多数人被这个代码推迟了,所以出于实际原因,最好遵守惯例.请注意,有很多不同的double值,因为有很长的values,所以你通过使用没有任何收获double.此外,生成"下一个可用ID"对于双精度来说可能很棘手,并且需要一些浮点运算知识.不值得麻烦.

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

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


Eri*_*son 8

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

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

  • 同意.鉴于这些是ID,没有理由使浮点运算复杂化. (2认同)
  • 或者很久.根据将来生成的唯一ID数量,int可能不够大. (2认同)

Cod*_*ile 8

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

1位 - 符号(0 =正,1 =负)
8位 - 指数(2 ^ x中x的特殊(偏差-127)表示)
23位 - Mantisa.存储的实际编号.

mantisa是导致问题的原因.它有点像科学记数法,只有基数2(二进制)中的数字看起来像1.110011 x 2 ^ 5或类似的东西.但在二进制中,第一个1总是1(除了0的表示)

因此,为了节省一点内存空间(双关语),IEEE决定应该假设1.例如,1011的mantisa确实是1.1011.

这可能会导致一些比较问题,特别是0,因为0不能在float中准确表示.除了其他答案描述的浮点数学问题之外,这是阻止==的主要原因.

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

比较两个浮点数(非语言特定的头脑)的正确方法如下:

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal
Run Code Online (Sandbox Code Playgroud)

其中ACCEPTABLE_ERROR是#defined或其他常量等于0.000000001或者需要的精度,正如Victor提到的那样.

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

  • Java具有已定义的浮点行为.它不依赖于平台. (3认同)

ali*_*isa 8

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

if (Float.compare(sectionID, currentSectionID) == 0) {...}
Run Code Online (Sandbox Code Playgroud)

但是,文档 并没有明确指出余量差异的值( 来自@Victor答案的epsilon),它总是存在于浮点数的计算中,但它应该是合理的,因为它是标准语言库的一部分.

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

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}
Run Code Online (Sandbox Code Playgroud)

是另一种解决方案.

  • 您链接的文档指出“如果 f1 在数值上等于 f2,则值为 0”,这使得它与执行 `(sectionId == currentSectionId)` 相同,这对于浮点来说不准确。epsilon 方法是更好的方法,在这个答案中:/sf/answers/76179001/ (2认同)

Tom*_*ine 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
    }
} 
Run Code Online (Sandbox Code Playgroud)

欢迎来到IEEE/754.

  • 对于浮点数,“==”并不意味着数字“与位相同”(相同的数字可以用不同的位模式表示,尽管其中只有一个是标准化形式)。同样,“-0.0f”和“0.0f”由不同的位模式表示(符号位不同),但与“==”比较为相等(但与“equals”不相等)。一般来说,您认为“==”是按位比较的假设是错误的。 (2认同)

Ada*_*ode 6

这是一个很长的(但希望有用的)讨论,可能会遇到这个问题以及许多其他浮点问题:每个计算机科学家应该知道的关于浮点运算的内容