一个Decimal对象是如何在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类的源代码和附带的文档。

  • 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(密包十进制)编码将系数延续的十进制数转换为二进制数。

因此,根据DPD算法,我们可以计算出系数延拓的十进制数编码为二进制数时的位数。

由于符号、指数延拓和组合字段都简单地用二进制表示,因此编码时的位数可以很容易地计算出来。

那么,我们可以用下面的公式计算十进制对象编码时的位数。位=(符号)+ (exp) +(梳子)+(压缩后的coeff)

这里,符号和组合分别固定为1bit和5bit(根据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的定义,当系数延拓改变时,指数延拓也随之改变,总位数也要改变)

我自己是一个通常在机械工程领域学习的学生,在信息学方面我是一个完全的初学者。如果我原来的理解有什么地方是错误的,或者有什么奇怪的逻辑发展,请告诉我。

我很感激你的帮助。

Forsys.getsizeof:

只考虑直接归属于对象的内存消耗,而不考虑它所引用的对象的内存消耗。

由于Decimal是一个Python类,它引用了几个其他对象(编辑:见下文),因此您只需获得引用的总大小,这是常量-不包括引用的值,这不是。

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中实现的。你所询问的版本已经在c语言中实现了。

C版本实际上将所有内容存储在对象本身中,但是精度的差异不足以检测到差异(因为内存分配在离散的块中)。试试用getcontext().prec = 300代替。

最新更新