Do you really know why 0.1 0.3 + 0.2 is not equal to it?

Open chrome console, to a particularly simple input as follows:

0.1 + 0.2 // copy the code .30000000000000004

Do not know if you have surprised such a simple calculation, both in the js or in python, are not accurate 0.3, this is why?

origin

To understand this issue, we first need to know how to float in the end is stored in the computer? I do not know how you think, in short, my first reaction is to assume the start of a 32-bit memory space, I could follow storage integers to imagine, for example, 1-24-bit integer bit, the remaining eight representatives decimal ,Is this okay? Of course we can, but first consider the following question:

56365a334d4d4f13bf10904f9f3766a6


Imagine the red area is the maximum that can be placed in the digital space, and now there is a problem when we want to continue to add 0 found fit, because space is limited, at this time, how would we do?

8a5695d1febf4983a08efe13c1c84cc1


Yes, yes, scientific notation, is what we in the learning process, if the number of bits too, we will generally be expressed in scientific notation, such a benefit is a small number of bits of writing, multi-digit number, so back to the computer, to represent the 32-bit real number, it can represent up to how many? 2 ^ 32 th power, is about 4.0 billion 4 billion digital lot? Many, however, and an infinite number of real number set to more than, drop in the ocean, not enough to see, so the designers of computers is necessary to consider this issue, how to calculate down more numbers?

Really "fixed points"

Remember the above said 1-24 is an integer bits, the remaining bits represent the fractional do? This storage is called fixed point, every 4 1-24 indicates a digit from 0 to 9, then there may be 6 denotes the integer part, and the remaining two represent the fractional part, and we expressed from 0 to 32 such real 999999.99 100,000,000, which is represented by decimal binary manner, is called BCD code (Binary-coded decimal), for example 8,421 yards, sequentially from left to right is the right to 8,4,2, 1, and so on, we are interested can go to find out.

What are the problems "fixed points"

Fixed-point number has several distinct disadvantages:

  • They accounted for a large number of bits, but the range of numbers that can be represented is limited;

  • Also said that not a lot of numbers and very small numbers

其实究其根本原因,还是这种方式的“有限”限制了它,那么有没有一种方式,可以让32位所能表示的数字,更“无限”一点,更适合我们的诉求?

当然,设计计算机的前辈智慧是无限的~

浮点数是如何表示的

就像使用科学计数法一样,计算机前辈在浮点数的设计中也用了一样的思想,IEEE的标准定义了2个基本的浮点数格式,一个是32位的单精度浮点数,一个是64位的双精度浮点数,也就是float或float32和double或float64这两个数据格式,双精度和单精度的表示形式是差不多的,我们以单精度的作为了解和学习。

da8c56a474ae479bb941f324440b5d9b


分为3部分:

  1. 第一部分是符号位,用s表示,代表正负,要记住的是在浮点数的范围内,所有数字都是有符号的;

  2. 第二部分是指数位,用e表示,代表指数,用8位bit表示的数字范围是0~255,为了同时表示大数和小数,我们把0~255去掉头尾(0,255后面会用到)的1~254去映射到-126~127,这样同时可以表示最大最小数字;

  3. 第三部分是有效数位,用f表示,代表的是有效的数位;

综合上述表示和科学计数法,我们的浮点数就可以表示为公式

(-1)^s * 1.f * 2^e

看完公式有没有发现问题?你会发现,我们这个公式无法表示0,的确,这是一个巧妙的设计,我们用0(8个bit都为0)和255(8个bit都为1)来表示一些特殊的数值,可以认为他们2个是特殊的flag位,比如当e和f都为0的时候,我们就认为这个浮点数是0,看下表:

84ee01d094654783a657410129e7f02b


以0.5为例,0.5的符号位s是0,f也是0,e是-1,

这样(-1)^0 * 1.0 * 2 ^ -1 = 0.5

用32位bit表示就是

s e f 0 0111 1110 0000 ...0 1位 8位 23位 0.5 通过这样的表示方式,可以明显的发现32位所能表示的实数范围是很大的,又因为这种方式创建的实数中小数点的位置是可以”浮动“的,所以也被叫做浮点数,

到这里我们知道了浮点数是怎么存储的了,但是还没解决我们开始的问题,为何0.1+0.2!=0.3,首先我们要知道0.1是怎么存储的:

(-1)^s * 1.f * 2^e = 0.1

求解e

s=0 f=0 e=Math.log2(0.1) // -3.321928094887362

可以看出来这里0.1是算不出来一个准确数字的,从0.1到0.9只有0.5是可以求出一个准确的值的,剩下的都算不出来一个准确的值,这也就是为什么0.1+0.2会导致的精度问题,也就是说浮点数无论是表示还是计算其实都是近似计算,而近似计算就一定会导致一些问题,比如,你希望银行给你存钱以及算利息的时候用浮点数计算吗?当然不希望,否则你的钱算多了还好,算少了岂不是亏大了~

浮点数&二进制

把一个二进制表示的浮点数(0.1001),转为10进制表示,因为小数点后的每一位都表示的是2的-N次方,因此转为10进制就是:

(1 * 2 ^ -1) + (0 * 2 ^ -2) + (0 * 2 ^ -3) + (1 * 2 ^ -4) = 0.5625

