Java中比较float和double原始类型

10

我碰到了Java的一个奇怪角落。(对我来说很奇怪)

double dd = 3.5;          
float ff = 3.5f;
System.out.println(dd==ff);   

输出结果为:true

double dd = 3.2;
float ff = 3.2f;
System.out.println(dd==ff);

输出:false

我发现,如果我们将任意两个值(例如上面例子中的float和double)与.5.0进行比较,如3.5、234.5、645.0, 则输出结果为true,即这两个值相等,否则输出结果为false,即使它们是相等的。

我甚至尝试了使用strictfp方法,但没有成功。 我错过了什么吗?


14
...又开始了。 - Kerrek SB
2
@Kerrek:你能带我一起去吗? - Ajinkya
2
@Ajinka:关于浮点类型的问题,有关值的精确表示的混淆问题每三天在SO上出现一次。 :-) - Kerrek SB
@Kerrek:哦,对不起。现在接下来的两天不用担心 :) - Ajinkya
1
@Ajinkya:的确,这就像是通过携带炸弹来利用统计学一样的东西。 - Kerrek SB
5个回答

21

请查看计算机科学家应该了解的浮点数知识.

将无限多的实数压缩为有限数量的位需要使用近似表示法....

--- 编辑以展示上述引用的含义 ---

您不应该对浮点数或双精度数进行相等比较,因为您无法确保您分配给浮点数或双精度数的数字是精确的。

所以

 float x = 3.2f;

这并不会得到一个值为3.2的浮点数,而是得到一个值为3.2加上一些非常小的误差,例如3.19999999997f。这就显而易见了,为什么比较操作不起作用。

要合理地比较两个浮点数是否相等,需要检查它们的值是否足够“接近”,可以像下面这样实现:

float error = 0.000001 * second;
if ((first >= second - error) || (first <= second + error)) {
   // close enough that we'll consider the two equal
   ...
}

谢谢提供链接。您能简单告诉我缺少什么吗? - Ajinkya
@Ajinkya,已经解释清楚了。祝你的项目好运。 - Edwin Buck
这是一篇非常有趣的关于浮点数比较的文章--通常情况下,使用浮点数的epsilon和相对误差检查是不错的选择,但利用IEEE754格式的特性会更好 :-) - Kerrek SB
最后一段代码对于负值无法正常工作。将errorMath.abs()包装起来即可使其正常工作。 - Sky Kelsey
为了判断两个数字是否相等,你应该在if语句中使用AND运算符(&&)。 - Sadegh Ghanbari

10

两者的区别在于,3.5可以在floatdouble中精确表示,而3.2在这两种类型中都无法被精确表示……它们两个最接近的近似值是不同的。

想象一下我们有两种定点数十进制类型,一种存储4位有效数字,另一种存储8位有效数字,并且我们让它们分别存储最接近“三分之一”的数字(无论如何我们可能做到这一点)。那么一个将具有值0.3333,而另一个将具有值0.33333333。

floatdouble之间的相等比较首先将float转换为double,然后比较这两个值-这相当于将我们“小十进制”类型中的0.3333转换为0.33330000。然后它会比较0.33330000和0.33333333是否相等,并给出false的结果。


非常感谢您回答我这个多余的问题。还要感谢您让它变得相当简单 :) - Ajinkya
感谢这个简单明了的解释。 - skystar7

4

浮点数是一种二进制格式,可以将数字表示为2的幂的和。例如,3.5可以表示为2 + 1 + 1/2。

将3.2f作为3.2的近似值,则为

2 + 1 + 1/8+ 1/16+ 1/128+ 1/256+ 1/2048+ 1/4096+ 1/32768+ 1/65536+ 1/524288+ 1/1048576+ 1/4194304 + a small error

然而将3.2d作为3.2的近似值是不准确的。
2 + 1 + 1/8+ 1/16+ 1/128+ 1/256+ 1/2048+ 1/4096+ 1/32768+ 1/65536+ 1/524288+ 1/1048576+ 1/8388608+ 1/16777216+ 1/134217728+ 1/268435456+ 1/2147483648+ 1/4294967296+ 1/34359738368+ 1/68719476736+ 1/549755813888+ 1/1099511627776+ 1/8796093022208+ 1/17592186044416+ 1/140737488355328+ 1/281474976710656+ 1/1125899906842624 + a smaller error

当你使用浮点数时,需要使用适当的四舍五入。如果你使用 BigDecimal(很多人都这么做),它已经内置了四舍五入功能。
double dd = 3.2;          
float ff = 3.2f;
// compare the difference with the accuracy of float.
System.out.println(Math.abs(dd - ff) < 1e-7 * Math.abs(ff));

顺便说一下,我用来打印双精度小数的分数的代码。

double f = 3.2d;
double f2 = f - 3;
System.out.print("2+ 1");
for (long i = 2; i < 1L << 54; i <<= 1) {
  f2 *= 2;
  if (f2 >= 1) {
    System.out.print("+ 1/" + i);
    f2 -= 1;
  }
}
System.out.println();

1
尽管你的回答看起来很复杂,但它确实有助于理解底层的事情。 - Ajinkya

3

浮点数的常见实现方式是IEEE754,它只能精确表示那些具有短而有限的二进制展开形式的数字,即仅由有限个(相邻的)二的幂次之和得到的数字。所有其他数字都无法精确表示。

由于floatdouble的大小不同,对于一个不可表示的值,在这两种类型中的表示也不同,因此它们比较起来是不相等的。

(二进制字符串的长度是尾数的大小,因此float为24位,double为53位,80位扩展精度浮点数为64位(Java中没有)。指数决定了比例尺度。)


谢谢你带我一起去 :) - Ajinkya
没问题 - 这有点像是心理复制/粘贴,所以没有什么损失 :-) 不过如果你计划比较浮点数,请务必查看那篇浮点数比较的文章。它真的很启发人。 - Kerrek SB

0

这应该可以工作:

BigDecimal ddBD = new BigDecimal(""+dd);
BigDecimal ffBD = new BigDecimal(""+ff);

// test for equality
ddBO.equals(ffBD);

当你想要比较浮点数或双精度数时,总是使用BigDecimal,并且始终使用带有字符串参数的BigDecimal构造函数!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接