浮点数探寻(一)我们常说的误差从哪里来

$f = 19.58;
var_dump(intval($f * 100)); //为啥输出1957

上面是一段PHP代码,肉眼可见的1958为什么变成了1957,这就要从计算机是怎么保存浮点数开始说起了。计算机处理小数时要把我们的十进制转化为二进制,而在转化成二进制的过程中,就会不可避免的丧失精度。现代的编程语言基本都用IEEE754标准来定义浮点数的二进制格式,我们来看一下如何把19.58转化成符合 IEEE754 标准的二进制数。

首先IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。常用的就是单精度和双精度,C语言中float通常指32位单精度,double指64位双精度。而PHP中只有float一种表示浮点数的数据类型,float在32位平台中使用的就是单精度标准,在64位平台中使用的双精度标准。下面为了便于计算,我们用32位单精度来计算十进制19.58的值。

先看一下IEEE754中单精度的组成

符号位指数位小数位
1位8位23位

符号位和小数位很好理解,那么什么是指数位?计算机用类似科学计数法的方式表示小数,举例十进制中

, 那么1就是这里面的指数位

二进制也类似,比如二进制小数0.0000101就是 , 这里-5就是指数位, 在二进制小数的科学计数法中,我们这么表示小数:

(这里说一下为什么非要以1.xxx开头,这是为了节约小数位,如果不就浪费了4位小数位存0了么。并且如果所有的浮点数都是这种格式,我们在存储中也可以忽略小数点前的1.这个整数位了,直接存小数位就可以了)

需要注意的是,浮点数指数位在计算机中不是以原码表示的,而是以移码表示的(有对原码,反码,补码不清楚的同学可以自行百度)。这里简单的说一下移码,比如8位二进制(最大正数为255)我们就设中间数127为0

10000001  (十进制129)   2

10000000  (十进制128)   1

01111111   (十进制127)    0

01111110   (十进制126)   -1

01111101   (十进制125)   -2

移码的移就是偏移量的意思(距离我们定义的0的距离)

讲完基本概念,我们来转化一下19.58这个十进制数

19 / 2 = 9  余 1

9  /  2 = 4  余 1

4  /  2 = 2  余 0

2  /  2 = 1  余 0

1  /  2 = 0  余 1

现在整数位确定了为10011

我们再转化小数位(小数转化规则:小数部分乘以2,取整数部分,直至乘积小数部分为0或超过小数位最大能表示的数):

0.58 * 2 = 1.16

0.16 * 2 = 0.32

0.32 * 2 = 0.64

0.64 * 2 = 1.28

0.28 * 2 = 0.56

0.56 * 2 = 1.12

0.12 * 2 = 0.24

0.24 * 2 = 0.48

0.48 * 2 = 0.96

0.96 * 2 = 1.92

......(就乘到这里吧,目测小数位不会等于0了,这也是损失精度的原因)

小数位从上到下就是1001010001

19.58二进制表示约等于10011.1001010001, 科学计数法就是

指数位127+4 = 131 = 10000011

符号位指数位小数位
01000001100111001010001...

 

为了证明以上不是我编的,我们验证一下

当然,他的精度要比我们高几位(我没有算完),但是前面几位是完全一样的,到了这里,我们就完整的用IEEE754表示了19.58这个浮点数了

那么假如我们要把它转回十进制呢?

10011.1001010001  整数位  = 19 , 小数位 

误差由此而来,因为我们在转换小数位的时候不能通过*2彻底地把小数位变成0,所以精度会有偏差,原理类似

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值