可以理解为,对于二进制转十进制来说,从小数点开始,往左就是把2的指数从0开始过一位+1,包括0,往右就是从-1开始依次-1。

把一个10进制的浮点数,转为二进制的话,和整数的二进制表示采用“除以 2,然后看余数”的方式相比,小数部分转换是用一个相似的反方向操作,就是乘以2,然后看是否大于1,如果大于1就记下1并把结果减去1,一直重复操作。

比如,十进制的9.1,小数部分0.1转为2进制的过程为:

1a484ce1c05b4218badbfc1d1d8897ae


这是得到一个无限循环的部分”0011“,整数部分9转为二进制就是1001,因此结果就是1001.000110011...

把小数点做移3位,得到一个浮点数的结果是 1.001000110011... * 2 ^ 3

找到我们上面的公式 (-1)^s * 1.f * 2^e 套公式可得到:

s = 0 f = 00100011001100110011 001(到23位后自动舍弃,因为最长只能放23位有效数字)

指数位是3,我们e的范围是1-254 对半分正数和负数,所以127表示0,从127开始加3,得到结果是130,130转为二进制表示结果就是: 1000 0010, 所以得到e=1000 0010, 结果如下:

6c33cac8d87946f7946863682dcda093


所以最终的二进制表示结果是: 0100 0001 0001 0001 1001 1001 1001 1001

如果我们再把这个浮点数表示换算成十进制, 实际得到的准确值是 9.09999942779541015625。相信你现在应该不会感觉奇怪了。

小心你的“存款”

首先,我们了解一下浮点数的加法计算过程是怎么样的,拿0.5 + 0.125来做计算,首先0.5套用公式计算结果是:

s = 0 有效位1.f = 1.0000... e = -1;

0.125 转换为:

s = 0 有效位1.f = 1.0000... e = -3;

然后,计算口诀是 指数位先对齐(小转大,这里要把e统一为-1), 然后按位相加符号位和有效位,e保持统一后的结果,因此:

符号位s 指数位e 有效位1.f 0.5 0 -1 1.0 0.125 0 -3 1.0 0.125对齐指数位 0 -1 0.01 0.5 + 0.125 0 -1 1.01 结果就是 (-1)^0 * 1.25 * 2^-1 = 0.625;

ps: 为啥是1.25?虽然我们计算得出的是1.01 但是不要忘记计算是通过2进制算的,计算十进制的时候要转回来哦,所以0100000.... 后面都是0不用管,小数部分,从头开始乘以2的-N次别忘了,所以结果就是2^-2 = 0.25 加上整数位的1 就是1.25了~

可以发现,其实浮点数的计算过程,通过一个加法器也是可以实现的,电路成本同样不会很高,但是需要注意一些别的问题:

计算过程中,需要先对齐,但是有效数位的长度是23位,假如有一个很大的数字和一个很小的数字进行相加,然后对齐的过程中,小数被0部位过程中直接溢出了,23位不够用了,就会出现问题,补完后一些有效位被丢掉了,从而导致结果上的误差,两个数的指数位差超过23,比如到2^24位(差不多1600万倍),这2个数相加后,结果就直接是较大数,较小数完全被抛弃了。。。

有些同学会急急忙忙去chrome的控制输入下面的代码:

Math.pow(2, 24) + 0.1 // 16777216.1 复制代码

骗人,结果不是还有0.1吗,别急,小伙伴,js内置的Number是64位的,你可以试试

Math.pow(2, 50) + 0.1 // 1125899906842624复制代码

是不是小数没了?【这种现象也叫大数吃小数】

所以如果银行采用IEEE-754 32位的浮点数计数方法来保管存款的话,假设你是一个大老板,你的账户中有2000万rmb,这个时候你的某一个员工给你打了1块钱,哈哈对不起,银行给算丢了,你的存款是不变的!所以,一般银行啊,电商一类的都会在涉及到钱的时候使用定点数或者整数来计算,避免出现精度丢失的问题,如果你去银行涉及数据库,一定要小心谨慎~

总结

这篇文章我们从浮点数的表示开始,到存储,到转换以及计算过程分析了真实的计算机世界中浮点数到底是怎么运行的,从中也了解了浮点数究竟为何会丢失精度:

  1. 浮点数在存储的时候可能出现不能准确转化为对应2进制的情况

  2. 在计算过程中,又存在大数吃小数的可能,也会导致数据不准确

延伸

精度丢失不是没法解决的,有成熟的方案,不做过多介绍,有兴趣大家可以去研究:

Summation Formula 算法

说明:

文章内容大部分参考自 徐文浩 老师的 「深入浅出计算机组成原理」专栏,加了一些自己的理解做了一个简单的总结,之后还会继续不定时的分享一些自己的所得,如果觉得还不错,点个赞吧~

ps: Some students might ask, since only 0.5 can be converted to an accurate figure, why not 0.1 + 0.1 problem, I have not carefully studied this, but my guess is because the calculation itself is a process of approximation calculation, so then get after the results, if the range is still an approximation, no error will think over this range, it will think the error occurred, in short, we can confirm that the calculation process is indeed get an approximate number, and this also indeed the cause of the loss of some precision floating-point computations -

Are interested can go here to see details of the actual number stored in the computer -

7a7095af9afe42929f706a7e8026e5d4


Guess you like

Origin blog.51cto.com/14516511/2435453