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和附件

根据文档,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的定义,系数续延改变时,指数续延也随之改变,总位数也要改变。)

我自己是一名平时就读机械工程专业的学生,​​信息学完全是初学者。 如果我原来的理解有什么地方不对,或者有什么奇怪的逻辑发展,请告诉我。

感谢您的帮助。

对于sys.getsizeof

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 代替。