Python 浮動小数点値を可能な限り最小量だけ増加させる 質問する

Python 浮動小数点値を可能な限り最小量だけ増加させる 質問する

Python で浮動小数点値を可能な限り最小量だけ増加させるにはどうすればよいですか?


背景: 浮動小数点値を辞書のキーとして使用しています。

たまに、とても時々 (おそらく決してないかもしれませんが、絶対にないわけではありません) 衝突が発生します。浮動小数点値をできるだけ小さい量だけ増やすことで、これを解決したいと思います。どうすればいいでしょうか?

C では、仮数のビットを調整してこれを実現しますが、Python ではそれは不可能だと思います。

ベストアンサー1

Python 3.9以降ではmath.nextafterstdlib にあります。古いバージョンの Python の代替方法については、以下をお読みください。

Pythonの浮動小数点値を可能な限り最小量だけ増加します

次後(x,y)関数は、y 方向で x に続く、離散的に異なる表現可能な浮動小数点値を返します。nextafter() 関数は、プラットフォーム上で動作することが保証されているか、次の値が不可能であることを示す適切な値を返します。

これらnextafter()の機能は、POSIXおよびISOC99基準であり、Visual C の _nextafter()C99 準拠の標準数学ライブラリ、Visual C、C++、Boost、Java はすべて、IEEE 推奨の nextafter() 関数またはメソッドを実装しています。(正直なところ、.NET に nextafter() があるかどうかはわかりません。Microsoft は C99 や POSIX をあまり気にしていません。)

なしここでのビット操作関数は、値が 0.0、負の 0.0、非正規数、無限大、負の値、オーバーフローまたはアンダーフローなどになるなどのエッジ ケースを完全にまたは正しく処理します。これはC言語でのnextafter()のリファレンス実装です。それがあなたの方向性である場合に、正しいビット調整を行う方法のアイデアを提供します。

nextafter()Python < 3.9 で除外された POSIX 数学関数を取得または他の関数を取得するための確実な回避策が 2 つあります。

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

システム数学 DLL に直接リンクします。

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)

または、マーク・ディキンソンの素晴らしい解決

明らかにナンピー解決策は最も簡単です。

おすすめ記事