这里有五个(实际上是四个半)解决方案。
解决方案1:使用Python 3.9或更高版本
2020年10月发布的Python
3.9包括一个新的标准库函数
math.nextafter,该函数直接提供此功能:用于
math.nextafter(x,math.inf)将下一个浮点数向正无穷大。例如:
>>> from math import nextafter, inf>>> nextafter(100.0, inf)100.00000000000001
如果查看方法提供的十六进制表示,则可以更容易地验证此函数是否确实 在
产生下一个浮点数
float.hex:
>>> 100.0.hex()'0x1.9000000000000p+6'>>> nextafter(100.0, inf).hex()'0x1.9000000000001p+6'
Python
3.9还引入了一个密切相关且经常有用的伴随函数
math.ulp,该函数给出一个值与下一个远离零的值之间的差:
>>> from math import ulp>>> nextafter(100.0, inf) - 100.01.4210854715202004e-14>>> ulp(100.0)1.4210854715202004e-14
解决方案2:使用NumPy
如果您没有Python
3.9或更高版本,但是可以访问NumPy,则可以使用
numpy.nextafter。对于常规Python
float,其语义与匹配
math.nextafter(尽管说Python的语义与NumPy的语义匹配会更公平,因为NumPy在Python之前
很早就 可以使用此功能)。
>>> from numpy import nextafter, inf>>> nextafter(100.0, inf)100.00000000000001
解决方案3:nextafter
自己包装C
C在中指定
nextafter功能
math.h(例如,参见C99的7.12.11.3节);这正是Python> =
3.9包装并在其
math模块中公开的函数。如果您没有Python
3.9或更高版本,则可以使用
ctypes或
cffi动态调用C
nextafter,也可以编写一个简单的Cython包装器或公开C的Python
C扩展
nextafter。
解决方案4:通过struct
模块进行位操作
如果您愿意(在实践中几乎总是安全的)假设Python正在使用IEEE
754浮点,那么编写提供的Python函数非常容易
nextafter。需要一些注意才能使所有极端情况正确。
IEEE 754二进制浮点格式经过精心设计,因此从一个浮点数到“下一个”数的转换就像递增位表示一样简单。它适用于范围内的任何数字
[0,infinity),可以跨越指数边界和次法线。要生成
nextUp覆盖整个浮点范围的版本,您还需要处理负数,无穷大,nan和涉及负零的一种特殊情况。以下是
nextUpPython中IEEE
754功能的标准兼容版本。它涵盖了所有极端情况。
import mathimport structdef nextup(x): # NaNs and positive infinity map to themselves. if math.isnan(x) or (math.isinf(x) and x > 0): return x # 0.0 and -0.0 both map to the smallest +ve float. if x == 0.0: x = 0.0 n = struct.unpack('<q', struct.pack('<d', x))[0] if n >= 0: n += 1 else: n -= 1 return struct.unpack('<d', struct.pack('<q', n))[0]的实施
nextDown和
nextAfter再这个样子。(请注意,这
nextAfter不是IEEE
754所指定的函数,因此对于IEEE特殊值应该发生的情况有一些猜测。在这里,我遵循Python的
decimal.Decimal类所基于的IBM
Decimal Arithmetic标准。)
def nextdown(x): return -nextup(-x)def nextafter(x, y): # If either argument is a NaN, return that argument. # This matches the implementation in decimal.Decimal if math.isnan(x): return x if math.isnan(y): return y if y == x: return y elif y > x: return nextup(x) else: return nextdown(x)
(部分)解决方案5:浮点运算
如果
x是肯定的但不太
float愿意,并且您愿意假设IEEE 754
binary64格式和语义,则有一个非常简单的解决方案:下一个从
xis向上浮动
x / (1 - 2**-53),下一个从
xis向下浮动
x *(1 - 2**-53)。
更详细地,假设满足以下所有条件:
- 您无需担心IEEE 754极端情况(零,无穷大,次法线,nans)
- 您不仅可以假定IEEE 754 binary64浮点 格式 ,还可以假定IEEE 754 binary64 语义 :即,所有基本算术运算均已根据当前舍入模式正确舍入。
- 您可以进一步假设当前的舍入模式为IEEE 754默认的“舍入为偶数”模式。
然后,该数量
1 - 2**-53可以精确地表示为a
float,并且给定非正态Python浮点正
x,
x / (1 -2**-53)将会匹配
nextafter(x, inf)。类似地,
x * (1 - 2**-53)将匹配
nextafter(x,-inf),除了在
x最小正法向值的拐角情况下
2**-1022。
使用此方法时要注意一件事:表达式
2**-53将从
pow系统的数学库中调用您,通常期望
pow正确取整并不安全。有许多更安全的方法可以计算此常数,其中一种是使用
float.fromhex。这是一个例子:
>>> d = float.fromhex('0x1.fffffffffffffp-1') # 1 - 2**-53, safely>>> d0.9999999999999999>>> x = 100.0>>> x / d # nextup(x), or nextafter(x, inf)100.00000000000001>>> x * d # nextdown(x), or nextafter(x, -inf)99.99999999999999这些技巧可在浮点数的正常范围内正常工作,包括在尴尬的情况下,例如精确的2的幂。
对于证据的草图:表明
x / d比赛
nextafter(x,inf)正正常的
x,我们可以通过两个动力,而不会影响正确性规模,因此在证明,我们可以不失一般性该损失承担
0.5 <= x <1.0。如果我们写
z了 精确 的数学值
x / d(认为是实数,而不是一个浮点数),则
z - x等于
x * 2**-53 / (1 -2**-53)。结合不等式
0.5 <= x <= 1 - 2**-53,我们可以得出结论
2**-54 < z - x <=2**-53,由于浮点数在间隔中正好
2**-53间隔开
[0.5, 1.0],因此足以保证最接近的浮点数
z是
nextafter(x,inf)。证明
x * d相似。



