根据这个 java.sun 页面 ==
是 Java 中浮点数的相等比较运算符。
但是,当我键入此代码时:
if(sectionID == currentSectionID)
进入我的编辑器并运行静态分析,我得到:“JAVA0078 浮点值与 == 相比
使用==
比较浮点值有什么问题?正确的方法是什么?
根据这个 java.sun 页面 ==
是 Java 中浮点数的相等比较运算符。
但是,当我键入此代码时:
if(sectionID == currentSectionID)
进入我的编辑器并运行静态分析,我得到:“JAVA0078 浮点值与 == 相比
使用==
比较浮点值有什么问题?正确的方法是什么?
测试浮动“平等”的正确方法是:
if(Math.abs(sectionID - currentSectionID) < epsilon)
其中 epsilon 是一个非常小的数字,例如 0.00000001,具体取决于所需的精度。
浮点值可能会有一点偏差,因此它们可能不会报告完全相等。例如,将浮点数设置为“6.1”,然后再次打印出来,您可能会得到类似“6.099999904632568359375”的报告值。这是浮动工作方式的基础;因此,您不想使用相等来比较它们,而是在一个范围内进行比较,也就是说,如果浮点数与您要与之比较的数字的差异小于某个绝对值。
登记册上的这篇文章很好地概述了为什么会出现这种情况;有用和有趣的阅读。
只是为了给出其他人所说的背后的原因。
浮点数的二进制表示有点烦人。
在二进制中,大多数程序员都知道 1b=1d, 10b=2d, 100b=4d, 1000b=8d 之间的相关性
好吧,它也适用于另一种方式。
.1b=.5d, .01b=.25d, .001b=.125, ...
问题是没有精确的方法来表示大多数十进制数,如 .1、.2、.3 等。你所能做的就是用二进制近似。当数字打印时,系统会进行一些四舍五入,以便显示 .1 而不是 .10000000000001 或 .999999999999(它们可能与存储的表示形式一样接近 0.1)
评论编辑:这是一个问题的原因是我们的期望。当我们将 2/3 转换为十进制时,我们完全预计 2/3 会在某个时候被伪造,无论是 0.7 或 .67 还是 .666667.. 但我们不会自动期望 .1 以与 2/3 相同的方式四舍五入——这正是正在发生的事情。
顺便说一句,如果您好奇它内部存储的数字是使用二进制“科学记数法”的纯二进制表示。因此,如果您告诉它存储十进制数 10.75d,它将为 10 存储 1010b,为十进制存储 .11b。所以它会存储 .101011 然后它在最后保存几位说:将小数点向右移动四位。
(虽然从技术上讲,它不再是小数点,而是现在的二进制点,但对于大多数会发现这个答案有用的人来说,这个术语不会让事情变得更容易理解。)
使用 == 比较浮点值有什么问题?
因为这不是真的0.1 + 0.2 == 0.3
我认为浮点数(和双打)有很多混淆,把它弄清楚是件好事。
在符合标准的 JVM [*]中使用浮点数作为 ID 并没有本质上的错误。如果您只是将浮点 ID 设置为 x,则不对其执行任何操作(即不进行算术运算),然后再测试 y == x,您会没事的。将它们用作 HashMap 中的键也没有任何问题。你不能做的是假设等式x == (x - y) + y
,等等。这就是说,人们通常使用整数类型作为 ID,你可以观察到这里的大多数人都被这段代码推迟了,所以出于实际原因,最好遵守约定. 请注意,有多少不同的double
值,就有多少 long values
,所以使用double
. 此外,使用双精度生成“下一个可用 ID”可能会很棘手,并且需要一些浮点运算知识。不值得麻烦。
另一方面,依赖两个数学等价计算结果的数值相等是有风险的。这是因为从十进制转换为二进制表示时会出现舍入误差和精度损失。这已经在SO上被讨论死了。
[*] 当我说“符合标准的 JVM”时,我想排除某些大脑受损的 JVM 实现。看到这个。
截至今天,快速简便的方法是:
if (Float.compare(sectionID, currentSectionID) == 0) {...}
但是,文档 没有明确指定浮动计算中始终存在的边距差异的值(@Victor 的答案中的epsilon ),但它应该是合理的,因为它是标准语言库的一部分。
然而,如果需要更高或定制的精度,那么
float epsilon = Float.MIN_NORMAL;
if(Math.abs(sectionID - currentSectionID) < epsilon){...}
是另一种解决方案。
由于舍入误差,浮点值不可靠。
因此,它们可能不应该用作键值,例如 sectionID。请改用整数,或者long
如果int
不包含足够的可能值。
这不是 java 特有的问题。使用 == 比较两个浮点数/双精度数/任何十进制类型数可能会由于它们的存储方式而导致问题。单精度浮点数(根据 IEEE 标准 754)有 32 位,分布如下:
1 位 - 符号(0 = 正,1 = 负)
8 位 - 指数(x 在 2^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 已经提到的那样。
某些语言具有此功能或内置此常量,但通常这是一个好习惯。
除了以前的答案之外,您应该知道还有一些奇怪的行为与-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。
这是一个关于这个问题以及您可能遇到的许多其他浮点问题的很长(但希望有用)的讨论:每个计算机科学家应该知道的关于浮点运算的知识
您可以使用 Float.floatToIntBits()。
Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
首先,它们是浮动的还是浮动的?如果其中之一是浮点数,则应使用 equals() 方法。此外,最好使用静态 Float.compare 方法。
如果你*必须*使用浮点数,strictfp 关键字可能很有用。
您可能希望它是 ==,但是 123.4444444444443 != 123.4444444444442
以下自动使用最佳精度:
/**
* 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
该类具有epsilon 和 ULP compareTo()
。equals()
产生相等实数的两种不同计算不一定产生相等的浮点数。使用 == 比较计算结果的人通常会对此感到惊讶,因此警告有助于标记否则可能是一个微妙且难以重现的错误。
您是否正在处理将浮点数用于名为 sectionID 和 currentSectionID 的东西的外包代码?只是好奇。
@Bill K:“浮点数的二进制表示有点烦人。” 怎么会这样?你会如何做得更好?有些数字不能以任何基数正确表示,因为它们永远不会结束。Pi 就是一个很好的例子。你只能近似它。如果您有更好的解决方案,请联系英特尔。
正如其他答案中提到的,双打可以有小的偏差。您可以编写自己的方法来使用“可接受的”偏差来比较它们。然而 ...
有一个用于比较双打的 apache 类:org.apache.commons.math3.util.Precision
它包含一些有趣的常数:SAFE_MIN
和EPSILON
,它们是简单算术运算的最大可能偏差。
它还提供了比较、相等或舍入双精度数的必要方法。(使用 ulps 或绝对偏差)
在一个答案中,我可以说,你应该使用:
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
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/
减少舍入误差的一种方法是使用 double 而不是 float。这不会使问题消失,但它确实减少了程序中的错误数量,并且 float 几乎从来都不是最佳选择。恕我直言。
正确的方法是
java.lang.Float.compare(float1, float2)