上下文对象是算术运算所在的环境。它们管理精度、设置舍入规则、确定将哪些信号视为异常,并限制指数的范围。
每个线程都有自己的当前上下文对象,可使用 getcontext() 和 setcontext() 函数来读取或修改。
decimal.getcontext() 返回活动线程的当前上下文。
decimal.setcontext(c) 将活动线程的当前上下文设为 c。
你也可以使用 with 语句和 localcontext() 函数来临时改变活动上下文。decimal.localcontext(ctx=None, **kwargs) 缓存正在使用的上下文对象,复制ctx参数(如果ctx未指定,就用正在使用的上下文对象)作为管理算术运算的环境。关键字参数用于改变新的上下文对象属性。 例如,以下代码会将当前 decimal 精度设为 42 位,执行一个运算,然后自动恢复之前的上下文: 3.11版开始可以使用关键字参数的方法如下: 如果输入非法关键字,抛出TypeError异常;如果输入了非法的值,抛出TypeError或ValueError异常:
新的上下文也可使用下述的Context构造器来创建。此外,模块还提供了三种预设的上下文:
class decimal.BasicContext 这是由通用十进制算术规范描述所定义的标准上下文。精度设为9。舍入设为 ROUND_HALF_UP(数学中的四舍五入,参考本文后面的舍入模式小节)。清除所有旗标。除了Inexact, Rounded 和 Subnormal 外启用所有陷阱(视为异常)。 由于启用了许多陷阱,此上下文更适用于调试。
class decimal.ExtendedContext 这是由通用十进制算术规范描述所定义的标准上下文。精度设为9。舍入设为 ROUND_HALF_EVEN。清除所有旗标。不启用任何陷阱(因此在计算期间不会引发异常)。 由于禁用了陷阱,此上下文适用于希望结果值为 NaN 或 Infinity 而不是引发异常的应用。这允许应用在出现当其他情况下会中止程序的条件时仍能完成运行。
class decimal.DefaultContext 此上下文被 Context 构造器用作新上下文的原型。改变一个字段(例如精度)的效果将是改变 Context 构造器所创建的新上下文的默认值。 此上下文适用于多线程环境。在线程开始前改变一个字段具有设置整个线程默认值的效果。不推荐在线程开始后改变字段,因为这会要求线程同步避免竞争条件。 在单线程环境中,最好完全不使用此上下文对象。而是简单地电显式创建上下文,具体如下所述。 默认值为 prec=28, rounding=ROUND_HALF_EVEN,并为 Overflow, InvalidOperation 和 DivisionByZero 启用陷阱。
在已提供的三种上下文之外,还可以使用 Context 构造器创建新的上下文。
class decimal.Context(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, clamp=None, flags=None, traps=None) 创建一个新上下文。如果某个字段未指定或为 None,则从 DefaultContext 拷贝默认值。如果 flags 字段未指定或为 None,则清空所有旗标。 prec 精度,为一个 [1, MAX_PREC] 范围内的整数,用于设置该上下文中算术运算的精度。MAX_PREC最大精度,参看《常量表》。 rounding 舍入模式,下面的“舍入模式”中列出的常量之一。参考舍入模式小节。 flags 旗标收集器, 接收运算过程中的所有信号。新上下文时为空,运行过程不自动清理,因此在调试某块代码之前先用clear_flags()方法进行清空。traps 陷阱启用器,其中列出的信号,只要flags捕捉到就会抛出异常。通常,新上下文应当只设置 traps 而让 flags 为空。参考信号小节。 Emin 和 Emax 字段给定指数所允许的范围。Emin 必须在 [MIN_EMIN, 0] 范围内,Emax 在 [0, MAX_EMAX] 范围内。MIN_EMIN最小精度,MAX_EMAX最大精度,参看《常量表》。 capitals 指数标识字母,为 0 或 1 (默认值)。如果设为 1,打印时指数附带大写的E:Decimal('6.02E+23');否则使用小写的e: Decimal('6.02e+23')。 clamp 数据截取模式,字段为 0 (默认值) 或 1。 如果设为 1,则 Decimal 实例的指数 e 的表示范围在此上下文中将严格限制为 Emin - prec + 1 <= e <= Emax - prec + 1。一个超过最大限制的普通数值将在可能的情况下减小其指数并为其系数添加相应数量的零,以便符合指数值限制;这可以保持数字值但会增加实际上无效的尾零(凭空增加有效数字)。一个低于最小限制的数(系数变整数后,指数低于最小限制),这样生成时先等值把系数变为整数,然后左移小数点到符合指数限制,舍入小数点后的数即得结果(显示结果为0,实际是次标准数。)。
例1:prec=6, Emax=999, clamp=1,则e<= 999 - 6 + 1=994,也就是说指数不大于994。那么1.23×10^999要转换为指数是994来表示,所以系数的小数点需要向右移动5位,即123000×10^994,打印出来便是1.23000×10^999。
例2:prec=6, Emin=-999, clamp=1,则e>=-999- 6+1=-1004,也就是说指数最小-1004。那么1.23×10^-1003转换系数为整数时是123×10^-1005,低于-1004的限制,要转换为指数是-1004来表示,所以系数的小数点需要向左移动1位成12.3×10^-1004,然后小数点后的数字进行舍入操作(因为小数点后的数字指数已低于-1004),即12×10^-1004,打印出来便是1.2×10^-1003。 如果 clamp 为 0 则将采用较灵活的方式。 Decimal实例用科学记数法调整后的指数最大值为Emax:。具体的实施方法:移动小数点使系数为带1位整数的小数,指数不超过Emax,超过会抛出异常,超过精度舍入多余数字,不超过无需补充0。 例3:prec=6, Emax=999, clamp=0,对于123.4567×10^992,转科学记数法是1.234567×10^994,指数未超999,精度已超过6,需要舍入,最后结果是1.23457×10^994。 Decimal实例的系数转整数后指数最小值是Emin-prec+1,小于这个值也不会抛出异常,只是小数点要继续向左移位并补0,直到指数等于这个值,系数舍入为整数即得结果。 例4:prec=6, Emin=-999, clamp=0,对于1.23×10^-1003,系数变为整数后是123×10^-1005,由-1005< -999-6+1=-1004,系数的小数点需要向左移动1位,成为12.3×10^-1004,舍去小数部分得结果12×10^-1004,即1.2×10^-1003。
可见,对于下限,clamp是1和0没有区别。clamp=1时可能产生无效的尾0,不适合工程或科学计算。它的目的只是:允许与IEEE 754 中描述的固定宽度十进制交换格式保持兼容。
常量
注:这些常量配适自C语言。
decimal.HAVE_THREADS 该值为 True。已弃用,因为 Python 现在总是启用线程。
decimal.HAVE_CONTEXTVAR 默认值为 True。如果是自行编译Python并使用--without-decimal-contextvar选项来配置,那么C语言运行底层使用线程(thread-local)而不是协程(coroutine-local)本地上下文,这样它的值是False。这在一些嵌套上下文场景中稍快。 3.9 新版功能,向下移植到 3.7 和 3.8。
舍入模式
decimal.ROUND_CEILING
向上舍入或天花板式舍入,即大于该值的最小舍入数。舍入方向为 Infinity。
decimal.ROUND_DOWN 地板/天花板式向0舍入,对于大于0的数用ROUND_FLOOR方法(下面),对于小于0的数用ROUND_CEILING方法(上面)。
decimal.ROUND_FLOOR 向下舍入或地板式舍入,即小于该值的最大舍入数。舍入方向为 -Infinity。
decimal.ROUND_HALF_DOWN 4舍6入5向0——小于5舍去,大于5舍入,等于5看那个方法靠近0。舍入到最接近的数,同样接近则舍入方向为零(绝对值小的数)。
decimal.ROUND_HALF_EVEN 4舍6入5向偶——小于5舍去,大于5舍入,等于5看那个方法得到的是“偶数”(连续的两个数字,一定有一个是偶数)。舍入到最接近的数,同样接近则舍入到最接近的偶数。
decimal.ROUND_HALF_UP 4舍6入5背0——小于5舍去,大于5舍入,等于5看那个方法远离0。舍入到最接近的数,同样接近则舍入到零的反方向(绝对值较大)。这就是我们数学课里学的“四舍五入”。
decimal.ROUND_UP 天花板/地板式背0舍入,对于大于0的数用ROUND_CEILING方法,对于小于0的数用ROUND_FLOOR方法。舍入到零的反方向。
decimal.ROUND_05UP 如果向0舍入(ROUND_DOWN)方法得到最后一位是0或5,改用背0舍入(ROUND_UP)方法。换一种说法:如果舍入位是0或5用背0舍入(ROUND_UP)方法,否则用向0舍入(ROUND_DOWN)方法。
信号
信号代表在计算期间引发的条件。每个信号对应于一个上下文旗标和一个上下文陷阱启用器。 上下文旗标将在遇到特定条件时被设定。在完成计算之后,将为了获得信息而检测旗标(例如确定计算是否精确)。在检测旗标后,请确保在开始下一次计算之前清除所有旗标。 如果为信号设定了上下文的陷阱启用器,则条件会导致特定的 Python 异常被引发。举例来说,如果设定了 DivisionByZero 陷阱,则当遇到此条件时就将引发 DivisionByZero 异常。
class decimal.Clamped 修改一个指数以符合表示限制。 通常,限位将在一个指数超出上下文的 Emin 和 Emax 限制时发生。在可能的情况下,会通过给系数添加零来将指数缩减至符合限制。
class decimal.DecimalException 其他信号的基类,并且也是 ArithmeticError 的一个子类。
class decimal.DivisionByZero 非无限数被零除的信号。 可在除法、取余或对一个数求负数次幂时发生。如果此信号未被陷阱捕获,则返回 Infinity 或 -Infinity (由计算的输入来确定正负符号)。
class decimal.Inexact 表明发生了舍入且结果是不精确的。 有非零数位在舍入期间被丢弃的信号。舍入结果将被返回。此信号旗标或陷阱被用于检测结果不精确的情况。
class decimal.InvalidOperation 执行了一个无效的操作。 表明请求了一个无意义的操作。如未被陷阱捕获则返回 NaN。可能的原因包括下面的运算:
Decimal('Infinity') - Decimal('Infinity')
Decimal(0) * Decimal('Infinity')
Decimal('Infinity') / Decimal('Infinity')
Decimal(x) % Decimal(0)
Decimal('Infinity') % Decimal(x)
Decimal(-x).sqrt() and x > 0
getcontext().sqrt(Decimal(-x)) and x >0
Decimal(0) ** Decimal(0)
class decimal.Overflow 数值的溢出。 表明在发生舍入之后的指数大于 Emax。如果未被陷阱捕获,则结果将取决于舍入模式,或者向下舍入为最大的可表示有限数,或者向上舍入为 Infinity。无论哪种情况,都将引发 Inexact 和 Rounded 信号。
class decimal.Rounded 发生了舍入,但或许并没有信息丢失。 一旦舍入丢弃了数位就会发出此信号;即使被丢弃的数位是零 (例如将 5.00 舍入为 5.0)。如果未被陷阱捕获,则不经修改地返回结果。此信号用于检测有效位数的丢弃。
class decimal.Subnormal 在舍入之前指数低于 Emin。 当操作结果是次标准数(即指数过小)时就会发出此信号。如果未被陷阱捕获,则不经修改过返回结果。
class decimal.Underflow 数字向下溢出导致结果舍入到零。 当一个次标准数结果通过舍入转为零时就会发出此信号。同时还将引发 Inexact 和 Subnormal 信号。
class decimal.FloatOperation 为 float 和 Decimal 的混合启用更严格的语义。 如果信号未被捕获(默认),则在 Decimal 构造器、create_decimal() 和所有比较运算中允许 float 和 Decimal 的混合。转换和比较都是完全精确的。发生的任何混合运算都将通过在上下文旗标中设置 FloatOperation 来静默地记录。通过 from_float() 或 create_decimal_from_float() 进行显式转换则不会设置旗标。 在其他情况下(即信号被捕获),则只静默执行相等性比较和显式转换。所有其他混合运算都将引发 FloatOperation。
下表格总结了信号的层级结构:
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)