文章目录
- 写在前面
- STEP.1 从一段代码开始
- STEP.2 灵活的精确度控制
- STEP.3 舍入控制
- 结束
写在前面
如果使用Python进行数据处理,那么对浮点类型的数据进行处理是避免不了的操作,这篇文章就对Python支持的浮点操作进行研究。
在开始之前,需要先明确两个概念,一个是浮点数精确度(和另一个概念双精度、单精度没直接关系),一个是小数点后的位数。
明确一下,本文提到的Python除非特殊说明,默认是指Python3。
举例如下浮点数精度
:可以理解为有效数字最大个数,比如“10.0023”和“0.000001”有效数字个数为6,那么精确度为6的浮点数变量就能精确保存这两个数。小数点位数
:这个是需要指定的小数点后保留多少位,一般还会跟随着指定超出的位数怎么处理,Python里面提供8种处理方式。
STEP.1 从一段代码开始
试着运行下面这段代码
from decimal import Decimal
from decimal import localcontext, getcontext
a = Decimal(12.345)
b = Decimal(1.000)
print("=" * 100)
print(a * b)
with localcontext() as ctx:
ctx.prec = 10
print(a * b)
print(a * b)
print("=" * 100)
print(a.quantize(Decimal('0.01'), rounding="ROUND_CEILING"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_DOWN"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_FLOOR"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_HALF_DOWN"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_HALF_EVEN"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_HALF_UP"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_UP"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_05UP"))
print("=" * 100)
g_ctx = getcontext()
g_ctx.rounding = "ROUND_CEILING"
print(g_ctx.quantize(a * b, Decimal('0.001')))
with localcontext() as ctx:
ctx.prec = 10
ctx.rounding = "ROUND_DOWN"
print((a * b).quantize(Decimal('0.001')))
print(g_ctx.quantize(a * b, Decimal('0.001')))
print("=" * 100)
输出
====================================================================================================
12.34500000000000063948846218
12.34500000
12.34500000000000063948846218
====================================================================================================
12.35
12.34
12.34
12.35
12.35
12.35
12.35
12.34
====================================================================================================
12.346
12.345
12.346
====================================================================================================
STEP.2 灵活的精确度控制
上面代码块中,ctx.prec = 10
就是对精确度进行制定。可以看出,默认精确度是28,也就是之前说的28个有效数字。但是使用with
的上下文方式来指定精确度,就可以限定精确度的影响范围。
with localcontext() as ctx:
ctx.prec = 10
print(a * b)
就是这段代码。为什么能生效?可以看一下这个localcontext
的源码。
def localcontext(ctx=None):
"""Return a context manager for a copy of the supplied context
Uses a copy of the current context if no context is specified
The returned context manager creates a local decimal context
in a with statement:
def sin(x):
with localcontext() as ctx:
ctx.prec += 2
# Rest of sin calculation algorithm
# uses a precision 2 greater than normal
return +s # Convert result to normal precision
def sin(x):
with localcontext(ExtendedContext):
# Rest of sin calculation algorithm
# uses the Extended Context from the
# General Decimal Arithmetic Specification
return +s # Convert result to normal context
>>> setcontext(DefaultContext)
>>> print(getcontext().prec)
28
>>> with localcontext():
... ctx = getcontext()
... ctx.prec += 2
... print(ctx.prec)
...
30
>>> with localcontext(ExtendedContext):
... print(getcontext().prec)
...
9
>>> print(getcontext().prec)
28
"""
if ctx is None: ctx = getcontext()
return _ContextManager(ctx)
这个方法返回了一个_ContextManager
类,继续进入看看。
class _ContextManager(object):
"""Context manager class to support localcontext().
Sets a copy of the supplied context in __enter__() and restores
the previous decimal context in __exit__()
"""
def __init__(self, new_context):
self.new_context = new_context.copy()
def __enter__(self):
self.saved_context = getcontext()
setcontext(self.new_context)
return self.new_context
def __exit__(self, t, v, tb):
setcontext(self.saved_context)
可以看出,这里面使用了一个上下文管理器。__enter__
方法定义了进入时刻要做的事情,也就是with localcontext()
时候要做的事情。__exit__
定义了with
代码块执行完了之后要做的事情。做的事情就是将当前上下文复制一份并且保存当前上下文,然后将当前上下文设置为新复制出来的上下文,等到退出的时候再将当前上下文进行还原。
copy
这个复制是深度复制方式,可以随意修改新复制出来的对象,而不会对原对象进行修改。同时重写了__copy__
方法。
def copy(self):
"""Returns a deep copy from self."""
nc = Context(self.prec, self.rounding, self.Emin, self.Emax,
self.capitals, self.clamp,
self.flags.copy(), self.traps.copy(),
self._ignored_flags)
return nc
__copy__ = copy
这样的话就可以在全局精确度设置的同时,需要单独处理的局部指定不同的精度。不只是对精度进行控制,还可以对舍入方式等进行指定。
STEP.3 舍入控制
舍入控制分为两个部分,一个是小数点后保留位数,一个是舍入方式。
print(a.quantize(Decimal('0.01'), rounding="ROUND_CEILING"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_DOWN"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_FLOOR"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_HALF_DOWN"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_HALF_EVEN"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_HALF_UP"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_UP"))
print(a.quantize(Decimal('0.01'), rounding="ROUND_05UP"))
quantize
就是进行舍入的设置,分别可以制定保留小数点后位数以及使用的舍入方式。舍入方式可以进行全局定义。
也可以局部定义。
with localcontext() as ctx:
ctx.prec = 10
ctx.rounding = "ROUND_DOWN"
print((a * b).quantize(Decimal('0.0001')))
支持的舍入操作
# 舍入方向 Infinity。
decimal.ROUND_CEILING
# 舍入方向为零。
decimal.ROUND_DOWN
# 舍入方向为 -Infinity。
decimal.ROUND_FLOOR
# 舍入到最接近的数,同样接近则舍入方向为零。
decimal.ROUND_HALF_DOWN
# 舍入到最接近的数,同样接近则舍入到最接近的偶数。
decimal.ROUND_HALF_EVEN
# 舍入到最接近的数,同样接近则舍入到零的反方向。
decimal.ROUND_HALF_UP
# 舍入到零的反方向。
decimal.ROUND_UP
# 如果最后一位朝零的方向舍入后为 0 或 5 则舍入到零的反方向;否则舍入方向为零。
decimal.ROUND_05UP
结束
本篇文章主要演示了Python的Decimal舍入相关操作,特别是在一些对浮点精度有要求的场景,一定要使用Decimal进行数据处理。
代码位置:https://github.com/huanghyw/py-notepad/blob/master/source/DecimalOpt.py