查看Python的浮点数如何在内存中存储
下面的探讨主要针对Python3,在Python2中不一定适用。
Python的浮点数实现原理:
CPython实现有一个PyFloatObject的结构体,用来构造Python的浮点数类型:
typedef struct {
PyObject_HEAD # 这个对象包含:引用计数+对象类型,占8+8=16字节
double ob_fval; # 这个是存储浮点数的地方,Python的浮点数就是C的double,双精度
} PyFloatObject;
所以Python的浮点数类型占24字节:
引用计数+对象类型+双精度浮点数 = 8+8+8 = 24字节
不过Python3的整数长度无限,所以占字节数不定
用Python代码验证浮点数:
代码:
1 | from ctypes import string_at |
探讨一下
十进制数 134.375 如何转换为二进制浮点数,并存储在内存中的:
134.375 等于二进制的 10000110.011,转换为IEEE754的浮点数格式:1.0000110011x2^7,其中首位1隐藏,尾数位=0000110011,符号位=0,指数位=(7+1023)的二进制=10000000110
134.375转换二进制小数的方法详见 浮点数在内存中是怎么存储的?
十进制小数部分最后一位不是5时,小数部分与2相乘不会得到1.0,就无法准确转换为二进制,比如134.372,小数0.372如果允许60个二进制位,将转换为:
010111110011101101100100010110100001110010101,100000010000000
整数部分134二进制为:10000110,首位1隐藏,变成0000110(7位),双精度52位尾数-7位整数部分=45位供小数存储。因为小数0.372无法准确转换为二进制,第46进行“向偶数舍入” [1],变成:
010111110011101101100100010110100001110010110
010111110011101101100100010110100001110010101,100000010000000 (原60位小数)
用代码验证
1 | import struct |
十进制数转换浮点数步骤(双精度)
1、整数部分转二进制:可以精确转换,假如数值为134.375
2、小数部分转二进制:可能无法精确转换,当小数最后一位是5时,一定能准确转换,转换后为10000110.011
3、写成规范化形式:1.0000110011x2^7
4、隐藏首位1:变成0000110011,这是尾数部分
5、计算符号位:正数0,负数1
6、计算指数位:双精度浮点数偏移=2^(指数位11-1)-1=1023,指数7+偏移1023=1030=10000000110(二进制)
7、最终内存中的样子:得到符号(S=1位)+指数(E=11位)+尾数(M=52位)的64比特二进制=0,10000000110,0000110011000000000000000000000000000000000000000000
浮点数(双精度)转换十进制数步骤
1、提取尾数位:补全首位的到,1.0000110011000000000000000000000000000000000000000000
2、指数移位操作:指数位10000000110=1030,1030-偏移1023=7,对尾数右移位7(负指数向左移位)=10000110.011000000000000000000000000000000000000000000
3、转换二进制整数+小数部分:1x2^7+0x2^6...0x2^0.0x(2^-1)+1x(2^-2)+1x(2^-3)...=134.375
Python的struct.pack()与struct.unpack()
python的struct主要用来处理C结构数据。主要有如下两个方法 [2]:
struct.pack(fmt, v1, v2, …)
struct.unpack(fmt, string)
下面用struct来验证python的浮点数是不是C的double:
可以看到,上面截图的两段代码显示的134.375浮点数的S、E、M是完全一样的。
浮点数的IEEE754标准
有效位计算方法1
一个十进制位需要多少二进制位来表示:
大约需要3.322个二进制位来表示一个十进制位,所以:
单精度浮点数24(23尾数位+1隐藏位)位二进制可以表示大约:24/3.322≈7.225 位的十进制。
可见单精度浮点数的准确有效位是7(有效位表示非零整数部分+小数部分的数字位数,如1.23有效位是3,0.1234有效位是4)。
双精度浮点数的有效位同理计算可得:53/3.322≈15.95,有效位是15,总的来说:
1、单精度浮点数是4字节32位,符号位+指数位+尾数位=1位+8位+23位,有效位7~8位
2、双精度浮点数是8字节64位,符号位+指数位+尾数位=1位+11位+52位,有效位15~16位 [3]
有效位计算方法2
1、单精度浮点数有效数24位全是1时:10^7 < 2^24-1=16777215 < 10^8
所以单精度浮点数能准确表示小数点后第7位,第8位部分准确。16777215是能够转化为单精度浮点数表示的整数的最大精度,超过这个数会进行“舍入”导致丢失精度。
2、双精度浮点数有效数53位全是1时:10^15 < 2^53-1=9007199254740991 < 10^16
所以双精度浮点数能准确表示小数点后第15位,第16位部分准确。9007199254740991是能够转化为双精度浮点数表示的整数的最大精度,超过这个数会进行“舍入”导丢失精度。[4]
Python的sys.float_info详解
sys.float_info输出内容:
1 | In [202]: sys.float_info |
1、max表示的就是最大规约数(即远离0的很大的数,加个负号成为最小规约数);
2、radix表示的是在电脑中储存的基数,二进制显然是2;
3、max_exp表示的是使得radix**(e-1)为可表示的有限浮点数最大指数,由于移码后的规约数的指数范围为-1022~1023,即最大为1023,所以最大的e自然就是1024;
4、max_10_exp表示的是让10**e为一个可表示的浮点数最大的指数,从结果1.7976931348623157e+308,得出max_10_exp自然就是308;
5、min表示最小的正规约数(即靠近0的很小的数,加个负号为靠近0的负的很大的数);
6、min_exp表示的是使得radix**(e-1)为可表示的有限浮点数最小指数,移码后的最小指数为-1022(尽管指数为0时,设定偏移量为1022,移码后的指数依然为-1022),因此最小的e为-1021;
7、min_10_exp表示的是让10**e为一个可表示的规约浮点数最小的指数,从结果2.2250738585072014e-308,得出这时最小的e为-307(要注意不是-308,因为10**(-308)比最小正规约数2.2250738585072014e-308还小,这是不符合要求的,所以应该是-307);
8、dig表示可以保证被精确的转为浮点数的整数的数字个数,保证被精确的转为浮点数的整数应该小于等于9007199254740991,该数有16位数字,但是大于该数的16位数字的整数是无法被精确的转为浮点数的,所以能确保精确的有效位是15;
9、mant_dig就是mantissa digits,即尾数位数,因为尾数的首位1被隐藏,所以真正的尾数位数共有52+1=53位;
10、epsilon表示最小的大于1的浮点数和1之间的差值;
11、rounds表示的是当一个整数转成浮点数,对无法精确表示的整数的近似模式,这里为1表示的是取距离原值最近的浮点数表示;[5]
Python浮点数举例
首先,双精度浮点数的全部53有效位可表示的最大十进制数是(53位全是1的情况):
2**53-1=9007199254740991
看一个更长的小数:
In [220]: 0.123456789123456789
Out[220]: 0.12345678912345678
为什么得到的浮点数是0.12345678912345678?它的小数位有17个,难道有效位变成了17?
其实有效位是16,因为0.12345678912345678中的1234567891234567才是精确的,最后的8是舍入后的值。那为什么是16个有效位?
因为上文说了双精度浮点数有效位15位是精确的,第16位部分精确,而1234567891234567<9007199254740991,1234567891234567这个值并没有完全填满53位尾数,当然是可以的。
舍入的那个值举例:
In [264]: 0.123456789123456749
Out[264]: 0.12345678912345674
In [265]: 0.123456789123456759
Out[265]: 0.12345678912345676
可以明显看到最后一位4或6是舍入值,因为它变大还是变小是不精确的,这取决于浮点数的舍入规则。[6]
参考:
[1] 整数转浮点数精度溢出的原因和处理方式
[2] Python中struct.pack()和struct.unpack()用法详细说明
[3] 浮点数的有效数字位数、浮点数(单精度、双精度数)的有效位、深入理解浮点数有效位
[4] 理解浮点数的二进制表示、整数转浮点数精度溢出的原因和处理方式、浮点数标准详解参考
[5] 浮点数的各种最值推算以及对python sys.float_info的解释、整数转浮点数精度溢出的原因和处理方式、python文档
[6] 整数转浮点数精度溢出的原因和处理方式