天天看点

Java规范Java面试题之浮点数双精度相等问题Java规范Java面试题之浮点数双精度相等问题

Java规范Java面试题之浮点数双精度相等问题

ps:简单的大学生应该知道的计算机组成原理。可以直接跳过不看

问题描述

工作空闲,同学群里有开始各种各样结婚生孩子养生的话题。翻阅阿里《JAVA开发手册v1.5.0华山版》,第八页,随手把一个规范的反例发到群里让他们看下结果,demo如下:

float a = 1.0f - 0.9f;
    float b = 0.9f - 0.8f;
    if (a == b) {
        // 预期进入此代码快,执行其它业务逻辑
        // 但事实上 a==b 的结果为 false
        System.out.println("niubi");
    }
    Float x = Float.valueOf(a);
    Float y = Float.valueOf(b);
    if (x.equals(y)) {
        // 预期进入此代码快,执行其它业务逻辑
        // 但事实上 equals 的结果为 false
        System.out.println("shabi");
    }
           

群里两个再喊结果是打印“shabi",当然注释部分删除后发群里的。

问题分析

看下阿里《JAVA开发手册v1.5.0华山版》的言简意赅的解释:

说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进

制无法精确表示大部分的十进制小数

Java 开发手册 8/44

同学继续疑问——大部分的小数指那些?

二进制的数表示十进制,都知道类似8421这种权重。

比如十进制的15可以用00001111表示,也就是8+4+2+1;那小数呢

都知道小数0.5=5*10的-1次方,这样说还不明显(因为这种说法是针对十进制的)。那换一种说法(二进制):

刚才说的8421码权重,再往后是什么,我们可以说是

⋯ / 8 / 4 / 2 / 1 / 1 2 / 1 4 / ⋯ \cdots/8/4/2/1/\frac{1}{2}/\frac{1}{4}/\cdots ⋯/8/4/2/1/21​/41​/⋯

可以看出来,十进制可以看成权重分别是

⋯ / 1 0 4 / 1 0 3 / 1 0 2 / 1 0 1 / 1 0 0 / 1 10 / 1 1 0 2 / 1 1 0 3 ⋯ \cdots/10^4/10^3/10^2/10^1/10^0/\frac{1}{10}/\frac{1}{10^2}/\frac{1}{10^3}\cdots ⋯/104/103/102/101/100/101​/1021​/1031​⋯

二进制权重也就是

⋯ / 2 3 / 2 2 / 2 1 / 2 0 / 1 2 1 / 1 2 2 / ⋯ \cdots/2^3/2^2/2^1/2^0/\frac{1}{2^1}/\frac{1}{2^2}/\cdots ⋯/23/22/21/20/211​/221​/⋯

那现在再看小数0.5,二进制情况下的说法就是

1 2 1 \frac{1}{2^1} 211​

计算机只使用二进制,可以表示的浮点数d也就一目了然了。

集 合 T = [ ⋯ / 2 3 / 2 2 / 2 1 / 2 0 / 1 2 1 / 1 2 2 / ⋯   ] , 则 d = ∑ a i , a i ∈ T 集合T=[\cdots/2^3/2^2/2^1/2^0/\frac{1}{2^1}/\frac{1}{2^2}/\cdots],则 d=\sum{a_i},a_i\in T 集合T=[⋯/23/22/21/20/211​/221​/⋯],则d=∑ai​,ai​∈T

也就是说可以用计算机表示出来的***浮点数d为集合T中任意N项的和***,另外任意一个数字使用的任意N项绝对不同。原理相当于十进制,你问我为什么10和11不一样。换成二进制也一样,为什么1101和1100不一样?因为都是唯一的,像唯一主键一样。

举个例子:

0.75=0.5+0.25,其他不在一一举例了。

回过头在看原题目:

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
    // 预期进入此代码快,执行其它业务逻辑
    // 但事实上 a==b 的结果为 false
}
           

理论上相等的a和b,在计算机中为什么不相等。如果换成如下代码,是否相等?

float a = 0.1f;
float b = 0.1f;
if (a == b) {
    
}
           

很显然,是相等的。因为都是0.1f,数字本身就可以说只具有唯一性,在某一进制中与其他数字的区别具有唯一性。

回到原题,0.9和0.8是一定不能用计算机精确表示出来的。都是用近似数来表示,其实上面我已经解释的很清楚了,看个人理解,就知道1.0f-0.9f一定不和0.9f-0.8f相等。

不止如此,同理:

m = a − b , n = c − d , 其 中 ( a = c , b = d 不 同 时 存 在 , a 、 b 、 c 、 d 都 是 含 有 有 限 位 小 数 的 小 数 ) m=a-b,n=c-d,其中(a=c,b=d不同时存在,a、b、c、d都是含有有限位小数的小数) m=a−b,n=c−d,其中(a=c,b=d不同时存在,a、b、c、d都是含有有限位小数的小数)

的情况下,

如 果 理 论 上 m = n , 则 计 算 机 中 m ≠ n 如果理论上m=n,则计算机中m\neq n 如果理论上m=n,则计算机中m​=n

想要讲解的讲解完了,其他部分请参考本文章提到的《JAVA开发手册》。里面有解析以及解决方案,另外没事去考一下“阿里巴巴认证证书——阿里巴巴编码规范(Java)”也是挺好的。