PEP 327 – Decimal 数据类型
- 作者:
- Facundo Batista <facundo at taniquetil.com.ar>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2003年10月17日
- Python 版本:
- 2.4
- 发布历史:
- 2003年11月30日, 2004年1月2日, 2004年1月29日
摘要
目标是提供一个 Decimal 数据类型,用于所有需要十进制表示但二进制浮点数精度不足以满足要求的场景。
Decimal 数据类型将支持 Python 标准函数和操作,并必须符合十进制算术 ANSI 标准 X3.274-1996 [1]。
Decimal 将是浮点数(而非定点数),并具有有限的精度(精度是结果中有效数字的最大数量)。然而,精度是用户可设置的,并且支持有效零尾数的概念,以便也可以使用定点数。
这项工作基于 Eric Price、Aahz 和 Tim Peters 编写的代码和测试函数。在 Python 2.4a1 之前不久,decimal.py 参考实现被移入标准库;连同文档和测试套件,这都是 Raymond Hettinger 的工作。本 PEP 中的大部分解释都摘自 Cowlishaw 的作品 [2],comp.lang.python 和 python-dev。
动机
在此我将阐述为什么我认为需要 Decimal 数据类型,以及为什么其他数字数据类型不足以满足需求。
我想要一个 Money 数据类型,并在 comp.lang.python 提出了一个预 PEP 后,社区同意提供一个具有所需算术行为的数字数据类型,然后在此基础上构建 Money:所有关于小数点后位数、舍入等的考虑都将通过 Money 来处理。本 PEP 的目的并非提供一个无需进一步努力即可用作 Money 的数据类型。
实现标准的最大优势之一是有人已经为您考虑了所有棘手的情况。GvR 将我引向了一个标准:Mike Cowlishaw 的通用十进制算术规范 [2]。该文档定义了通用目的的十进制算术。该规范的正确实现将符合 ANSI/IEEE 标准 854-1987 中定义的十进制算术,除了一些小的限制,并且还将提供未舍入的十进制算术和整数算术作为其 proper subsets。
二进制浮点数的困境
在十进制数学中,许多数字无法用固定小数位数的十进制数表示,例如 1/3 = 0.3333333333……。
在基数 2(标准浮点数计算方式)中,1/2 = 0.1,1/4 = 0.01,1/8 = 0.001 等。十进制的 0.2 等于 2/10 等于 1/5,导致二进制小数数为 0.001100110011001……。正如您所见,问题在于某些十进制数无法在二进制中精确表示,从而导致微小的舍入误差。
因此,我们需要一个能够精确表示十进制数的 Decimal 数据类型。我们需要的不是二进制数据类型,而是十进制数据类型。
为什么需要浮点数?
所以我们转向十进制,但为什么是浮点数?
浮点数使用固定数量的数字(精度)来表示数字,当数字过大或过小时则使用指数。例如,精度为 5
1234 ==> 1234e0
12345 ==> 12345e0
123456 ==> 12346e1
(请注意,最后一行数字已舍入以适应五个数字)。
相比之下,我们有一个具有无限精度的 long 整数的例子,这意味着您可以拥有任意大的数字,而不会丢失任何信息。
在定点数中,小数点的位置是固定的。有关定点数据类型,请参阅 Tim Peter 的 SourceForge 上的 FixedPoint [4]。我选择浮点数是因为它更容易实现标准的算术行为,然后您可以在 Decimal 之上实现定点数据类型。
但是,为什么我们不能拥有无限精度的浮点数?这并不容易,因为存在不精确的除法。例如:1/3 = 0.3333333333333… 无限循环。在这种情况下,您应该存储无限个 3,这会占用太多内存;)。
John Roth 提议取消除法运算符,强制用户使用显式方法,以避免这种麻烦。这在 comp.lang.python 中引起了负面反应,因为每个人都希望在数字数据类型中支持 / 运算符。
暴露了这一点后,您可能在想“嘿!我们不能直接存储 1 和 3 作为分子和分母吗?”,这让我们进入下一个话题。
为什么不使用有理数?
有理数使用两个整数(分子和分母)存储。这意味着算术运算无法直接执行(例如,要将两个有理数相加,您首先需要计算公分母)。
引用 Alex Martelli
将两个有理数(分别占用 O(M) 和 O(N) 空间)相加得到一个占用 O(M+N) 内存空间的有理数,其性能影响实在太麻烦了。在纯 Python 和扩展(例如 gmpy)中都有优秀的 Rational 实现,但我认为它们将始终是“小众市场”。可能值得 PEP,但没有 Decimal 就不值得——Decimal 才是表示货币总额的正确方式,这是现实世界中一个真正主要的用例。
无论如何,如果您对这种数据类型感兴趣,您可能想看看 PEP 239:为 Python 添加有理数类型。
那么,我们拥有什么?
结果是 Decimal 数据类型,具有有限精度和浮点特性。
它会有用吗?我无法比 Alex Martelli 说得更好
Python(开箱即用)不允许您拥有具有您指定的任何精度的二进制浮点数:您仅限于硬件提供的精度。Decimal,无论用作定点数还是浮点数,都不应受到此类限制:您在创建数字时指定的任何有限精度(只要您的内存允许)都应该同样有效。编程简单性的绝大部分开销都可以隐藏在应用程序程序之外,并置于合适的十进制算术类型中。根据 http://speleotrove.com/decimal/,一种数据类型可以用于整数、定点和浮点十进制算术——以及用于不会让应用程序程序员抓狂的货币算术。
这种数据类型有几种用途。如前所述,我将将其用作 Money 的基础。在这种情况下,有限精度不是问题;引用 Tim Peters
20 位精度足以支付自古以来全球经济总产值,精确到分。
通用十进制算术规范
在此我将包含规范 [2] 中的信息和描述(数字的结构、上下文等)。本节包含的所有要求都无需讨论(除了拼写错误或其他错误),因为它们已在标准中,而 PEP 只是为了实现标准。
由于版权限制,我无法在此复制规范中的解释,因此我将尝试用我自己的话来解释。我强烈鼓励您阅读原始规范文档 [2] 以获取详细信息或如果您有任何疑问。
算术模型
该规范基于十进制算术模型,由相关标准定义:IEEE 854 [3]、ANSI X3-274 [1] 和 IEEE 754 [6] 的拟议修订版 [5]。
模型包含三个组件
- 数字:操作使用的输入或输出的数值。
- 操作:加法、乘法等。
- 上下文:一组用户可以选择的参数和规则,用于控制操作的结果(例如,使用的精度)。
数字
数字可以是有限的或特殊值。前者可以精确表示。后者是无穷大和未定义(如 0/0)。
有限数字由三个参数定义
- 符号:0(正)或 1(负)。
- 系数:一个非负整数。
- 指数:一个带符号整数,表示系数的十的幂。
有限数字的数值由下式给出
(-1)**sign * coefficient * 10**exponent
特殊值命名如下
- 无穷大:一个无限大的值。可以是正无穷大或负无穷大。
- 安静 NaN(“qNaN”):表示未定义的结果(非数字)。不引起无效操作条件。NaN 的符号无意义。
- 信令 NaN(“sNaN”):也是非数字,但在任何操作中使用时都会引起无效操作条件。
背景
上下文是一组用户可以选择的参数和规则,用于控制操作的结果(例如,使用的精度)。
上下文之所以得名,是因为它包围着 Decimal 数字,上下文的某些部分充当操作的输入和输出。由应用程序决定使用一个或多个上下文,但绝不是每个 Decimal 数字都有一个上下文。例如,典型的用法是在程序开始时将上下文的精度设置为 20 位,然后不再显式使用上下文。
这些定义不影响 Decimal 数字的内部存储,只影响算术运算的执行方式。
上下文主要由以下参数定义(请参阅 上下文属性 以了解所有上下文属性)
- 精度:算术运算(整数 > 0)可能产生结果的最大有效数字位数。此值没有最大值。
- 舍入:必要时进行舍入时使用的算法名称,包括“round-down”、“round-half-up”、“round-half-even”、“round-ceiling”、“round-floor”、“round-half-down”和“round-up”。请参阅下面的 舍入算法。
- 标志和陷阱启用器:异常情况分为信号,可单独控制,每个信号包含一个标志(发生信号时设置为布尔值)和一个陷阱启用器(控制行为的布尔值)。信号包括:“clamped”、“division-by-zero”、“inexact”、“invalid-operation”、“overflow”、“rounded”、“subnormal”和“underflow”。
默认上下文
该规范定义了两个默认上下文,用户应易于选择。
基本默认上下文
- flags:全部设置为 0
- trap-enablers:inexact、rounded 和 subnormal 设置为 0;所有其他设置为 1
- precision:设置为 9
- rounding:设置为 round-half-up
扩展默认上下文
- flags:全部设置为 0
- trap-enablers:全部设置为 0
- precision:设置为 9
- rounding:设置为 round-half-even
异常情况
下表列出了算术运算期间可能出现的异常情况、相应的信号和定义的结果。有关详细信息,请参阅规范 [2]。
| 条件 | 信号 | 结果 |
|---|---|---|
| Clamped | clamped | 参见规范 [2] |
| 除以零 | division-by-zero | [sign,inf] |
| Inexact | inexact | unchanged |
| 无效操作 | invalid-operation | [0,qNaN](当原因是信令 NaN 时为 [s,qNaN] 或 [s,qNaN,d]) |
| Overflow | overflow | 取决于舍入模式 |
| Rounded | rounded | unchanged |
| Subnormal | subnormal | unchanged |
| Underflow | underflow | 参见规范 [2] |
注意:当标准谈论“存储不足”时,只要这是关于没有足够存储空间来保存数字内部结构而与实现相关的行为,此实现将引发 MemoryError。
关于溢出和下溢,python-dev 上曾就人工限制进行了长期讨论。普遍的共识是,除非有重要原因,否则应仅保留人工限制。Tim Peters 提出了三个理由
…消除指数的界限实际上意味着溢出(和下溢)永远不会发生。但在现实世界的浮点数使用中,溢出是一种有价值的安全网,就像煤矿里的金丝雀一样,当程序出现故障时能及早发出危险信号。几乎所有 854 的实现都使用(正如 IBM 的标准所建议的)“禁止”指数值来编码非有限数字(无穷大和 NaN)。有界指数几乎可以以零额外存储成本来做到这一点。如果指数无界,则必须改用额外的位数。这种成本在尝试更节省时间和空间更高效的实现之前是隐藏的。
IBM 标准虽然庞大,但只是提供完整数字功能的一个微小开端。指数大小无界将极大地复杂化实现,例如,十进制 sin() 和 cos()(那时对 pi 的有效数字需要知道多少才能执行参数约简,没有先验限制)。
Edward Loper 给出了一个例子,说明何时应越过界限:概率。
话虽如此,Robert Brewer 和 Andrew Lentvorski 希望用户可以轻松修改这些限制。实际上,这完全有可能
>>> d1 = Decimal("1e999999999") # at the exponent limit
>>> d1
Decimal("1E+999999999")
>>> d1 * 10 # exceed the limit, got infinity
Traceback (most recent call last):
File "<pyshell#3>", line 1, in ?
d1 * 10
...
...
Overflow: above Emax
>>> getcontext().Emax = 1000000000 # increase the limit
>>> d1 * 10 # does not exceed any more
Decimal("1.0E+1000000000")
>>> d1 * 100 # exceed again
Traceback (most recent call last):
File "<pyshell#3>", line 1, in ?
d1 * 100
...
...
Overflow: above Emax
舍入算法
round-down:忽略被丢弃的数字;结果不变(向零舍入,截断)
1.123 --> 1.12
1.128 --> 1.12
1.125 --> 1.12
1.135 --> 1.13
round-half-up:如果被丢弃的数字表示大于或等于一半(0.5),则结果应加 1;否则忽略被丢弃的数字
1.123 --> 1.12
1.128 --> 1.13
1.125 --> 1.13
1.135 --> 1.14
round-half-even:如果被丢弃的数字表示大于一半(0.5),则结果系数加 1;如果它们表示小于一半,则结果不变;否则,如果结果的最右一位是偶数,则结果不变,如果其最右一位是奇数,则结果加 1(使其成为偶数)。
1.123 --> 1.12
1.128 --> 1.13
1.125 --> 1.12
1.135 --> 1.14
round-ceiling:如果所有被丢弃的数字都是零或符号为负,则结果不变;否则,结果加 1(向上舍入到正无穷大)
1.123 --> 1.13
1.128 --> 1.13
-1.123 --> -1.12
-1.128 --> -1.12
round-floor:如果所有被丢弃的数字都是零或符号为正,则结果不变;否则,结果的绝对值加 1(向下舍入到负无穷大)
1.123 --> 1.12
1.128 --> 1.12
-1.123 --> -1.13
-1.128 --> -1.13
round-half-down:如果被丢弃的数字表示大于一半(0.5),则结果加 1;否则忽略被丢弃的数字
1.123 --> 1.12
1.128 --> 1.13
1.125 --> 1.12
1.135 --> 1.13
round-up:如果所有被丢弃的数字都是零,则结果不变,否则结果加 1(远离零舍入)
1.123 --> 1.13
1.128 --> 1.13
1.125 --> 1.13
1.135 --> 1.14
基本原理
我必须将要求分为两个部分。第一部分是符合 ANSI 标准。这方面所有要求都在 Mike Cowlishaw 的作品 [2] 中指定。他还提供了一个**非常庞大**的测试用例套件。
第二部分的要求(支持标准 Python 函数、可用性等)将从此处开始详细介绍,我将在此包含所有已做出的决定及其原因,以及仍在讨论的主题。
显式构造
显式构造不受上下文影响(没有舍入,没有精度限制等),因为上下文仅影响操作结果。唯一的例外是当您从上下文创建时。
从整数或长整数
没有损失,也没有必要指定任何其他信息
Decimal(35)
Decimal(-124)
从字符串
将支持包含 Python 十进制整数字面量和 Python 浮点文字符串。此转换过程中没有信息丢失,因为字符串直接转换为 Decimal(没有通过浮点数的中间转换)。
Decimal("-12")
Decimal("23.2e-7")
此外,您还可以通过这种方式构造所有特殊值(无穷大和非数字)。
Decimal("Inf")
Decimal("NaN")
从浮点数
关于将浮点数传递给构造函数的初始讨论是关于应该发生什么。
Decimal(1.1) == Decimal('1.1')Decimal(1.1) == Decimal('110000000000000008881784197001252...e-51')- 会引发一个异常
几个人声称(1)是这里的更好选择,因为当您编写 Decimal(1.1) 时,您期望的是这样的结果。引用 John Roth 的话,实现起来很容易
找到实际数字的结束位置以及虚假数字的开始位置并不难。您可以直观地找到它们,并且实现它们的算法是众所周知的。
但是,如果我真的想让我的数字是 Decimal('110000000000000008881784197001252...e-51'),为什么我不能写 Decimal(1.1)?为什么我应该期望 Decimal“舍入”它?请记住 1.1 *是*二进制浮点数,所以我可以预测结果。这对初学者来说不直观,但事实就是如此。
无论如何,Paul Moore 证明了(1)是行不通的,因为
(1) says D(1.1) == D('1.1')
but 1.1 == 1.1000000000000001
so D(1.1) == D(1.1000000000000001)
together: D(1.1000000000000001) == D('1.1')
这是错误的,因为如果我写 Decimal('1.1'),它是精确的,而不是 D(1.1000000000000001)。他还提议提供一个显式转换为浮点数的选项。bokr 说您需要将精度放在构造函数中,mwilson 同意。
d = Decimal (1.1, 1) # take float value to 1 decimal place
d = Decimal (1.1) # gets `places` from pre-set context
但 Alex Martelli 说
使用指定的精度进行构造是可以的。因此,我认为“从浮点数构造,带有一些默认精度”会面临严重欺骗无经验用户的风险。
因此,在 c.l.p 上接受的解决方案是,您不能用浮点数调用 Decimal。而是必须使用一个方法:Decimal.from_float()。语法
Decimal.from_float(floatNumber, [decimal_places])
其中 floatNumber 是构造的浮点数来源,decimal_places 是小数点后的位数,在此应用 round-half-up 舍入(如有必要)。这样,您可以执行,例如
Decimal.from_float(1.1, 2): The same as doing Decimal('1.1').
Decimal.from_float(1.1, 16): The same as doing Decimal('1.1000000000000001').
Decimal.from_float(1.1): The same as doing Decimal('1100000000000000088817841970012523233890533447265625e-51').
基于后来的讨论,决定将 from_float() 从 Py2.4 的 API 中删除。几个想法促成了这个过程
- Decimal 和二进制浮点数之间的交互迫使用户处理表示和舍入的棘手问题。避免这些问题是该模块存在的主要原因。
- 该模块的第一个版本应侧重于安全、最小化和必要的部分。
- 虽然理论上很好,但浮点数和十进制数之间交互的实际用例却很少。Java 包含了浮点数/十进制转换,以处理一个不常见的情况,即计算最好以十进制进行,即使旧版数据结构要求输入和输出存储在二进制浮点数中。
- 如果需要,用户可以使用字符串表示作为中间类型。这种方法的优点是它明确了精度和表示的假设(无需猜测后台发生了什么)。
- Java 中 BigDecimal(double val) 的文档反映了他们对该构造函数的经验
The results of this constructor can be somewhat unpredictable and its use is generally not recommended.
从元组
Aahz 建议从元组构造:更容易实现 eval() 的往返转换,“拥有 Decimal 数值的人无需将其转换为字符串。”
结构将是一个包含三个元素的元组:符号、数字和指数。符号为 1 或 0,数字是十进制数字的元组,指数是带符号的 int 或 long。
Decimal((1, (3, 2, 2, 5), -2)) # for -32.25
当然,您可以通过这种方式构造所有特殊值
Decimal( (0, (0,), 'F') ) # for Infinity
Decimal( (0, (0,), 'n') ) # for Not a Number
从 Decimal
没有秘密,只是复制。
所有情况下的语法
Decimal(value1)
Decimal.from_float(value2, [decimal_places])
其中 value1 可以是 int、long、string、3 元组或 Decimal,value2 只能是 float,而 decimal_places 是一个可选的非负整数。
从上下文创建
python-dev 中的这一项同时来自两个来源。Ka-Ping Yee 提议在实例创建时将上下文作为参数传递(他希望传递的上下文仅在创建时使用:“它不会持久化”)。Tony Meyer 要求 from_string 如果收到一个参数“honor_context”且值为 True,则遵守上下文。(我不喜欢这样,因为文档规定上下文必须被遵守,而且我不想让方法遵守关于参数值的规范。)
Tim Peters 给出了一项使用上下文进行创建的原因
一般来说,在数字计算中,文字可以给出高精度,但精度不是免费的,而且通常不需要。
Casey Duncan 希望使用另一种方法,而不是布尔参数
我发现布尔参数是一个普遍的反模式,尤其是因为我们有类方法。为什么不使用一个替代构造函数,如 Decimal.rounded_to_context("3.14159265")。
在决定其语法的过程中,Tim 提出了一个更好的主意:他建议不在 Decimal 中设置创建不同上下文的方法,而是让 Context 拥有一个创建 Decimal 实例的方法。基本上,而不是
D.using_context(number, context)
它将是
context.create_decimal(number)
来自 Tim
虽然规范中的所有操作(除了两个 to-string 操作)都使用上下文,但没有操作支持可选的本地上下文。Decimal() 构造函数默认忽略上下文是对规范的扩展。我们必须提供一个遵守上下文的 from-string 操作以满足规范。我不建议使用任何“本地上下文”的概念,因为它会使模型复杂化且非必需。
因此,我们决定使用上下文方法来创建一个 Decimal,该 Decimal 将(仅在创建时)使用该特定上下文(之后的操作将使用线程的上下文)。但是,使用什么名称的方法呢?
Tim Peters 提议了三种从不同来源创建的方法(from_string、from_int、from_float)。我提议使用一个方法,create_decimal(),而不关心数据类型。Michael Chermside:“这个名字很符合我的思路。它使用上下文的事实,从它是 Context 方法这一点就很明显了。”
社区对此表示同意。我认为这是可以的,因为新手不会使用来自 Context 的创建方法(Decimal 中用于从浮点数构造的单独方法只是为了防止新手遇到二进制浮点数问题)。
所以,简而言之,如果您想使用特定的上下文创建 Decimal 实例(该上下文仅在创建时使用,之后不再使用),您必须使用该上下文的一个方法
# n is any datatype accepted in Decimal(n) plus float
mycontext.create_decimal(n)
示例
>>> # create a standard decimal instance
>>> Decimal("11.2233445566778899")
Decimal("11.2233445566778899")
>>>
>>> # create a decimal instance using the thread context
>>> thread_context = getcontext()
>>> thread_context.prec
28
>>> thread_context.create_decimal("11.2233445566778899")
Decimal("11.2233445566778899")
>>>
>>> # create a decimal instance using other context
>>> other_context = thread_context.copy()
>>> other_context.prec = 4
>>> other_context.create_decimal("11.2233445566778899")
Decimal("11.22")
隐式构造
由于隐式构造是操作的结果,因此它将受到上下文的影响,具体细节将在各点详述。
John Roth 建议“其他类型应以 Decimal() 构造函数处理它的方式进行处理”。但 Alex Martelli 认为
这种与 Python 传统的彻底决裂将是一个可怕的错误。23+“43”不像 23+int(“45”) 那样处理,而且这非常好。用户明确表示他们想要构造(转换)与仅仅错误地将一个可能为字符串的对象相加,这是完全不同的事情。
因此,在此我再次定义每种数据类型的行为。
从整数或长整数
整数或长整数被视为在当前上下文中从 Decimal(str(x)) 显式构造的 Decimal(这意味着应用了 to-string 规则的舍入,并设置了相应的标志)。这保证了像 Decimal('1234567') + 13579 这样的表达式与 Decimal('1234567') + Decimal('13579') 的心智模型相符。该模型有效,因为所有整数都可以表示为没有表示错误的字符串。
从字符串
所有人都同意在此处引发异常。
从浮点数
Aahz 强烈反对与浮点数交互,建议进行显式转换
问题在于 Decimal 在精度、准确性和范围方面都比 float 更强大。
有效的 Python 表达式,如 35 + 1.1,似乎表明 Decimal(35) + 1.1 也应该有效。然而,仔细观察表明,这仅演示了整数到浮点数转换的可行性。因此,Decimal 浮点数的正确类比是 35 + Decimal(1.1)。整数到浮点数和整数到 Decimal 的这两种强制转换都可以进行,而不会产生表示误差。
二进制和十进制浮点数之间的强制转换问题更为复杂。我提议允许与浮点数交互,进行精确转换,并在超过当前上下文精度时引发 ValueError(这可能太棘手了,因为例如,对于精度为 9 的情况,Decimal(35) + 1.2 是可以的,但 Decimal(35) + 1.1 会引发错误)。
事实证明这太棘手了。如此棘手,以至于 c.l.p 同意在这种情况下引发 TypeError:您不能混合 Decimal 和 float。
从 Decimal
这里没有问题。
上下文的使用
在上一份预 PEP 中,我说过“上下文必须无处不在,意味着对其的更改会影响当前和未来的所有 Decimal 实例”。我错了。作为回应,John Roth 说
上下文应该可以为特定用法选择。也就是说,应用程序应该能够同时处理多个不同的上下文。
在 comp.lang.python 中,Aahz 解释说,这个想法是有一个“每个线程一个上下文”。因此,一个线程的所有实例都属于一个上下文,您可以更改线程 A 中的上下文(以及该线程中实例的行为),而不会更改线程 B 中的任何内容。
另外,再次纠正我,他说
上下文仅适用于操作,不适用于 Decimal 实例;更改上下文不会影响现有实例(如果它们没有操作)。
关于特殊情况,当需要使用与当前上下文不同的规则执行操作时,Tim Peters 说上下文将作为操作的方法。这样,用户“可以创建任何需要的私有上下文对象,并将算术表示为对其私有上下文对象的显式方法调用,从而默认的线程上下文对象既不被查询也不被修改”。
Python 的可用性
- Decimal 应该支持以下情况下的基本算术(
+, -, *, /, //, **, %, divmod)和比较(==, !=, <, >, <=, >=, cmp)运算符(请参阅 隐式构造 以了解 OtherType 可以是哪些类型,以及每种情况下的处理方式)- Decimal op Decimal
- Decimal op otherType
- otherType op Decimal
- Decimal op= Decimal
- Decimal op= otherType
- Decimal 应该支持一元运算符(
-, +, abs)。 - repr() 应该往返,意味着
m = Decimal(...) m == eval(repr(m))
- Decimal 应该是不可变的。
- Decimal 应该支持内置方法
- min, max
- float, int, long
- str, repr
- hash
- bool (0 为 false,其他为 true)
python-dev 上曾就 hash() 的行为进行过一些讨论。社区一致认为,如果值相等,那么这些值的哈希值也应该相等。因此,虽然 Decimal(25) == 25 是 True,但 hash(Decimal(25)) 应该等于 hash(25)。
细节是您不能比较 Decimal 和浮点数或字符串,所以我们不必担心它们给出相同的哈希值。简而言之
hash(n) == hash(Decimal(n)) # Only if n is int, long, or Decimal
关于 str() 和 repr() 的行为,Ka-Ping Yee 提议 repr() 的行为与 str() 相同,而 Tim Peters 提议 str() 的行为应像规范中的 to-scientific-string 操作。
这是可能的,因为(来自 Aahz):“字符串格式已经包含了重建 Decimal 对象所需的所有必要信息。”
并且它也符合规范;Tim Peters
没有要求有一个*命名*为“to_sci_string”的方法,唯一的要求是提供一种*表达* to-sci-string 功能的方式。to-sci-string 的含义由标准精确定义,并且对于 str(Decimal) 和 repr(Decimal) 都是一个不错的选择。
文档
本节解释了 Decimal 和 Context 的所有公共方法和属性。
Decimal 属性
Decimal 没有公共属性。内部信息存储在槽中,不应由最终用户访问。
Decimal 方法
以下是规范中定义的转换和算术运算,以及如何通过实际实现来实现这些功能。
- to-scientific-string:使用内置函数
str()>>> d = Decimal('123456789012.345') >>> str(d) '1.23456789E+11'
- to-engineering-string:使用方法
to_eng_string()>>> d = Decimal('123456789012.345') >>> d.to_eng_string() '123.456789E+9'
- to-number:使用 Context 方法
create_decimal()。标准构造函数或from_float()构造函数不能使用,因为它们不使用上下文(正如规范对此转换所指定的)。 - abs:使用内置函数
abs()>>> d = Decimal('-15.67') >>> abs(d) Decimal('15.67')
- add:使用运算符
+>>> d = Decimal('15.6') >>> d + 8 Decimal('23.6')
- subtract:使用运算符
->>> d = Decimal('15.6') >>> d - 8 Decimal('7.6')
- compare:使用方法
compare()。此方法(而非内置函数 cmp())仅应用于处理特殊值。>>> d = Decimal('-15.67') >>> nan = Decimal('NaN') >>> d.compare(23) '-1' >>> d.compare(nan) 'NaN' >>> cmp(d, 23) -1 >>> cmp(d, nan) 1
- divide:使用运算符
/>>> d = Decimal('-15.67') >>> d / 2 Decimal('-7.835')
- divide-integer:使用运算符
//>>> d = Decimal('-15.67') >>> d // 2 Decimal('-7')
- max:使用方法
max()。仅当处理特殊值时才使用此方法(而非内置函数 max())。>>> d = Decimal('15') >>> nan = Decimal('NaN') >>> d.max(8) Decimal('15') >>> d.max(nan) Decimal('NaN')
- min:使用方法
min()。仅当处理特殊值时才使用此方法(而非内置函数 min())。>>> d = Decimal('15') >>> nan = Decimal('NaN') >>> d.min(8) Decimal('8') >>> d.min(nan) Decimal('NaN')
- minus:使用一元运算符
->>> d = Decimal('-15.67') >>> -d Decimal('15.67')
- plus:使用一元运算符
+>>> d = Decimal('-15.67') >>> +d Decimal('-15.67')
- multiply:使用运算符
*>>> d = Decimal('5.7') >>> d * 3 Decimal('17.1')
- normalize:使用方法
normalize()>>> d = Decimal('123.45000') >>> d.normalize() Decimal('123.45') >>> d = Decimal('120.00') >>> d.normalize() Decimal('1.2E+2')
- quantize:使用方法
quantize()>>> d = Decimal('2.17') >>> d.quantize(Decimal('0.001')) Decimal('2.170') >>> d.quantize(Decimal('0.1')) Decimal('2.2')
- remainder:使用运算符
%>>> d = Decimal('10') >>> d % 3 Decimal('1') >>> d % 6 Decimal('4')
- remainder-near:使用方法
remainder_near()>>> d = Decimal('10') >>> d.remainder_near(3) Decimal('1') >>> d.remainder_near(6) Decimal('-2')
- round-to-integral-value:使用方法
to_integral()>>> d = Decimal('-123.456') >>> d.to_integral() Decimal('-123')
- same-quantum:使用方法
same_quantum()>>> d = Decimal('123.456') >>> d.same_quantum(Decimal('0.001')) True >>> d.same_quantum(Decimal('0.01')) False
- square-root:使用方法
sqrt()>>> d = Decimal('123.456') >>> d.sqrt() Decimal('11.1110756')
- power:用户运算符
**>>> d = Decimal('12.56') >>> d ** 2 Decimal('157.7536')
以下是其他方法及其存在的原因
adjusted():返回调整后的指数。此概念在规范中定义:调整后的指数是当数字表示为科学记数法(小数点前有一个数字)时的指数值。>>> d = Decimal('12.56') >>> d.adjusted() 1
from_float():用于从浮点数数据类型创建实例的类方法。>>> d = Decimal.from_float(12.35) >>> d Decimal('12.3500000')
as_tuple():显示 Decimal 的内部结构,三元组。此方法不是规范所要求的,但 Tim Peters 提议并获得了社区的同意(它对于开发和调试很有用)。>>> d = Decimal('123.4') >>> d.as_tuple() (0, (1, 2, 3, 4), -1) >>> d = Decimal('-2.34e5') >>> d.as_tuple() (1, (2, 3, 4), 3)
上下文属性
这些是可用于修改上下文的属性。
prec(int):精度。>>> c.prec 9
rounding(str):舍入类型(如何舍入)。>>> c.rounding 'half_even'
trap_enablers(dict):如果 trap_enablers[exception] = 1,则在发生异常时引发异常。>>> c.trap_enablers[Underflow] 0 >>> c.trap_enablers[Clamped] 0
flags(dict):当发生异常时,flags[exception] 会递增(无论 trap_enabler 是否已设置)。应由 Decimal 实例的用户重置。>>> c.flags[Underflow] 0 >>> c.flags[Clamped] 0
Emin(int):最小指数。>>> c.Emin -999999999
Emax(int):最大指数。>>> c.Emax 999999999
capitals(int):布尔标志,用于在字符串中使用“E”(True/1)或“e”(False/0)(例如,“1.32e+2”或“1.32E+2”)。>>> c.capitals 1
上下文方法
以下方法符合规范中的 Decimal 功能。请注意,通过特定上下文调用的操作将使用该上下文,而不是线程上下文。
要使用这些方法,请注意当运算符是二元运算符还是单目运算符时,语法会发生变化,例如
>>> mycontext.abs(Decimal('-2'))
'2'
>>> mycontext.multiply(Decimal('2.3'), 5)
'11.5'
因此,以下是规范中的操作和转换以及如何通过上下文实现它们(其中 d 是 Decimal 实例,n 是可以在 隐式构造 中使用的数字)。
- to-scientific-string:
to_sci_string(d) - to-engineering-string:
to_eng_string(d) - to-number:
create_decimal(number),有关number,请参阅 显式构造。 - abs:
abs(d) - add:
add(d, n) - subtract:
subtract(d, n) - compare:
compare(d, n) - divide:
divide(d, n) - divide-integer:
divide_int(d, n) - max:
max(d, n) - min:
min(d, n) - minus:
minus(d) - plus:
plus(d) - multiply:
multiply(d, n) - normalize:
normalize(d) - quantize:
quantize(d, d) - remainder:
remainder(d) - remainder-near:
remainder_near(d) - round-to-integral-value:
to_integral(d) - same-quantum:
same_quantum(d, d) - square-root:
sqrt(d) - power:
power(d, n)
divmod(d, n) 方法通过 Context 支持十进制功能。
这些是返回 Context 中有用信息的类方法。
Etiny():考虑精度的最小指数。>>> c.Emin -999999999 >>> c.Etiny() -1000000007
Etop():考虑精度的最大指数。>>> c.Emax 999999999 >>> c.Etop() 999999991
copy():返回上下文的副本。
参考实现
截至 Python 2.4-alpha,代码已签入标准库。最新版本可从以下网址获取:
http://svn.python.org/view/python/trunk/Lib/decimal.py
测试用例如下:
http://svn.python.org/view/python/trunk/Lib/test/test_decimal.py
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0327.rst