Python3标准库:decimal定点数和浮点数的数学运算
1. decimal定点数和浮点数的数学运算
decimal模块实现了定点和浮点算术运算,使用的是大多数人所熟悉的模型,而不是程序员熟悉的模式(即大多数计算机硬件实现的IEEE浮点数运算)。Decimal实例可以准确的表示如何数,对其上火其下取整,还可以限制有效数字的个数。
1.1 Decimal
小数值被表示为Decimal类的实例。构造函数取一个整数或字符串作为参数。在使用浮点数创建Decimal之前,可以先将浮点数转换为一个字符串,以使调用者能够显式的处理值的位数,因为如果使用硬件浮点数表示则可能无法准确的表述。或者,类方法from_float()可以把浮点数转换为精确的小数表示。
import decimal fmt = "{0:<25} {1:<25}" print(fmt.format("Input", "Output")) print(fmt.format("-" * 25, "-" * 25)) # Integer print(fmt.format(5, decimal.Decimal(5))) # String print(fmt.format("3.14", decimal.Decimal("3.14"))) # Float f = 0.1 print(fmt.format(repr(f), decimal.Decimal(str(f)))) print("{:<0.23g} {:<25}".format( f, str(decimal.Decimal.from_float(f))[:25]) )
浮点值0.1并没有被表示为一个精确的二进制值,所以float的表示与Decimal值不同。在这个输出的最后一行,完整的字符串表示被截断为25个字符。
Decimal还可以由元组创建,其中包含一个符号标志(0表示正,1表示负)、由数位组成的一个tuple以及一个整数指数。
import decimal # Tuple t = (1, (1, 1), -2) print("Input :", t) print("Decimal:", decimal.Decimal(t))
基于元组的表示在创建时不太方便,不过它提供了一种可移植的方式,这样可以导出小数值而不损失精度。元组形式可以通过网络传输,或者在不支持精确小数值的数据库中存储,以后再转换回Decimal实例。
1.2 格式化
Decimal对应Python的字符串格式化协议,使用与其他数值类型一样的语法和选项。
import decimal d = decimal.Decimal(1.1) print("Precision:") print("{:.1}".format(d)) print("{:.2}".format(d)) print("{:.3}".format(d)) print("{:.18}".format(d)) print(" Width and precision combined:") print("{:5.1f} {:5.1g}".format(d, d)) print("{:5.2f} {:5.2g}".format(d, d)) print("{:5.2f} {:5.2g}".format(d, d)) print(" Zero padding:") print("{:05.1}".format(d)) print("{:05.2}".format(d)) print("{:05.3}".format(d))
格式字符串可以控制输出的宽度,精度(即有效数字个数),以及其填充值以占满宽度的方式。
1.3 算术运算
Decimal重载了简单的算术操作符,所以可以采用与内置数值类型相同的方式来处理Decimal实例。
import decimal a = decimal.Decimal("5.1") b = decimal.Decimal("3.14") c = 4 d = 3.14 print("a =", repr(a)) print("b =", repr(b)) print("c =", repr(c)) print("d =", repr(d)) print() print("a + b =", a + b) print("a - b =", a - b) print("a * b =", a * b) print("a / b =", a / b) print() print("a + c =", a + c) print("a - c =", a - c) print("a * c =", a * c) print("a / c =", a / c) print() print("a + d =", end=" ") try: print(a + d) except TypeError as e: print(e)
Decimal操作符还接受整数参数,不过,在这些操作符使用浮点值之前必须把浮点值转换为Decimal实例。
除了基本算术运算,Decimal还包括一些方法来查找以10为底的对数和自然对数。log10()和ln()返回的值都是Decimal实例,所以可以与其他值一样在公式中直接使用。
1.4 特殊值
除了期望的数字值,Decimal还可以表示很多特殊值,包括正负无穷大值、“不是一个数”(NaN)和0。
import decimal for value in ["Infinity", "NaN", "0"]: print(decimal.Decimal(value), decimal.Decimal("-" + value)) print() # Math with infinity print("Infinity + 1:", (decimal.Decimal("Infinity") + 1)) print("-Infinity + 1:", (decimal.Decimal("-Infinity") + 1)) # Print comparing NaN print(decimal.Decimal("NaN") == decimal.Decimal("Infinity")) print(decimal.Decimal("NaN") != decimal.Decimal(1))
与无穷大值相加会返回另一个无穷大值。与NaN比较相等性总会返回false,而比较不等性总会返回true。与NaN比较大小来确定排序顺序是未定义的,这会导致一个错误。
1.5 上下文
到目前为止,前面的所有例子使用的都是decimal模块的默认行为。还可以使用一个上下文(context)来覆盖某些设置,如保持的精度、如何完成取整、错误处理等。上下文可以应用于一个线程中的所有Decimal实例,或者在一个小代码区中本地应用。
1.5.1 当前上下文
要获取当前全局上下文,可以使用getcontext()。
import decimal context = decimal.getcontext() print("Emax =", context.Emax) print("Emin =", context.Emin) print("capitals =", context.capitals) print("prec =", context.prec) print("rounding =", context.rounding) print("flags =") for f, v in context.flags.items(): print(" {}: {}".format(f, v)) print("traps =") for t, v in context.traps.items(): print(" {}: {}".format(t, v))
这个示例脚本显示了Context的公共属性。
1.5.2 精度
上下文的prec属性控制了作为算术运算结果创建的新值所要保持的精度。字面量值会按这个属性保持精度。
import decimal d = decimal.Decimal("0.123456") for i in range(1, 5): decimal.getcontext().prec = i print(i, ":", d, d * 1)
要改变精度,可以直接为这个属性赋一个1到decimal.MAX_PREC之间的新值。
1.5.3 取整
取整有多种选择,以保证值在所需的精度范围内。
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", ] header_fmt = "{:10} " + " ".join(["{:^8}"] * 6) print(header_fmt.format( " ", "1/8 (1)", "-1/8 (1)", "1/8 (2)", "-1/8 (2)", "1/8 (3)", "-1/8 (3)", )) for rounding_mode in ROUNDING_MODES: print("{0:10}".format(rounding_mode.partition("_")[-1]), end=" ") for precision in [1, 2, 3]: context.prec = precision context.rounding = getattr(decimal, rounding_mode) value = decimal.Decimal(1) / decimal.Decimal(8) print("{0:^8}".format(value), end=" ") value = decimal.Decimal(-1) / decimal.Decimal(8) print("{0:^8}".format(value), end=" ") print()
这个程序显示了使用不同算法将同一个值取整为不同精度的效果。
1.5.4 本地上下文
可以使用with语句对一个代码块应用上下文。
import decimal with decimal.localcontext() as c: c.prec = 2 print("Local precision:", c.prec) print("3.14 / 3 =", (decimal.Decimal("3.14") / 3)) print() print("Default precision:", decimal.getcontext().prec) print("3.14 / 3 =", (decimal.Decimal("3.14") / 3))
Context支持with使用的上下文管理器API,所以这个设置只在块内应用。
1.5.5 各实例的上下文
还可以用上下文构造Decimal实例,然后从这个上下文继承精度以及转换的取整参数。
import decimal # Set up a context with limited precision c = decimal.getcontext().copy() c.prec = 3 # Create our constant pi = c.create_decimal("3.1415") # The constant value is rounded off print("PI :", pi) # The result of using the constant uses the global context print("RESULT:", decimal.Decimal("2.01") * pi)
例如,这样一来,应用就可以选择与用户数据精度不同的常用值精度。
1.5.6 线程
“全局”上下文实例上是线程本地上下文,所以完全可以使用不同的值分别配置各个线程。
import decimal import threading from queue import PriorityQueue class Multiplier(threading.Thread): def __init__(self, a, b, prec, q): self.a = a self.b = b self.prec = prec self.q = q threading.Thread.__init__(self) def run(self): c = decimal.getcontext().copy() c.prec = self.prec decimal.setcontext(c) self.q.put((self.prec, a * b)) a = decimal.Decimal("3.14") b = decimal.Decimal("1.234") # A PriorityQueue will return values sorted by precision, # no matter what order the threads finish. q = PriorityQueue() threads = [Multiplier(a, b, i, q) for i in range(1, 6)] for t in threads: t.start() for t in threads: t.join() for i in range(5): prec, value = q.get() print("{} {}".format(prec, value))
这个例子使用指定的值来创建一个新的上下文,然后安装到每个线程中。