妈妈,我的电脑连加法都算错了

192 阅读4分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

垃圾电脑,连一个加法都算不对

小朱: 怎么给儿子换电脑了,我那旧电脑她不想嘛。
小仙女: 你也知道旧电脑,连个加法都算不对,你好意思。
小朱: 纳尼,什么加法算不对。

示例

public static void main(String[] args) {
    float f = 2000000000f;
    System.out.println((f + 50f) == f);
}

小仙女: 虽然我没学过,但是没吃过猪肉,没见过猪跑吗?肯定是false啊。但是得到的竟然是true
小朱: 😯,那你新电脑试过了吗?
小仙女: 那还用试吗,肯定没问题,我试下好了。
......
小仙女: 走,和我一起找他们理论去,垃圾电脑,毁我儿子。
小朱: ......

各位,你们的电脑是不是也出问题了。😄😄😄

解析

我们先来了解计算机怎么存储float的数据
float的存储正是将4字节32位划分为了3部分来分别存储正负号,指数部分,小数部分

  • 1.Sign(1位): 用来表示浮点数是正数还是负数,0表示正数,1表示负数。
  • 2.Exponent(8位): 指数部分。为了同时表示正负指数以及他们的大小顺序,这里实际存储的是指数+127。
  • 3.Mantissa(23位): 尾数部分。

我们先算2000000000f的二进制

float f =  2000000000f;
int i = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(i));
//运行结果
//1001110111011100110101100101000

结果应该在补一个0,正好32位。
有同学有可能对着很模糊,为什么2000000000f这的二进制是这样的。下面我们来介绍一下,因为这个例子全是整数部分所以这么写

public static void main(String[] args) {
    Integer x = 2000000000;
    StringBuilder stringBuilder = new StringBuilder();
    while (x >= 1){
        //求余数
        Integer y = x % 2;
        x = x / 2;
        stringBuilder.append(y);
    }
    //反转字符串
    stringBuilder.reverse();
    System.out.println(stringBuilder);
}
//结果
//1110111001101011001010000000000

1110111001101011001010000000000可以写成1.11011100110101100101 * 2^30

第一步: Sign(1位)因为是正数所以第一位是0 第二步: Exponent(8位)因为指数是30+127=157转成二进制是10011101
第三步: Mantissa(23位)11011100110101100101,000这个地方不足23位补0,以逗号分割
综上所得第一步➕第二步➕第三步不足32位补0得到
0,10011101,11011100110101100101,000,这就得到上面的值了(上面的值第一位0隐藏了)。
到这里读者还是不明白为什么(f + 50f) == ftrue,其实2000000000f == 2000000050f。是不是不可思议。当你把2000000050f用二进制表示出来你就知道了。

float f =  2000000050f;
int i = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(i));
//运行结果
//1001110111011100110101100101000

没有任何变化,为什么呢?我们来按我们的方法去写。

public static void main(String[] args) {
    Integer x = 2000000050;
    StringBuilder stringBuilder = new StringBuilder();
    while (x >= 1){
        //求余数
        Integer y = x % 2;
        x = x / 2;
        stringBuilder.append(y);
    }
    //反转字符串
    stringBuilder.reverse();
    System.out.println(stringBuilder);
}
//结果
//1110111001101011001010000110010

同上面
1110111001101011001010000110010可以写成1.11011100110101100101000011001 * 2^30

第一步: Sign(1位)因为是正数所以第一位是0 第二步: Exponent(8位)因为指数是30+127=157转成二进制是10011101
第三步: Mantissa(23位)11011100110101100101000,011001因为超了23位,所以多余的部分是四舍五入,这里是遇1进位所以得到的是11011100110101100101000
综上所得第一步➕第二步➕第三步超过32位四舍五入得到
0,10011101,11011100110101100101000,这就得到上面的值了(上面的值第一位0隐藏了)。
所以2000000000f == 2000000050f是true。这下是不是理解了。去考考你的同学和同事,让他们怀疑一下自己的编程人生(大牛除外😄)。

最后留一个问题0.1 + 0.2 == 0.3吗?之前讲过哦。