Python 使用实数参数计数,count()
函数接收非整形参数,可以定义count(0.5, 0.1)
这样的表达式来提供浮点数。如果步长值没有合适的表示形式,会形成累积误差。最好使用(0.5 + x * 0.1 for x in count())
等整形count()
参数来避免累积误差出现。
下面介绍如何检验累积误差。探索实数近似值用到了函数式编程的一些实用技巧。
下面定义一个函数从迭代器中不断取值,直到满足给定条件。until()
函数定义如下:
from typing import Callable, Iterator TypeVar
T_ = TypeVar("T_")
def until(
terminate: Callable[[T_], bool],
iterator: Iterator[T_]
) -> T_:
i = next(iterator)
if terminate(i):
return i
return until(terminate, iterator)
首先从迭代器对象中取一个值,类型由类型变量T_
定义。如果这个值通过了测试,即取到了符合要求的值,迭代结束,返回值的类型也是由类型变量T_
定义的;否则递归调用函数自身,测试后面的值。
下面是一个可迭代对象实例和一个测试函数。
Generator = Iterator[Tuple[float, float]]
source: Generator = zip(count(0, 0.1), (.1*c for c in count()))
Extractor = Callable[[Tuple[float, float]], float]
x: Extractor = lambda x_y: x_y[0]
y: Extractor = lambda x_y: x_y[1]
Comparator = Callable[[Tuple[float, float]], bool]
neq: Comparator = lambda xy: abs(x(xy)-y(xy)) > 1.0E-12
生成器source
赋值语句中的类型标示表明它在二元组上迭代。两个抽取函数x()
和y()
从二元组中抽取实数。比较函数neq()
接收实数二元组,返回布尔值。
通过赋值语句创建匿名函数对象,其中的类型标示协助mypy工具确定参数和返回值类型。
mypy工具检查until()
函数时,将类型变量T_
关联到实际类型Tuple[float, float]
2。通过这个关联,能确认source
生成器和neq()
函数可以作为until()
函数的参数。
这是从原始数据文件中记录数据行号的常规做法。
until(neq, source)
在每次迭代中比较实数的近似值,直到二者出现显著差异。count()
函数给出其中一个近似值
生成器函数给出另一个近似值
理论上这两个值没有差别,但基于抽象表示的具体近似值存在差异。
计算结果如下所示:
>>> until(neq, source)
(92.799999999999, 92.80000000000001)
928次迭代后,累积误差达到了10^{-12},两个结果都没有精确的二进制表示。
count()
函数的示例接近了Python的递归上限。如果要测试更大的累积误差,需要用尾调用优化技术重写until()
函数。
计算能检测到的最小误差的方法如下:
>>> until(lambda x, y: x != y, source)
(0.6, 0.6000000000000001)
用相等检测代替了误差范围检测。6次迭代后,count(0, 0.1)
的累积误差达到了 10^{-16} ,用二进制表示\frac{1}{10}需要无限长的位数,实际存储时只能截取保存有限位数字,导致误差产生并累积。