计算机浮点数那些事

十进制小数转二进制小数

对十进制小数乘2得到的整数部分和小数部分,整数部分既是相应的二进制数码;
再用2乘小数部分(之前乘后得到新的小数部分),又得到整数和小数部分;
如此不断重复,直到小数部分为0或达到精度要求为止。
第一次所得到为最高位,最后一次得到为最低位。

如:

0.8125的二进制:

0.81252=1.625 取整是1
0.625
2=1.25 取整是1
0.252=0.5 取整是0
0.5
2=1.0 取整是1

即0.8125的二进制是0.1101(第一次所得到为最高位,最后一次得到为最低位)

0.1 的二进制:

乘数 乘2结果 整数部分 说明
0.1 0.2 0
0.2 0.4 0
0.4 0.8 0
0.8 1.6 1 减1
0.6 1.2 1 减1
0.2 0.4 0
0.4 0.8 0
0.8 1.6 1 减1
0.6 1.2 1 减1

可见,十进制 0.1 转为二进制后为 0.000110011001100110011... 无限循环小数

计算机浮点数表示

计算机存储浮点数遵循 IEEE 754 格式。

V = (-1)^S M 2^E

  • S(Sign/1bit):符号部分,当 S=0,V 为正数;当 S=1,V 为负数。
  • E(Exponent/8bits):指数部分。以2为底。需要注意的是,E 在这里是一个无符号整数,但是实际上指数是可以存在负数的,所以 IEEE 754 规定,E 需要偏移127(64位为1023),如,2^10 的指数是10,保存成32位浮点数时,必须保存成 10+127=137,即10001001。
  • M(Mantissa/23bits):表示有效数字,大于等于1,小于2。第一位 1 可以被舍去,只保留小数部分节省一位有效数字。

指数 E 还分为三种情况:

  1. E 不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
  2. E全为0。这时,浮点数的指数E等于 1-127(或者 1-1023),有效数字M不再加上第一位的1,而是还原为 0.xxxxxx 的小数。这样做是为了表示±0,以及接近于0的很小的数字。
  3. E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。

IEEE 754 中规定:

对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M;
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

计算误差

下面以双精度 double 类型的 0.1+0.2 为例

十进制 0.1
=> 二进制 0.00011001100110011...(循环0011)
=> 尾数为 1.1001100110011001100...1100(共52位,除了小数点左边的1),指数为 -4(偏移1023后二进制为 01111111011),符号位为0
=> 存储为:0 01111111011 1001100110011001100110011001100110011001100110011001
=> 因为尾数最多52位,所以实际存储的值为0.00011001 10011001 10011001 10011001 10011001 10011001 10011001

十进制0.2
=> 二进制0.0011001100110011...(循环0011)
=> 尾数为 1.1001100110011001100...1100(共52位,除了小数点左边的1),指数为-3(偏移1023后二进制为 1111111100),符号位为0
=> 存储为:0 01111111100 1001100110011001100110011001100110011001100110011001
因为尾数最多52位,所以实际存储的值为0.00110011 00110011 00110011 00110011 00110011 00110011 00110011

两者相加
0.00011001 10011001 10011001 10011001 10011001 10011001 10011001
+0.00110011 00110011 00110011 00110011 00110011 00110011 00110011
=0.01001100 11001100 11001100 11001100 11001100 11001100 11001100
转换成10进制之后得到:0.30000000000000004

可以发现,由于十进制下的部分小数无法使用二进制精确表示,所以会存在一定的计算误差。在平常应用中我们很少会遇到这个问题,比如遇到支付宝这样的货币系统,需要高精度的计算时,通常我们会定义精确到小数点后两位,然后使用整形来代替浮点型,即使用一分钱的整数倍。