如何在Python中以最小可能的增量递增浮点数?
背景:我将浮点数用作字典键。
偶尔会出现碰撞,我希望通过以尽可能小的量递增浮点数来解决这些碰撞。如何实现?
在C语言中,我会操作曼特尼斯位来实现这一点,但我认为在Python中不可能这样做。
背景:我将浮点数用作字典键。
偶尔会出现碰撞,我希望通过以尽可能小的量递增浮点数来解决这些碰撞。如何实现?
在C语言中,我会操作曼特尼斯位来实现这一点,但我认为在Python中不可能这样做。
math.nextafter
。阅读以下内容以了解旧版本Python的替代方法。
nextafter(x,y)函数返回在方向y上接近x的下一个离散不同的可表示浮点值。 nextafter()函数保证在该平台上正常工作或者返回合理值以指示下一个值不可能存在。
nextafter()
函数是POSIX和ISO C99标准的一部分,并且在Visual C中为_nextafter()。符合C99标准的数学库,Visual C,C ++,Boost和Java都实现了IEEE推荐的nextafter()函数或方法。(老实说,我不知道.NET是否有nextafter()。 Microsoft并不太关注C99或POSIX。)
这里的位操作函数都无法完全或正确地处理边缘情况,例如数值经过0.0、负0.0、次标准数、无穷大、负数、溢出或下溢等。如果您需要正确的位操作,这里有一个C语言中nextafter()的参考实现可以为您提供思路。
在Python < 3.9中,有两个可靠的解决方案来获取nextafter()
或其他被排除的POSIX数学函数:
使用Numpy:
>>> import numpy
>>> numpy.nextafter(0,1)
4.9406564584124654e-324
>>> numpy.nextafter(.1, 1)
0.10000000000000002
>>> numpy.nextafter(1e6, -1)
999999.99999999988
>>> numpy.nextafter(-.1, 1)
-0.099999999999999992
import ctypes
import sys
from sys import platform as _platform
if _platform == "linux" or _platform == "linux2":
_libm = ctypes.cdll.LoadLibrary('libm.so.6')
_funcname = 'nextafter'
elif _platform == "darwin":
_libm = ctypes.cdll.LoadLibrary('libSystem.dylib')
_funcname = 'nextafter'
elif _platform == "win32":
_libm = ctypes.cdll.LoadLibrary('msvcrt.dll')
_funcname = '_nextafter'
else:
# these are the ones I have access to...
# fill in library and function name for your system math dll
print("Platform", repr(_platform), "is not supported")
sys.exit(0)
_nextafter = getattr(_libm, _funcname)
_nextafter.restype = ctypes.c_double
_nextafter.argtypes = [ctypes.c_double, ctypes.c_double]
def nextafter(x, y):
"Returns the next floating-point number after x in the direction of y."
return _nextafter(x, y)
assert nextafter(0, 1) - nextafter(0, 1) == 0
assert 0.0 + nextafter(0, 1) > 0.0
如果你真的非常想要一个纯Python解决方案:
# handles edge cases correctly on MY computer
# not extensively QA'd...
import math
# 'double' means IEEE 754 double precision -- c 'double'
epsilon = math.ldexp(1.0, -53) # smallest double that 0.5+epsilon != 0.5
maxDouble = float(2**1024 - 2**971) # From the IEEE 754 standard
minDouble = math.ldexp(1.0, -1022) # min positive normalized double
smallEpsilon = math.ldexp(1.0, -1074) # smallest increment for doubles < minFloat
infinity = math.ldexp(1.0, 1023) * 2
def nextafter(x,y):
"""returns the next IEEE double after x in the direction of y if possible"""
if y==x:
return y #if x==y, no increment
# handle NaN
if x!=x or y!=y:
return x + y
if x >= infinity:
return infinity
if x <= -infinity:
return -infinity
if -minDouble < x < minDouble:
if y > x:
return x + smallEpsilon
else:
return x - smallEpsilon
m, e = math.frexp(x)
if y > x:
m += epsilon
else:
m -= epsilon
return math.ldexp(m,e)
或者,使用Mark Dickinson出色的解决方案
显然,Numpy的解决方案是最简单的。
Decimal.next_plus()
е‡Ңж•°пәЊз”Ёжі•з±»дәәдғҺJavaзљ„Math.nextUp()
е‡Ңж•°гЂ‚иҮ¦жѓ…иҮ·еЏ‚иЂѓиү™дёҒstackoverflowй“ңжҺӨпәљhttps://dev59.com/questions/5lbTa4cB1Zd3GeqP-3V2#5756149 - jfsx
更大的一些数来提供给 nextafter
函数。假设您总是提供 x + 1
作为 y
参数;如果 x
非常接近最大可能值,这将给您带来错误的答案。也许更方便的方法是考虑 nextafter
的 y
参数的符号,以指示所需的是增量还是减量。 - wberryfrom _testcapi import DBL_MAX,DBL_MIN,FLT_MAX,FLT_MIN
- JohnMudd从Python 3.9开始,发布于2020年10月5日,您可以使用math.nextafter
函数:
math.nextafter(x, y)
返回在 x 轴向 y 进行舍入后的下一个浮点数。
如果 x 等于 y,则返回 y。
例子:
math.nextafter(x, math.inf)
上升:朝正无穷方向。
math.nextafter(x, -math.inf)
下降:朝负无穷方向。
math.nextafter(x, 0.0)
靠近零。
math.nextafter(x, math.copysign(math.inf, x))
远离零。参见
math.ulp()
。
与math.copysign(math.inf, x)
相比,更简单的替代方法是直接使用2*x
。
math.nextafter(x, 2*x)
。 - Simply Beautiful Artmath.frexp
和sys.float_info.epsilon
分别操作尾数和指数。>>> m, e = math.frexp(4.0)
>>> (m+sys.float_info.epsilon)*2**e
4.0000000000000018
暂且不考虑为什么我们想要增加浮点数值,我认为Autopulated的回答可能是正确的。
但对于问题领域而言,我和大多数回答者一样对将浮点数用作字典键的想法表示疑虑。如果使用Decimal(正如主要评论中所提出的)存在“重量级”解决方案的反对意见,我建议您自己妥协:找出时间戳的实际分辨率,选择足够覆盖它的数字位数,然后将所有时间戳乘以必要的倍数,以便您可以使用整数作为键。如果您负担得起超出计时器精度的额外一两个数字,则可以更有信心地确保没有或减少碰撞,并且如果存在碰撞,则只需添加1(而不是一些繁琐的操作来查找下一个浮点值)。
不要对值进行递增,而是对冲突的键使用元组。如果您需要保持它们的顺序,则每个键都应该是一个元组,而不仅仅是重复项。
4.0 < ()
--> True
- kindall<strike>
标签,但只在IE上显示,所以我的编辑使答案变得非常混乱。现在应该解决了。 - Mark Ransom<
运算符,会得到一个 TypeError
错误。 - bignoseTypeError: unorderable types: float() < tuple()
- Brian Minton如果可能的话,我建议不要假定浮点数(或时间戳)是唯一的。使用计数迭代器、数据库序列或其他服务来发行唯一标识符。
import struct
def floatToieee754Bits(f):
return struct.unpack('<Q', struct.pack('<d', f))[0]
def ieee754BitsToFloat(i):
return struct.unpack('<d', struct.pack('<Q', i))[0]
def incrementFloat(f):
i = floatToieee754Bits(f)
if f >= 0:
return ieee754BitsToFloat(i+1)
else:
raise Exception('f not >= 0: unsolved problem!')
不要通过更改键来解决冲突,那么如何收集冲突呢?例如:
bag = {}
bag[1234.] = 'something'
变成
bag = collections.defaultdict(list)
bag[1234.].append('something')
有趣的问题。您需要添加的数量显然取决于冲突值的大小,以便规范化添加仅影响最不重要的位。
没有必要确定可以添加的最小值。所有你需要做的就是近似它。FPU格式提供52个尾数位加上一个隐藏位,共53位精度。没有任何物理常数达到这种精度水平。没有传感器能够测量到任何接近它的东西。因此,您没有遇到困难的问题。
在大多数情况下,对于键k,您将能够添加k/253,由于52位小数加上隐藏位。
但是,没有必要为了追求最后一位或靠近它的任何内容而冒险触发库错误或探索舍入问题。
1.可能需要多次添加,直到不再发生冲突,至少为了破坏任何恶毒的单元测试作者。
1 / 2 ** 1020.
的内容。 - DigitalRossimport sys
>>> sys.float_info.epsilon
2.220446049250313e-16
x+=x*sys.float_info.epsilon
。 - Mark Ransomsys.float_info.epsilon
被定义为“1.0和下一个可表示的最大值之间的最小差”,因此相对于其他值来说不安全。 - martineauepsilon = 2 * pow(2,-53)
。 - Mike T