为什么在Java中比较浮点数是不一致的?

41
class Test{  
    public static void main(String[] args){  
        float f1=3.2f;  
        float f2=6.5f;  

        if(f1==3.2){
            System.out.println("same");  
        }else{
            System.out.println("different");  
        }
        if(f2==6.5){
            System.out.println("same");  
        }else{  
            System.out.println("different");  
        }
    }  
}  

输出:

different
same
为什么输出是这样的?我期望第一种情况的结果是same

6
可能是因为标题有误导作用。我本以为这是一个关于如何区分floatdouble的问题。 - drewr
@DrewR。谢谢。我已更新。 - PSR
我不明白。你的问题中仍然没有任何“double”。你为什么改变了标题? - drewr
1
@DrewR。3.26.5都是双精度浮点数。 - Mdev
下次请不要使用“提前感谢...”这样的称谓。 - hello_there_andy
显示剩余3条评论
8个回答

41

区别在于6.5在浮点数和双精度浮点数中都可以被准确表示,而3.2在任一类型中都不能被准确表示。其两个最接近的近似值也不同。

对float和double进行相等性比较首先将float转换为double然后进行比较。因此存在数据丢失。


您不应该将float或double进行相等性比较;因为无法保证您分配给float或double的数字是准确的。

这种舍入误差是浮点计算的特征

将无限多的实数压缩到有限数量的位中需要近似表示。尽管存在无限多的整数,在大多数程序中,整数计算的结果可以存储在32位中。

相比之下,对于任何固定位数,使用实数进行的大多数计算会产生不能使用那么多位精确表示的量。因此,浮点计算的结果通常必须舍入以适应其有限表示。这种舍入误差是浮点计算的特征。

查看《计算机科学家应该了解的浮点运算》以获取更多信息!


为什么第二个结果是真的? - PSR
9
6.5 = 6+1/2 = 6+2^-1 = 110.1(2)(精确值)。但是3.2 = 3 + 5^-1,所以在二进制中不能用有限小数表示。只有分母只含有基数的质因数的有理数才可以在该进制下表示。 - phuclv

25
他们都是IEEE浮点数标准不同部分的实现。一个float占用4个字节,而double占用8个字节。
作为一个经验法则,在大多数情况下,你应该优先使用double,只有在有充分理由时才使用float。(例如,使用float而不是double的一个好理由是,“我知道我不需要那么高的精度,但需要在内存中存储一百万个数据。”)另外值得一提的是,很难证明你不需要double精度。
此外,在比较浮点数值相等性时,通常会使用类似于 Math.abs(a-b) < EPSILON 的方法,其中ab是要比较的浮点值,EPSILON是一个小的浮点值,如1e-5。这是因为浮点值很少编码它们“应该”具有的精确值 - 而是通常编码非常接近的值 - 因此在确定两个值是否相同时必须“眯起眼睛”。编辑:每个人都应该阅读@Kugathasan Abimaran下面发布的链接:计算机科学家应该了解的有关浮点算术的一切!

为什么第二个结果是真的? - PSR
两个答案都可以。抱歉我不能接受超过一个答案。所以+1给你。 - PSR
3
没问题!我觉得@Kugathasan Abimaran可能给出了更好的答案。 :) Translated: 没关系!我认为@Kugathasan Abimaran可能给出了更好的答案。 :) - sigpwned

5
为了看到你所处理的内容,你可以使用Float和Double的toHexString方法:

FloatDouble 的 toHexString 方法:

class Test {
    public static void main(String[] args) {
        System.out.println("3.2F is: "+Float.toHexString(3.2F));
        System.out.println("3.2  is: "+Double.toHexString(3.2));
        System.out.println("6.5F is: "+Float.toHexString(6.5F));
        System.out.println("6.5  is: "+Double.toHexString(6.5));
    }
}
$ java Test
3.2F is: 0x1.99999ap1
3.2  is: 0x1.999999999999ap1
6.5F is: 0x1.ap2
6.5  is: 0x1.ap2

一般来说,一个数如果等于A * 2^B(其中A和B是整数,其允许的值由语言规范确定,double类型有更多允许的值),则它具有精确表示。

在这种情况下, 6.5 = 13/2 = (1+10/16)*4 = (1+a/16)*2^2 == 0x1.ap2, 而 3.2 = 16/5 = ( 1 + 9/16 + 9/16^2 + 9/16^3 + . . . ) * 2^1 == 0x1.999. . . p1。 但Java只能容纳有限数量的数字,因此它会在某个点上截断.999.....(你可能记得从数学上来讲,0.999. . .=1,在十进制中。在十六进制中,它将是0.fff. . .=1)。


3
class Test {  
  public static void main(String[] args) {  
    float f1=3.2f;  
    float f2=6.5f;  

    if(f1==3.2f)  
      System.out.println("same");  
    else  
      System.out.println("different");  

    if(f2==6.5f)  
      System.out.println("same");  
    else  
      System.out.println("different");  
    }  
  }

试试这样做,它会起作用。如果没有 'f',你将比较一个浮点数与其他不同精度的浮点类型,这可能会导致意外的结果,就像你的情况一样。

实际上,没有 f,它是两个浮点类型之间的比较,只是精度不同。 - afsantos

2
无法直接比较floatdouble类型的值。在进行比较之前,必须将double转换为float或将float转换为double。如果进行前面的比较,则转换会询问“是否float持有double值的最佳float表示形式?”如果进行后面的转换,则问题将是“float是否持有double值的完美表示形式”。在许多情况下,前者的问题更有意义,但Java假定所有floatdouble之间的比较都旨在询问后者的问题。
建议无论语言是否允许,编码标准都应绝对禁止直接比较floatdouble类型的操作数。例如:
float f = function1();
double d = function2();
...
if (d==f) ...

d表示一个在float中无法精确表示的值时,很难确定预期的行为是什么。如果意图是将f转换为double,并将该转换的结果与d进行比较,则应将比较写成:

if (d==(double)f) ...

虽然类型转换不会改变代码的行为,但它清楚地表明了代码的行为是有意为之。如果意图是比较f是否持有d的最佳float表示,则应该如下所示:

if ((float)d==f)

注意,这种行为与没有强制转换时非常不同。如果您的原始代码将每个比较的 double 操作数转换为 float,那么两个相等性测试都将通过。

1
通常情况下,使用==运算符比较浮点数并不是一个好的做法,因为存在近似问题。

0

6.5可以在二进制中精确表示,而3.2则不行。这就是为什么精度差异对于6.5并不重要,因此6.5 == 6.5f

快速回顾二进制数的工作原理:

100 -> 4

10 -> 2

1 -> 1

0.1 -> 0.5 (or 1/2)

0.01 -> 0.25 (or 1/4)

等等。

6.5的二进制表示为:110.1(精确结果,其余数字都是零)

3.2的二进制表示为:11.001100110011001100110011001100110011001100110011001101...(这里精度很重要!)

浮点数只有24位精度(其余用于符号和指数),因此:

3.2f的二进制表示为:11.0011001100110011001100(不等于双精度近似值)

基本上就像你在写十进制数的1/5和1/7一样:

1/5 = 0,2
1,7 = 0,14285714285714285714285714285714.

-1

浮点数比双精度数精度低,因为浮点数使用32位,其中1位用于符号,23位用于精度,8位用于指数。而双精度数使用64位,其中52位用于精度,11位用于指数,1位用于符号...精度是重要的问题。表示为浮点数和双精度数的十进制数可以相等或不相等,这取决于需要精度的范围(即小数点后数字的范围可能会有所不同)。 问候 S. ZAKIR


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