珍爱生命,使用numpy.seterr

inf或者NaN是数值计算中最臭名昭著的两个变量了,他们性质诡异,而且往往会带来极其难以排查的bug:

  • 一般而言正常配置的计算过程是不会出现inf或者NaN的,一旦出现往往意味着计算过程在某个地方出了问题。但根据浮点数的定义infNaN是有效的浮点数,因此这一bug暂时不会引起任何副作用,直到运行了对inf或者NaN进行检查的函数才会报错,甚至一直不报错显示到前端。

幸运的是,如果程序是以NumPy作为数值运算的基础,可以利用numpy.seterr进行全局配置,使NumPy在遇到这类数值错误(比如说浮点数溢出)时立即报错/警告/调用回调函数。

这个函数的使用方法在文档中已经有了详细的介绍,API全面好用,不用多说。下面介绍一点我自己琢磨出来的最佳实践。

文档中可以看出,目前NumPy支持的运算错误包括四种,定义于IEEE 754 标准

  • 被0除(divide by zero)。
  • 上溢出(overflow),最常见的情况就是一个大的数被放到了exp的上面进而导致inf溢出。
  • 下溢出(underflow),和上溢出恰好相反,一个非常小(绝对值很大)的数被放到exp的上面,导致最后的结果是0。
  • 无效操作(invalid operation),比如对负数开方,结果是NaN

这四种情况中,一般来说只有三个负面作用比较大,那就是被0除,上溢出和无效操作。为什么下溢出负面作用小呢?因为下溢出本身不产生inf或者NaN,引起的误差可以认为是广义的浮点误差,一般来说计算可以继续。比如说某个物体移动的速度指数衰减,那么我们在时间\(t\)很大的时候可以自然地认为它的速度就是0,它在一段时间的位移也是0,得到的总体来说是可靠的结果。相反,如果某个物体的移动速度是指数增长的,在较大的\(t\)时刻其速度就变成了inf,位移也是inf,虽然从数学定义上来说不算错,但这样的计算显然是不可继续的,得到的结果显示到前端也是非常不友好的。
因此虽然seterr函数支持一个all的关键字一次性为所有可能的数值错误配置处理方式,一般而言对于被0除、上溢出和无效操作可以设置较高的错误级别,而对下溢出保持默认的ignore或者设置成warn即可。

另外Python语言本身对内置的float也带有一个类似的功能,定义于fpectl模块中。但Python毕竟不是专注于数值计算的库而是一个应用广泛的编程语言,这一模块受到很多限制,默认不被包含在Python中。