python 中的 Decimal 对象究竟是如何编码的?
How exactly is a Decimal object encoded in python?
我目前正在使用 python (v3.8.5) 中的 decimal.Decimal
编写代码。
我想知道是否有人知道 Decimal 对象的实际编码方式。
不明白为什么改了getcontext().prec
,内存大小还是一样,等于改十进制浮点数的系数和指数,如下
from decimal import *
from sys import getsizeof
## coefficient bits = 3
getcontext().prec = 3
temp = Decimal('1')/Decimal('3')
print(temp.as_tuple()) >>> DecimalTuple(sign=0, digits=(3, 3, 3), exponent=-3)
print(getsizeof(temp)) >>> 104
## coefficient bits = 30
getcontext().prec = 30
temp = Decimal('1')/Decimal('3')
print(temp.as_tuple()) >>> DecimalTuple(sign=0, digits=(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), exponent=-30)
print(getsizeof(temp)) >>> 104
为了理解上面的行为,我阅读了Decimal的源代码class和附件
- https://github.com/python/cpython/blob/main/Lib/_pydecimal.py
- http://speleotrove.com/decimal/decarith.html
- http://speleotrove.com/decimal/decbits.pdf
根据文档,Python的Decimal对象是基于IEEE 754-2008实现的,使用DPD(Densely packed decimal)编码将系数延拓的十进制数字转换为二进制数字。
因此,根据DPD算法,我们可以计算出系数延拓的十进制位编码成二进制位的位数。
并且由于符号、指数延续、组合域都是简单的用二进制表示,编码时的位数很容易计算出来。
因此,我们可以通过以下公式计算Decimal对象编码时的位数。
bits = (sign) + (exp) + (comb) + (compressed coeff)
这里sign和combination分别固定为1bit和5bits(根据IEEE 754-2008的定义。https://en.wikipedia.org/wiki/Decimal_floating_point)
所以,我写了上面的代码,使用 Decimal 对象的 as_tuple()
检查 {sign, exponent, coefficient} 的列表,并计算内存中的实际位数。
然而,如上所述,Decimal 对象的内存大小根本没有改变,即使系数中的位数应该已经改变。
(我理解Decimal对象不仅是十进制编码,还是列表等对象。)
出现以下两个问题。
(1)我对python中Decimal对象的编码算法理解有误吗?
(python3.8.5 是否使用了比 IEEE 754-2008 更高效的编码算法?)
(2) 假设我对算法的理解是正确的,为什么Decimal对象的内存大小保持不变,即使系数已经改变?
(根据IEEE754-2008的定义,系数续延改变时,指数续延也随之改变,总位数也要改变。)
我自己是一名平时就读机械工程专业的学生,信息学完全是初学者。
如果我原来的理解有什么地方不对,或者有什么奇怪的逻辑发展,请告诉我。
感谢您的帮助。
Only the memory consumption directly attributed to the object is accounted for, not the memory consumption of objects it refers to.
因为 Decimal
是一个 Python class 并且引用了其他几个对象(编辑:见下文),你只需要得到引用的总大小,它是常量 -不包括引用值,这些值不是。
getcontext().prec = 3
temp = Decimal(3) / Decimal(1)
print(sys.getsizeof(temp))
print(sys.getsizeof(temp._int))
getcontext().prec = 300
temp = Decimal(3) / Decimal(1)
print(sys.getsizeof(temp)) # same
print(sys.getsizeof(temp._int)) # not same
(请注意,我在示例中使用的 _int
插槽是 CPython 的 Decimal
的内部实现细节,如前导下划线所暗示;此代码不是保证在其他 Python 实现中工作,甚至在其他版本中工作。)
编辑: 糟糕,我的第一个答案是在旧的 Python 上,其中 Decimal
在 Python 中实现。你问的那个版本有 implemented in C.
C 版本实际上将所有内容都存储在对象本身内部,但是您在精度上的差异不足以检测到差异(因为内存是在离散块中分配的)。尝试使用 getcontext().prec = 300
代替。
我目前正在使用 python (v3.8.5) 中的 decimal.Decimal
编写代码。
我想知道是否有人知道 Decimal 对象的实际编码方式。
不明白为什么改了getcontext().prec
,内存大小还是一样,等于改十进制浮点数的系数和指数,如下
from decimal import *
from sys import getsizeof
## coefficient bits = 3
getcontext().prec = 3
temp = Decimal('1')/Decimal('3')
print(temp.as_tuple()) >>> DecimalTuple(sign=0, digits=(3, 3, 3), exponent=-3)
print(getsizeof(temp)) >>> 104
## coefficient bits = 30
getcontext().prec = 30
temp = Decimal('1')/Decimal('3')
print(temp.as_tuple()) >>> DecimalTuple(sign=0, digits=(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), exponent=-30)
print(getsizeof(temp)) >>> 104
为了理解上面的行为,我阅读了Decimal的源代码class和附件
- https://github.com/python/cpython/blob/main/Lib/_pydecimal.py
- http://speleotrove.com/decimal/decarith.html
- http://speleotrove.com/decimal/decbits.pdf
根据文档,Python的Decimal对象是基于IEEE 754-2008实现的,使用DPD(Densely packed decimal)编码将系数延拓的十进制数字转换为二进制数字。
因此,根据DPD算法,我们可以计算出系数延拓的十进制位编码成二进制位的位数。
并且由于符号、指数延续、组合域都是简单的用二进制表示,编码时的位数很容易计算出来。
因此,我们可以通过以下公式计算Decimal对象编码时的位数。 bits = (sign) + (exp) + (comb) + (compressed coeff)
这里sign和combination分别固定为1bit和5bits(根据IEEE 754-2008的定义。https://en.wikipedia.org/wiki/Decimal_floating_point)
所以,我写了上面的代码,使用 Decimal 对象的 as_tuple()
检查 {sign, exponent, coefficient} 的列表,并计算内存中的实际位数。
然而,如上所述,Decimal 对象的内存大小根本没有改变,即使系数中的位数应该已经改变。 (我理解Decimal对象不仅是十进制编码,还是列表等对象。)
出现以下两个问题。
(1)我对python中Decimal对象的编码算法理解有误吗? (python3.8.5 是否使用了比 IEEE 754-2008 更高效的编码算法?)
(2) 假设我对算法的理解是正确的,为什么Decimal对象的内存大小保持不变,即使系数已经改变? (根据IEEE754-2008的定义,系数续延改变时,指数续延也随之改变,总位数也要改变。)
我自己是一名平时就读机械工程专业的学生,信息学完全是初学者。 如果我原来的理解有什么地方不对,或者有什么奇怪的逻辑发展,请告诉我。
感谢您的帮助。
Only the memory consumption directly attributed to the object is accounted for, not the memory consumption of objects it refers to.
因为 Decimal
是一个 Python class 并且引用了其他几个对象(编辑:见下文),你只需要得到引用的总大小,它是常量 -不包括引用值,这些值不是。
getcontext().prec = 3
temp = Decimal(3) / Decimal(1)
print(sys.getsizeof(temp))
print(sys.getsizeof(temp._int))
getcontext().prec = 300
temp = Decimal(3) / Decimal(1)
print(sys.getsizeof(temp)) # same
print(sys.getsizeof(temp._int)) # not same
(请注意,我在示例中使用的 _int
插槽是 CPython 的 Decimal
的内部实现细节,如前导下划线所暗示;此代码不是保证在其他 Python 实现中工作,甚至在其他版本中工作。)
编辑: 糟糕,我的第一个答案是在旧的 Python 上,其中 Decimal
在 Python 中实现。你问的那个版本有 implemented in C.
C 版本实际上将所有内容都存储在对象本身内部,但是您在精度上的差异不足以检测到差异(因为内存是在离散块中分配的)。尝试使用 getcontext().prec = 300
代替。