今天的学习中遇到了 int型数据转换成float型精度丢失的问题,
明明float的表示范围更大怎么会丢失精度呢?
问题深究下来就关系到了浮点数的存储方式, 这里复习下计算机组成的课本内容, 也给不知道的朋友学习下
编程中用到的浮点数就是float和double, 长度分别是32位和64位,
浮点数是以二进制的 小数乘指数的方式存储的, 底数是2 不是10
以32位的float举例:
十进制的0.65625转换为二进制浮点数:
- 先换成二进制小数:0.10101(数部分用除2取余的方法, 小数部分用乘2取整的方法)
- 移位, 移到小数点前只剩1. : 1.0101*2^(-1) ( IEEE 754标准规定小数点前只留一位且是1,除非这个数是0这个过程叫规格化)
- 补全位数,1位符号位, 8位指数移码 ,23位尾数:
- 先说第一部分符号位,正数为0, 所以第一位是 0
- 尾数, 此例中就是1.0101,因为IEEE 754标准规定小数点前留1(为的是一个数的唯一表示,且防止全是0浪费位数),所以既然都是1.XXX就没必要存储这个1.了 ,直接存储小数点后的数即 0101, 补全23位也就是00000000000000000000101
- 剩下 指数部分了, 指数用移码表示, 即加上偏移量 ,对于32位的float偏移量是127(二进制01111111), 64位double偏移量1023(二进制001111111111),此例中就是 (-1)+01111111=01111110
把上边3部分连起来就得到了32位浮点数:00111111000000000000000000000101
学到这新同学们应该思考一下应该怎么把一个浮点数的2进制存储换回10进制数,就是上边过程的逆向
上边的例子是 32位的浮点数,64位的浮点数的分布如下: 1位符号位, 11位指数移码(偏移量1023),尾数52位,方法和上边是一样的
还有一些特殊的值如下表, S表示符号位,E表示加偏移量之前的指数,M表示去1.之前的尾数
学习到这里 , 我当时心里是有疑虑的, 指数为什么要加偏移量?多麻烦
大家可以查阅一些资料, 有时间可能会单写一个博客介绍为什么指数用移码表示
回到开篇的问题,
int型数据转换成float型精度丢失的问题, 明明float的表示范围更大怎么会丢失精度呢?
由上边的浮点数存储方式可以看出,32位浮点数有23位来拓展精度, int型整数4个字节,31位(1位符号位)全部用来拓展精度, 当int表示的数超过float所能表示的精度时, 只能靠丢失精度来近似表示了
说到这肯定还有很多人不理解,我们再以10进制举个简单的例子 ,
比如我定义8位十进制无符号的浮点数 , 4位表示指数,四位表示尾数,统一用0.XXX来格式化尾数,不存储0.
8位的整数全部表示十进制数
如果我把整数00001234化成浮点数即0.1234*10^4 = 0004|1234 (这里|分隔开指数和尾数)
那么我的整数如果是12345678呢, 按规则转化成浮点数是 0.12345678*10^8
按照我的规定,指数0008没问题,但是尾数却超出了4位,只能丢失一部分尾数来近似表达
也就是这样的8位浮点数虽然表示范围最大可以达到0.9999*10^(9999) ,但是却不一定能精确表示比这个值小的一个整数
二进制的道理是一样的, 当int的值大于2 ^(24) (或小于-2 ^(24) )时, (请大家自己想一下为什么临界值是2 ^(24),结合尾数位)转换成float就有可能发生精度的丢失.
注意上边的话,大于或小于临界值时转换有可能造成精度的丢失, 为什么是有可能?
回顾我自定义的10进制 浮点数的例子,
00123456无法用我定义的8位浮点数表示, 显然超过了我定义数的临界值
那么比这个数大的08000000呢?
不就是0008*10^(7)吗?位数补全就是0007|0008,
完全可以,没有精度丢失,而且离最大值差远了!
这又是为什么呢, 因为限制位数的浮点数能表示范围之内的数, 超过临界值是跳跃的,不能覆盖所有的整数(更别说实数了)
所以超过临界值 后就有可能无法精准表示范围内的整数, 这也就是int型数据转换成float型精度丢失的原因, 而转换成double就不会出现这个问题,因为double的尾数很长, 临界值比int最大值还要大,不可能造成精度丢失
大家明白了吗?慎用float, 尤其是关于钱的程序(好像对于金融领域double也不够)
因为水平有限,计算和概念可能出问题,欢迎沟通!
如有错误, 请大家及时指正,谢谢!