1. 小数的二进制表示
以10.625为例。整数部分进行除2取余的操作,10的二进制为1010。小数部分进行乘2取整操作,直到小数部分为0或达到需要的精度:
- 0.625*2=1.25 取整数1,小数部分0.25继续计算
- 0.25*2=0.5 取整数0,小数部分0.5继续计算
- 0.5*2=1.0 取整数1,小数部分为0,停止计算
因此0.625对应的二进制为101。10.625对应的二进制为1010.101。
可以发现,一些小数对应的二进制数是无法精确表示出来的。例如0.1的二进制数为:
- 0.1*2=0.2 取整数0,小数部分0.2继续计算
- 0.2*2=0.4 取整数0,小数部分0.4继续计算
- 0.4*2=0.8 取整数0,小数部分0.8继续计算
- 0.8*2=1.6 取整数1,小数部分0.6继续计算
- 0.6*2=1.2 取整数1,小数部分0.2继续计算
- 产生循环
因此,0.1对应的二进制数为:0.00011001100110011...
2. IEEE 754标准
二进制浮点数算术标准,详见百度。这里仅以double为例,不涉及float。
IEEE 754定义的double(64-bit)由三部分组成:
- 符号位(1-bit): 0表示正数,1表示负数
- 指数位(11-bit):表示浮点数的指数,采用偏移量的存储方式,偏移值为1023
- 尾数位(52-bit):表示有效数字,默认是1.xxxx的形式
例如,10.625对应的二进制为1010.101,小数点左移3位得到其标准化形式为:1.010101*2^3。指数为3,加上偏移量1023,可得指数位为1026。相应的二进制为10000000010。尾数位则为010101。故10.625符合IEEE 754标准的二进制为:
0 10000000010 01010100000000...补齐52bit。
在Java中对比一下:
double d1=10.625; long valBits=Double.doubleToLongBits(d1); System.out.println(Long.toBinaryString(valBits));// 输出为:100000000100101010000000000000000000000000000000000000000000000,前面加个0,补齐64-bit
类似地,0.1的二进制标准化形式为:1.1001100110011...*2^(-4)。指数为-4,加上偏移量1023,则指数位为:1019,二进制为01111111011。故0.1符合IEEE 754的二进制为:
0 01111111011 1001100110011001100110011001100110011001100110011010
符合IEEE 754标准的二进制数再转换为十进制,可使用如下公式:
其中,e为指数位对应的十进制数,f为尾数位对应的十进制数(小数)。
先不考虑符号sign和f部分的系数(即二进制中的0和1),可知:
利用上述公式计算得到符合IEEE 754标准的0.1的二进制再转换为十进制的值为:
3. 0.1+0.2=?
0.1符合IEEE 754标准的二进制表示为(指数位为1019,指数为-4):
0 01111111011 1001100110011001100110011001100110011001100110011010
0.2符合IEEE 754标准的二进制表示为(指数位为1020,指数为-3):
0 01111111100 1001100110011001100110011001100110011001100110011010
先将二者的指数对齐(向大指数对齐)。即0.1的尾数右移一位(相当于小数点左移一位)。移位后尾数变为:
1100110011001100110011001100110011001100110011001101
然后与0.2的尾数相加可得:
0.1100110011001100110011001100110011001100110011001101 +
1.1001100110011001100110011001100110011001100110011010=
10.0110011001100110011001100110011001100110011001100111
将尾数标准化,指数+1,尾数右移1位。
0011001100110011001100110011001100110011001100110011(1)
可见末尾的1会被移除掉,这时会向上舍入(如果末尾是0,则直接舍掉了),尾数变为了:
0011001100110011001100110011001100110011001100110100
最终0.1+0.2的结果的二进制为:
0 01111111101 0011001100110011001100110011001100110011001100110100
转换为十进制为:0.300000000000000044408920985006261616945266723632812500
public static void main(String[] args){double d1=0.1;double d2=0.2;double d3=d1+d2;System.out.println(d3); } // 输出为:0.30000000000000004
但是0.3符合IEEE 754标准的二进制是:
0 01111111101 0011001100110011001100110011001100110011001100110011
转换为十进制为:0.299999999999999988897769753748434595763683319091796875