Float 的存储结构
问题导入
1 | In [1]: 0.1 + 0.2 |
简介
python 的 float 类型是由 C 语言的 double 类型实现的,double 类型对应 IEEE754 中的双精度实现方法。 double 类型的存储占用的空间大小在 64 位的机器上为 8 个字节,即 64 个比特位,其中 1 位为符号位,11 位为指数位,52 位为尾数位,如下图所示
浮点数的存储分为两种类型,单精度浮点数和双精度浮点数,分别对应 IEEE754 中的 float 和 double 类型。其中 float 类型占用 4 个字节,即 32 个比特位,double 类型占用 8 个字节,即 64 个比特位。
IEEE754 中,关于双精度浮点数存储定义为 1 位符号位,11 位指数位,52 位尾数位,如下图所示
1 | 0 1 2 3 |
sign
:符号位,0 代表正数,1 代表负数exponent
:指数位,用于表示浮点数的指数部分,用移码表示,即真实值加上 1023fraction
:尾数位,用于表示浮点数的尾数部分,用二进制表示,最高位默认为 1,即1.xxxxxx
,所以只需要存储小数点后面的 52 位即可
IEEE754 中,关于单精度浮点数存储定义为 1 位符号位,8 位指数位,23 位尾数位,如下图所示
1 | 0 1 2 3 |
- 使用 4 个字节,32 位进行存储
sign
符号位,占用一个位,0 代表正数,1 代表负数exponent
指数位,表示浮点的指数位,真实值加上 127,占用 8 个位fraction
尾数位,标识浮点的尾数部分,最高位默认为 1,即1.xxxxxx
,所以只需要存储小数点后面的 23 位即可
生成方法
- 判断符号位,如果是负数,将符号位设置为 1,否则设置为 0
- 将整数部分转换为二进制
- 将小数部分不断乘以 2,取整数部分,直到小数部分为 0
- 将整数和小数部分拼接起来,然后进行规格化,即小数点左移或者右移,使得小数点左边只有一个 1
- 最后将符号位、指数位和尾数位拼接起来,即为最终的二进制表示
末尾位舍入模式
任何有效数上的运算结果,通常都存放在较长的寄存器中,当结果被放回浮点格式时,必须将多出来的比特丢弃。 有多种方法可以用来运行舍入作业,实际上IEEE标准列出4种不同的方法:
- 舍入到最接近:舍入到最接近,在一样接近的情况下偶数优先(Ties To Even,这是默认的舍入方式):会将结果舍入为最接近且可以表示的值,但是当存在两个数一样接近的时候,则取其中的偶数(在二进制中式以0结尾的)。
- 朝+∞方向舍入:会将结果朝正无限大的方向舍入。
- 朝-∞方向舍入:会将结果朝负无限大的方向舍入。
- 朝0方向舍入:会将结果朝0的方向舍入。
举例
0.1 值双精度类型的存储计算方法
0.1 是正数,所以符号位为 0
整数部分为 0,所以整数部分的二进制为 0
小数部分为 0.1,不断乘以 2,取整数部分,直到小数部分为 0
1
2
3
4
5
6
7
8
90.1 * 2 = 0.2 -> 0
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
...所以小数部分的二进制为
000 1100 1100 1100 ...
将整数和小数部分拼接起来,得到
0.0001100110011001100110011001100110011001100110011001100
,然后进行移位,使得小数点左边只有一个 1,即1.100110011001100110011001100110011001100110011001100
,所以指数位为 -4,即 -4 + 1023 = 1019,所以指数位为 011 1111 1011最后得到的二进制表示为
0 011 1111 1011 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001
,后面还存在的数为1001 1001
,故根据舍入规则算最接近的数应该加 1,所以最后四位变为1001 + 1 = 1010
最后得到 16 进制表示为
3fb999999999999a
验证
1 | import struct |
1 | In [3]: float_to_hex(0.1) |
还原
按照拼接的方法进行组合将其拆为 0 011 1111 1011 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
第一位为 0,表示正数
指数位为 011 1111 1011,即 1019,减去 1023 得到 -4
即尾数位需要向左移动四个位获得
0 000 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101 0
1 | 0 = 0 * 2^-1 = 0 |
加起来为 0.1000000000000000055511151231257827021181583404541015625
问题解答
0.1 + 0.2 = 0.30000000000000004
- 0.1 的二进制表示为
0.0001100110011001100110011001100110011001100110011001100
- 0.2 的二进制表示为
0.0011001100110011001100110011001100110011001100110011010
- 相加后得到值为
0.0100110011001100110011001100110011001100110011001100110
- 即
0.3000000000000000444089209850062616169452667236328125
0.3 + 0.2 = 0.5
- 0.3 的二进制表示为
0.0100110011001100110011001100110011001100110011001100110
- 0.2 的二进制表示为
0.0011001100110011001100110011001100110011001100110011010
- 相加后得到值为
0.1000000000000000000000000000000000000000000000000000000
- 即
0.5