C++人该知道的N个问题与做法:浮点数在计算机中的存储方式与精度丢失问题(float,double)

浮点数在计算机中的存储方式

对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit。

不论是float还是double在存储方式上都是遵从IEEE的规范的

    无论是单精度还是双精度在存储中都分为三个部分:

  1. 符号位(Sign) : 0代表正,1代表为负   
  2. 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储  
  3. 尾数部分(Mantissa):尾数部分

单精度与双精度的存储方式如下图所示:

                                  

如上图:float的存储为1bit符号位,8bit指数位,23尾数位

               double的存储为1bit符号位,11bit指数位,52尾数位

存储方式都是用科学计数法来存储数据的,比如8.25用十进制的科学计数法表示就为:8.25*(10^0),而120.5可以表示为:1.205*(10^2),在计算机存储中,首先要将上面的数更改为二进制的科学计数法表示。

8.25用二进制表示为1000.01,即为1.00001*(2^3)

120.5用二进制表示为1111000.1,即为1.1110001*(2^6)

IEEE 对有效数字M和指数E,还有一些特别规定。

当1≤M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

对于指数部分,因为指数可正可负,位的指数位能表示的指数范围就应该为:-127-128,指数部分的存储采用移位存储,存储的数据为元数据+127,你也可以理解为指数部分的首位为符号位,1为正,0为负(虽然这是不严谨的)。

指数E还可以再分成三种情况:

(1)E不全为0或不全为1这时,浮点数就采用上面的规则表示,即指数E的计算值减去127得到真实值,再将有效数字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)。

所以按照上面的介绍;

8.25(1000.01)的存储为      【0】【1000 0010】 【000 0100 0000 0000 0000 0000】

                                             符号为正    指数为3                         尾数00001

120.5(1111000.1)的存储为       【0】【1000 0101】【111 0001 0000 0000 0000 0000】

                                                   符号为正    指数为6                        尾数1110001

而双精度浮点数的存储和单精度的存储大同小异,不同的是指数部分和尾数部分的位数。

8.25(1000.01)的存储为      【0】【100 0000 0010】 【0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000】

                                              符号为正        指数为3                                                     尾数00001

那么问题来了:

精度问题

float单精度的2.2转换为double双精度后,精确到小数点后14位后变为了2.20000004768372,而单精度的2.25转换为双精度后,变为了2.25000000000000,为何2.2在转换后的数值更改了而2.25却没有更改呢?

其实通过上面关于两种存储结果的介绍,我们已经大概能找到答案。

2.25的单精度存储方式: 0 1000 0001 001 0000 0000 0000 0000 0000,

2.25的双精度存储方式: 0 100 0000 0001 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,

这样2.25在进行强制转换的时候,数值是不会变的,而我们再看看2.2呢,2.2用科学计数法表示应该为:将十进制的小数转换为二进制的小数的方法为将小数*2,取整数部分,所以得到的二进制是一个无限循环的排列 000110011001100110011... 

       2.2的float存储为   :  【0】【1000 0001】【000 1100 1100 1100 1100 1100】 (1100无限循环)

对于单精度数据来说,尾数只能表示24bit的精度,所以当再将其转换成10进制之后就不再是2.2了

而double类型的数据也存在同样的问题,所以在浮点数表示中会产生些许的误差,在单精度转换为双精度的时候,也会存在误差的问题。

这时就会有人很疑问,为什么有的是.999999...而有的却是.00000001...,这就要看下面的舍入规则了;

舍入规则

  以23位尾数位的单精度浮点数为例,舍入时需要重点参考第24位

  若第24位为1,且第24位之后全部为0。此时就要使第23位为0:若第23位本来就是0则不管,若第23位为1,则第24位就要向第23位进一位,这样第23位就可以为0

  若第24位为1,且第24位之后不全为0,则第24位就要向第23位进一位完成上舍入。

  若第24位为0,此时直接舍去不进位,称为下舍入。

 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿尔兹

如果觉得有用就推荐给你的朋友吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值