User:Xyy23330121/Python/decimal
decimal 是一种特殊的浮点数,它可以用于解决 float 把十进制小数转换成二进制无限循环小数,再舍入时产生的误差。在 Python 中,decimal 的内容由 decimal 模块提供。
float 储存数字的方式,简单来讲就是用二进制的方式储存三部分内容:正负号、二进制数字m
、二进制数字n
。来表示形如 +m * 2**n
或 -m * 2**n
的内容。decimal 与之类似,但它表示的是形如 +m * 10**n
的内容。
由于科学计数法中、底数的作用, float 保留的有效位数,是二进制的有效位数。十进制的有限小数、不一定能由二进制的有限小数表示出来。此时,取二进制小数中、有限部分的内容,一定会产生误差。而 Decimal 保留的有效位数是十进制的有效位数。这种方式使得计算机的数字计算与人的直觉相符。
这并不代表 Decimal 不会产生舍入误差,只是舍入误差以符合直觉的方式出现——也就能够简单的以符合直觉的方式解决。
Decimal 也被一些语言称作是货币值,这是因为货币运算一般不包含除法、也不会有超出限制的位数,从而 Decimal 能精确地计算商品的价格等内容。
import decimal as d
print(d.Decimal(10)) #10
print(d.Decimal(0.1)) #0.1000000000000000055511151231257827021181583404541015625
print(d.Decimal('0.1')) #0.1
print(d.Decimal('1.1e-1')) #0.11
print(d.Decimal('-1e-1')) #-0.1
print(d.Decimal('-inf')) #-Infinity
print(d.Decimal('nan')) #NaN
print(d.Decimal('-nan114514'))#-NaN114514
Decimal 可以由以上“类型构造器”方式,从整数、浮点数和字符串中生成。生成时,推荐使用字符串或整数。
对于浮点数,它会连同误差的部分也精确地表现——从而误差并没有减小。
也可以用元组来创建 Decimal,我们见以下方式:
import decimal as d
# 符号位 有效数位 指数
num_tuple = (0, (1, 2, 3), -1)
print(d.Decimal(num_tuple)) #12.3
用元组创建 Decimal 时,元组需要为一个三元组。
其中,第一个元素为符号位,其数值为 0
或 1
,分别代表正数或负数。上面例子中是正数。
第二个元素为包含所有有效数位的元组,十进制的每一位都要分开表示,上面例子中、该元组表示整数 123
。
第三个元素表示指数部分的整数,上面例子使用了 -1
,从而指数部分是 。
从而,以上元组表示的数字是:。
Decimal 支持一切数字的运算。包括:
- 四则运算、带余除法、绝对值
- 用 int() 、 float() 等类型构造器,转换为 int 或 float。
Decimal 在数学运算时,不能和 float 混用。在比较运算时是可以的。
Decimal 之间在进行 Python 带余除法时,取余数的符号和被除数符号相同。
这个行为与浮点数的带余除法或整数的带余除法是不同的,而与 math.fmod 函数的行为相同。
Decimal 对象支持许多方法,包括用于数学运算的对数函数和向整数四舍五入等。读者可以阅读相关文档[1]来了解其内容。
Decimal 的具体运算,比如精度等,是由上下文对象decimal.Context
规定的。
decimal 的上下文对象包含以下内容:
|
名称 | 注释 | |
---|---|---|---|
|
prec | Decimal 在计算时的有效位数。 这个精度对用类型构造器创建 Decimal 时的精度没有影响。 | |
使用 32 位运行 Python 时,此项为 1 ~ 425000000 的整数, 使用 64 位时,此项为 1 ~ 999999999999999999 的整数。 | |||
|
rounding | 决定运算需要舍入时,Decimal的舍入行为。具体可以为以下模式: | |
值 | 模式 | ||
decimal.ROUND_CEILING | 向上舍入 | ||
decimal.ROUND_FLOOR | 向下舍入 | ||
decimal.ROUND_DOWN | 向零舍入 | ||
decimal.ROUND_UP | 向无穷大舍入 | ||
decimal.ROUND_HALF_DOWN | 舍入到最接近的数字 如果有两个数字同样接近,则向零舍入 | ||
decimal.ROUND_HALF_UP | 舍入到最接近的数字 如果有两个数字同样接近,则向无穷大舍入 | ||
decimal.ROUND_HALF_EVEN | 舍入到最接近的数字 如果有两个数字同样接近,则向偶数舍入 | ||
decimal.ROUND_05UP | 如果舍入后最后一位 为 0 或 5,则向无穷 大舍入; 否则,向零舍入。 | ||
|
traps | 一个字典,具体见“异常处理方式”章节 | |
|
flags | 一个字典,具体见“错误提示”章节 | |
|
Emin | 创建 Decimal 时,指数部分的最小值 | |
使用 32 位运行 Python 时,此项为 -425000000 ~ 0 的整数, 使用 64 位时,此项为 -999999999999999999 ~ 0 的整数。 | |||
Emax | 创建 Decimal 时,指数部分的最大值 | ||
使用 32 位运行 Python 时,此项为 0 ~ 425000000 的整数, 使用 64 位时,此项为 0 ~ 999999999999999999 的整数。 | |||
|
clamp | 若设为 1 ,则当指数部分过大时,会减小其指数部分,并移除数字末尾的零。这使得指数部分的限制为是 Emin - prec + 1 ~ Emax - prec + 1 的整数。若设为 0 ,则不进行这类操作,此时指数部分限制为是Emin ~ Emax 的整数。
| |
|
capitals | 若设为 1 ,则在打印 1*10^1 时,打印为 1E+1 。若设为 0 ,则打印为 1e1 。
|
decimal 模块有一套特殊的异常处理方式,这个异常处理方式是由上下文对象的 traps 属性决定的。traps 为一个内容为 错误类型:是否报错
的字典。
当出现“可以认为出错了”的操作时,decimal 模块会查询对应错误类型的设置,如果 traps[错误类型]
的值为 True
,则会引发错误实例以报错。
反之,如果 traps[错误类型]
的值为 False
,则不进行报错。此时,操作的结果会被特殊处理。
以下是几种异常类型,以及不报错时的处理方法。
当指数超出当前上下文对象给出的上限时出现。
不报错、且上下文中 clamp 为 1 时,会对指数进行修改,并向有效数字的末尾添加零,使实例正确反应数值。这对有效数字的位数有影响。
当指数超出当前上下文对象给出的下限时出现。
未报错时,会对指数进行修改,并减少有效数字部分末尾的零,使实例正确反应数值。这对有效数字的位数有影响。
当数值超出当前上下文对象给出的限制中、可表示的最大数值时出现。
不报错时,会进行舍入。视舍入方向,可能有两个结果:
- 无穷大
- 可表示的最大数值
当数值超出当前上下文对象给出的限制中、可表示的最小数值时出现。
不报错时,会进行舍入,并舍入为 0。
当被除数为有限数字,而除数为零时出现。有限数字指的是无穷大或 NaN
不报错时,会让计算结果变为无穷大。此时,当被除数与除数符号相同时,返回正无穷大;当被除数与除数符号不同时,返回负无穷大。
当产生无意义运算时出现。无意义运算可能为:
- 无穷大乘无穷大
- 无穷大乘零
- 无穷大除无穷大
- 以零为除数,作带余除法
- 被除数为无穷大,除数为有限数字,作带余除法
- 对负数求平方根
- 零的零次幂
- 有限数字的非整数次幂,或无穷大次幂
不报错时,计算结果为 NaN。
当发生舍入时出现,不一定产生舍入误差。
不报错时,会进行舍入。
当发生舍入、且会产生舍入误差时出现。
不报错时,会进行舍入,并产生舍入误差。
当 float 和 Decimal 混合使用时出现。
不报错时,以下的计算可以进行:
- 类型构造器输入浮点数
decimal.Decimal(float)
- 不等于的比较运算
decimal.Decimal < float
等
如果此项设置为报错,又需要从 float 创建 Decimal 时,可以使用 classmethod decimal.Decimal.from_float(f)
。
这是以上所有错误的基类。它在默认情况下不在 flags 字典中,也不在 traps 字典中。
以下内容总结了各个信号的层级结构:
exceptions.ArithmeticError(exceptions.Exception)
DecimalException
Clamped
DivisionByZero(DecimalException, exceptions.ZeroDivisionError)
Inexact
Overflow(Inexact, Rounded)
Underflow(Inexact, Rounded, Subnormal)
InvalidOperation
Rounded
Subnormal
FloatOperation(DecimalException, exceptions.TypeError)
获取当前使用的上下文。通过这个方法,也可以设置当前上下文的内容。比如:
import decimal as d
D = d.Decimal
current_context = d.getcontext()
print(current_context)
print(D(1)/D(7))
current_context.prec = 7
print(D(1)/D(7))
输出为:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
0.1428571428571428571428571429
0.1428571
即便设置了不进行报错,有时也需要了解是否出现了问题。上下文对象的 flags 属性就是用于对异常进行提示的。
flags 是一个内容为 错误类型:是否出现过
的字典。当异常产生(但不一定报错)时,flags[错误类型]
的值会被改为 True
。
我们看以下示例:
import decimal as d
D = d.Decimal
#解除 decimal.DivisionByZero 的报错,使程序不会因报错而终止。
current_context = d.getcontext()
current_context.traps[d.DivisionByZero] = 0
print("引发信号前:")
print(current_context)
print(current_context.flags[d.DivisionByZero])
#引发 decimal.DivisionByZero 信号。
D(1)/D(0)
#查看引发信号后的结果
print("引发信号后:")
print(current_context)
print(current_context.flags[d.DivisionByZero])
#清除之前的提示
current_context.clear_flags()
#查看清除提示后的结果
print("清除提示后:")
print(current_context)
print(current_context.flags[d.DivisionByZero])
输出为:
引发信号前:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, Overflow])
False
引发信号后:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[DivisionByZero], traps=[InvalidOperation, Overflow])
True
清除提示后:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, Overflow])
False
对于已有的上下文对象,可以用这个方法,将其设置为当前上下文: decimal.setcontext(c)
decimal 模块提供了几种预设的上下文:
- decimal.DefaultContext 导入模块时自动使用的、默认上下文。
- decimal.BasicContext 通用十进制算术规范描述所定义的标准上下文。
精度为 9,舍入为 ROUND_HALF_UP,不会进行任何错误提示,但所有错误都会报错。 - decimal.ExtendedContext 通用十进制算术规范描述所定义的标准上下文。
精度为 9,舍入为 ROUND_HALF_UP,不会进行任何错误提示,且所有错误都不会报错。
读者也可以自行创建上下文对象。
decimal.Context(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, clamp=None, flags=None, traps=None)
各个参数的内容如上面所示。特别的,默认值为 decimal.DefaultContext
的值,即:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
在上述几个示例中,读者可能注意到:在打印时,traps 和 flags 并没有显示为字典。事实上,在创建上下文对象时,输入的 traps 和 flags 也不需要必须是字典。这是因为在创建上下文对象时,decimal 模块使用了以下代码来将列表转化为字典:
_signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded,
Underflow, InvalidOperation, Subnormal, FloatOperation]
dc = DefaultContext
if traps is None:
self.traps = dc.traps.copy()
elif not isinstance(traps, dict):
self.traps = dict((s, int(s in traps)) for s in _signals + traps)
else:
self.traps = traps
if flags is None:
self.flags = dict.fromkeys(_signals, 0)
elif not isinstance(flags, dict):
self.flags = dict((s, int(s in flags)) for s in _signals + flags)
else:
self.flags = flags
通过阅读以上代码,如果读者需要设置自己的、使用 Decimal 的运算。读者应该了解如何向上下文对象添加运算中的信号。
decimal 模块提供了一个上下文管理器 decimal.localcontext(ctx=None, **kwargs),它是这样使用的:
def sin(x):
with localcontext() as ctx:
ctx.prec += 2 #增加计算精度
i, lasts, s, fact, num, sign = 0, 0, 1, 1, 1, 1
while s != lasts:
lasts = s
i += 2
fact *= i * (i-1)
num *= x * x
sign *= -1
s += num / fact * sign
return +s #进行一次“正”运算,重新舍入到当前精度
在创建此管理器时,此管理器会保存当前上下文,并提供当前上下文的副本作为新的当前上下文。在 with 子句中对当前上下文的修改,都不会影响已保存的上下文。当 with 语句结束时,此管理器会把保存的上下文重新设置为当前上下文。
这使得我们可以简单地在局部增加计算精度。
特别的,如果输入了 kwargs
,则管理器提供副本时,会对副本按照 kwargs
给出的属性进行修改。
kwargs
提供了上下文对象所不支持的属性时,会引发 TypeError。如果 kwargs
提供了无效的属性值,则会引发 TypeError 或 ValueError。
Python 文档中还提供了许多 decimal 的使用示例。包括正弦、余弦等。
结合之前已学过的知识,读者可以自行撰写一个模块来实现 decimal 的数学运算,或者使用 decimal 的特性来实现更多的功能。