Python自带的decimal模块用于十进制数学计算,它是在浮点类型的基础上设计的,可以非常精确地在计算机中存储和计算,精度优于floating point,因为浮点数并不能精确的表示十进制数,因为计算机由底层CPU和IEEE 754标准通过自己的浮点单位去执行算术时的特征,因此对于精度要求高但效率不要求的场景,比如财务等,decimal可以较好的替换float类型。
Decimal重载了简单的算术运算符,所以可以采用内置数值类型同样的方式处理 Decimal实例。Decimal构造函数取一个整数或字符串作为参数。使用浮点数创建 Decimal 之前,可以先将浮点数转换为一个字符串,使调用者能够显式地处理值得位数,还可以由元组创建,其中包含一个符号标志(0 表示正,1 表示负)、数字 tuple 以及一个整数指数。
>>>
>>> f1 = 1.23
>>> f2 = 3.21
>>> f1 + f2
4.4399999999999995
>>>
>>> from decimal import Decimal
>>> from decimal import getcontext
>>>
>>> d1 = Decimal('1.23')
>>> d2 = Decimal('3.21')
>>> d3 = d1 + d2
>>> print(type(d3), d3)
4.44
>>> # 很准,位数也不变
>>>
>>>
>>> t1 = (1, (1, 1), -2)
>>> Decimal(t1)
Decimal('-0.11')
>>> t2 = (1, (1, 2), 3)
>>> Decimal(t2)
Decimal('-1.2E+4')
>>> t3 = (0, (1, 2), 3)
>>> Decimal(t3)
Decimal('1.2E+4')
>>>
>>> str(f3)
'2.468'
>>>
但相除或相乘的话,小数点的位数还是变了:
>>>
>>> d4 = d2 / d1
>>> d4
Decimal('2.609756097560975609756097561')
>>> d3
Decimal('4.44')
>>> d5 = d1 * d2
>>> d5
Decimal('3.9483')
>>>
可以通过getcontext().prec = x(x为你想要的精度来设置)来设置Decimal类型保留有效位数,注意不是保留小数的位数,如果要保留小数位数用round()。
>>>
>>> context = getcontext()
>>> context.prec = 3 # 保留3位数
>>> print(context)
Context(prec=3, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])
>>> d6 = d1 * d2
>>> d6
Decimal('3.95')
>>> d7 = d2 / d1
>>> d7
Decimal('2.61')
>>> d8 = d1 + d2
>>> d8
Decimal('4.44')
>>>
由上可以看到,设置一次getcontext().prec = x后,其下面的Decimal类型全部保留了x位数,有时不需要它管辖得太广,那么我们需要用localcontext创建一个局部上下文。
>>> from decimal import localcontext
>>>
>>> with localcontext() as ctx:
ctx.prec = 4
d9 = d2 / d1
d10 = d1 + d2
print(d9, d10)
2.610 4.44
>>> with localcontext() as ctx:
ctx.prec = 5
d11 = d2 / d1
d12 = d1 * d2
print(d11, d12)
2.6098 3.9483
>>>
>>> from decimal import Decimal
>>>
>>> v = Decimal(str(1.536842))
>>> v1 = Decimal(str(3.0125873))
>>> v
Decimal('1.536842')
>>> v1
Decimal('3.0125873')
>>>
>>> from decimal import localcontext
>>> with localcontext() as ctx:
ctx.prec = 5 # 保留5位有效数字
v2 = v + v1
v3 = v * v1
print(v2, v3)
4.5494 4.6299
>>>
>>> # 保留5位小数
>>> v4 = round(float(v + v1), 5)
>>> v4
4.54943
>>> v5 = round(float(v * v1), 5)
>>> v5
4.62987
>>>
如果觉得局部上下文设置精度太不灵活,要用的时候都得设置一次,那么还可以使用实例上下文。
>>>
>>> ctx1 = getcontext().copy() # 创建一个带精度的context
>>> ctx1.prec = 2 # 设置context精度
>>> ctx2 = getcontext().copy() # 创建一个带精度的context
>>> ctx2.prec = 5 # 设置context精度
>>>
>>> d13 = ctx1.create_decimal(str(f1))
>>> d13
Decimal('1.2')
>>> d14 = ctx1.create_decimal(str(f2))
>>> d14
Decimal('3.2')
>>> d13 + d14
Decimal('4.4')
>>> d13 * d14
Decimal('3.84')
>>> d14 / d13
Decimal('2.67')
>>> # 从上面看到,create_decimal创建Decimal实例时精度是对的,但是他们的实例进行算术运算后,精度就变了
>>> d15 = ctx2.create_decimal(str(f1))
>>> d15
Decimal('1.23')
>>> d16 = ctx2.create_decimal(str(f2))
>>> d16
Decimal('3.21')
>>> d15 + d16
Decimal('4.44')
>>> d15 * d16
Decimal('3.95')
>>> d16 / d15
Decimal('2.61')
>>>
>>> f3 = 2.468
>>> ctx3 = getcontext().copy() # 创建一个带精度的context
>>> ctx3.prec = 5 # 设置context精度
>>>
>>> d17 = ctx3.create_decimal(str(f3))
>>> d17
Decimal('2.468')
>>> d17 + d15
Decimal('3.70')
>>> d17 * d15
Decimal('3.04')
>>> d17 / d15
Decimal('2.01')
>>>
rounding取整,有多种选择,以保证值在所需精度范围内。
•ROUND_CEILING 总是趋向于无穷大向上取整。
•ROUND_DOWN 总是趋向 0 取整。
•ROUND_FLOOR 总是趋向负无穷大向下取整。
•ROUND_HALF_DOWN 如果最后一个有效数字大于或等于 5 则朝 0 反方向取整;否则,趋向 0 取整。
•ROUND_HALF_EVEN 类似于 ROUND_HALF_DOWN,不过,如果最后一个有效数字值为 5,则会检查前一位。偶数值会导致结果向下取整,奇数值导致结果向上取整。
•ROUND_HALF_UP 类似于 ROUND_HALF_DOWN,不过如果最后一位有效数字为 5,值会朝 0 的反方向取整。
•ROUND_UP 朝 0 的反方向取整。
•ROUND_05UP 如果最后一位是 0 或 5,则朝 0 的反方向取整;否则向 0 取整。
>>>
>>> import decimal
>>>
>>> context = decimal.getcontext()
>>> ROUNDING_MODES = [
'ROUND_CEILING',
'ROUND_DOWN',
'ROUND_FLOOR',
'ROUND_HALF_DOWN',
'ROUND_HALF_EVEN',
'ROUND_HALF_UP',
'ROUND_UP',
'ROUND_05UP',
]
>>> context.prec = 3
>>> for mode in ROUNDING_MODES:
context.rounding = getattr(decimal, mode)
value = decimal.Decimal(str(f3))
print(value)
2.468
2.468
2.468
2.468
2.468
2.468
2.468
2.468
>>>
>>> context.prec = 3
>>> for mode in ROUNDING_MODES:
context.rounding = getattr(decimal, mode)
value = decimal.Decimal(str(f3)) / decimal.Decimal('1.25689')
print(value)
1.97
1.96
1.96
1.96
1.96
1.96
1.97
1.96
除了期望的数字值,Decimal 还可以表示很多特殊值,包括正负无穷大值、“不是一个数”(NaN)和 0,python并没有特殊的语法来表示这些特殊的浮点值,但是可以使用float()来创建它们。
>>> import decimal
>>>
>>> for value in ['Infinity', 'inf', 'NaN', 'nan', '0']:
print (decimal.Decimal(value), decimal.Decimal('-' + value))
Infinity -Infinity
Infinity -Infinity
NaN -NaN
NaN -NaN
0 -0
与无穷大值相加会返回另一个无穷大值。与 NaN 比较相等性总会返回 false,而比较不等性总会返回 true。与 NaN 比较大小来确定排序顺序没有明确定义,这会导致一个错误。
>>>
>>> decimal.Decimal('inf') + 1
Decimal('Infinity')
>>> decimal.Decimal('-inf') + 100
Decimal('-Infinity')
>>> decimal.Decimal('nan') == decimal.Decimal('inf')
False
>>> decimal.Decimal('nan') == decimal.Decimal('-inf')
False
>>> decimal.Decimal('nan') != decimal.Decimal(0)
True
>>>