浮点数与定点数

定点数

假设32位数,我们用 4 个比特来表示 0~9 的整数,那么 32 个比特就可以表示 8 个这样的整数。

然后我们把最右边的 2 个 0~9 的整数,当成小数部分;把左边 6 个 0~9 的整数,当成整数部分。这样,我们就可以用 32 个比特,来表示从 0 到 999999.99 这样 1 亿个实数了

这种用二进制来表示十进制的编码方式,叫作BCD 编码(Binary-Coded Decimal)

缺点:

1.浪费,本来 32 个比特我们可以表示 40 亿个不同的数,但是在 BCD 编码下,只能表示 1 亿个数

2.这样的表示方式没办法同时表示很大的数字和很小的数字


浮点数

在计算机里,我们也可以用一样的办法,用科学计数法来表示实数。浮点数的科学计数法的表示,有一个 IEEE 的标准,它定义了两个基本的格式。一个是用 32 比特表示单精度的浮点数,也就是我们常常说的 float 或者 float32 类型。另外一个是用 64 比特表示双精度的浮点数,也就是我们平时说的 double 或者 float64 类型

双精度类型和单精度类型差不多,这里,我们来看单精度类型,双精度你自然也就明白了


单精度的 32 个比特可以分成三部分

1.符号位,0表示正

2.指数位8个比特。我们一般用 e 来表示。8 个比特能够表示的整数空间,就是 0~255。我们在这里用 1~254 映射到 -126~127 这 254 个有正有负的数上。

因为我们的浮点数,不仅仅想要表示很大的数,还希望能够表示很小的数,所以指数位也会有负数。这里我们没有用到 0 和 255 。没错,这里的 0(也就是 8 个比特全部为 0) 和 255 (也就是 8 个比特全部为 1)另有它用

3.最后,是一个 23 个比特组成的有效数位。我们用 f 来表示

综合科学计数法,浮点数可以这样表示


这里的浮点数,没有办法表示 0。的确,要表示 0 和一些特殊的数,我们就要用上在 e 里面留下的 0 和 255 这两个表示,这两个表示其实是两个标记位。在 e 为 0 且 f 为 0 的时候,我们就把这个浮点数认为是 0。至于其它的 e 是 0 或者 255 的特殊情况,你可以看下面这个表格,分别可以表示出无穷大、无穷小、NAN 以及一个特殊的不规范数


我们可以以 0.5 为例子。0.5 的符号为 s 应该是 0,f 应该是 0,而 e 应该是 -1,也就是0.5 = (−1)^0 × 1.0 × 2^(-1) = 0.5,对应的浮点数表示,就是 32 个比特。


s = 0,e = 2^-1,需要注意,e 表示从 -126 到 127 个,-1 是其中的第 126 个数,这里的 e 如果用整数表示,就是 2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^1 = 126, 1.f = 1.0

在这样的浮点数表示下,不考虑符号的话,浮点数能够表示的最小的数和最大的数,差不多是 1.17×10^−38 和 3.40×10^38。比前面的 BCD 编码能够表示的范围大多了


当我们在python控制台或者是浏览器控制台输入0.3 + 0.6

>>> 0.3 + 0.6
0.8999999999999999

这是因为,浮点数没有办法精确表示 0.3、0.6 和 0.9。事实上,我们拿出 0.1~0.9 这 9 个数,其中只有 0.5 能够被精确地表示成二进制的浮点数,也就是 s = 0、e = -1、f = 0 这样的情况


浮点数二进制表示

我们输入一个任意的十进制浮点数,背后都会对应一个二进制表示。比方说,我们输入了一个十进制浮点数 9.1。那么按照之前的讲解,在二进制里面,我们应该把它变成一个“符号位 s+ 指数位 e+ 有效位数 f”的组合。第一步,我们要做的,就是把这个数变成二进制

首先,我们把这个数的整数部分,变成一个二进制。这个我们前面讲二进制的时候已经讲过了。这里的 9,换算之后就是 1001

接着,我们把对应的小数部分也换算成二进制。我们先来定义一下,小数的二进制表示是怎么回事。我们拿 0.1001 这样一个二进制小数来举例说明。和上面的整数相反,我们把小数点后的每一位,都表示对应的 2 的 -N 次方。那么 0.1001,转化成十进制就是

1 x 2^-1 + 0 x 2^-2 + 0 x 2^-3 + 1 x 2^-4 = 0.5625

小数部分转换成二进制是用一个相似的反方向操作,就是乘以 2,然后看看是否超过 1。如果超过 1,我们就记下 1,并把结果减去 1,进一步循环操作。在这里,我们就会看到,0.1 其实变成了一个无限循环的二进制小数,0.000110011。这里的“0011”会无限循环下去


然后,我们把整数部分和小数部分拼接在一起,9.1 这个十进制数就变成了 1001.000110011…这样一个二进制表示

用科学记数法表示为

1.001000110011... x 2^3

那这个二进制的科学计数法表示,我们就可以对应到了浮点数的格式里了。这里的符号位 s = 0,对应的有效位 f=001000110011…。因为 f 最长只有 23 位,那这里“0011”无限循环,最多到 23 位就截止了。于是,f=00100011001100110011 001。最后的一个“0011”循环中的最后一个“1”会被截断掉。对应的指数为 e,代表的应该是 3。因为指数位有正又有负,所以指数位在 127 之前代表负数,之后代表正数,那 3 其实对应的是加上 127 的偏移量 130,转化成二进制,就是 130,对应的就是指数位的二进制,表示出来就是 10000010


然后,我们把“s+e+f”拼在一起,就可以得到浮点数 9.1 的二进制表示了。最终得到的二进制表示就变成了:

010000010 0010 0011001100110011 001

如果我们再把这个浮点数表示换算成十进制, 实际准确的值是 9.09999942779541015625,这就是精度损失的由来

所以当运算次数非常多,精度损失就会非常大,此时可以引入 Kahan Summation 算法,就是在每次的计算过程中,都用一次减法,把当前加法计算中损失的精度记录下来,然后在后面的循环中,把这个精度损失放在要加的小数上,再做一次运算


上一篇: 电路-cpu运算
下一篇: cpu-硬件性能
作者邮箱: 203328517@qq.com