15

Does Python provide a function to get the floating-point value that results from incrementing the least significant bit of an existing floating-point value?

I'm looking for something similar to the std::nextafter function that was added in C++11.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • I believe no, but just trying to see how `std::nextafter` is implemented and may be we can come up with something equivalent. – Abhijit May 02 '12 at 20:11
  • My cython based solution should work on windows (saw you're a windows guy in your profile). You can install cython from pip. Requires gcc/g++. You may have to set the libraries,library_dirs and include_dirs paths in the setup.py, they are the standard file paths you would pass to your compiler to build something that uses cmath. Not sure what those paths would look like on windows or even if they would be needed (probably not), but no reason it shouldn't work. – Endophage May 02 '12 at 20:51
  • No, it doesn't. The easiest way to fake it is to use the struct module to convert to an 8-byte int, add one to the int, and convert back. That works fine for positive numbers, and needs some tweaking for negative numbers. – Mark Dickinson May 02 '12 at 21:06
  • @MarkDickinson, you also need tweaks for NAN, INF, overflow into the exponent, etc. There's a lot of edge cases. – Mark Ransom May 02 '12 at 21:08
  • @MarkRansom Hence use cython to wrap the existing functions... don't bother re-inventing the wheel... – Endophage May 02 '12 at 21:18
  • @MarkRansom: NaNs and infinities, yes. Overflow into the exponent, no. That bit 'just works', as does dealing with subnormals. IEEE 754 floating point is nicely arranged so that for the whole range [0, infinity), getting the next number is as simple as treating the bits as a representation of an integer and incrementing by one. – Mark Dickinson May 03 '12 at 05:39
  • @MarkRansom For clarity, I've put this into an answer. It covers *all* the edge cases. – Mark Dickinson May 03 '12 at 06:12

3 Answers3

28

Here are five (really four-and-a-half) possible solutions.

Solution 1: use Python 3.9 or later

Python 3.9, released in October 2020, includes a new standard library function math.nextafter which provides this functionality directly: use math.nextafter(x, math.inf) to get the next floating-point number towards positive infinity. For example:

>>> from math import nextafter, inf
>>> nextafter(100.0, inf)
100.00000000000001

It's a bit easier to verify that this function really is producing the next float up if you look at the hexadecimal representation, provided by the float.hex method:

>>> 100.0.hex()
'0x1.9000000000000p+6'
>>> nextafter(100.0, inf).hex()
'0x1.9000000000001p+6'

Python 3.9 also introduces a closely related and frequently useful companion function math.ulp which gives the difference between a value and the next value away from zero:

>>> from math import ulp
>>> nextafter(100.0, inf) - 100.0
1.4210854715202004e-14
>>> ulp(100.0)
1.4210854715202004e-14

Solution 2: use NumPy

If you don't have Python 3.9 or later, but you do have access to NumPy, then you can use numpy.nextafter. For regular Python floats, the semantics match those of math.nextafter (though it would be fairer to say that Python's semantics match NumPy's, since NumPy had this functionality available long before Python did).

>>> from numpy import nextafter, inf
>>> nextafter(100.0, inf)
100.00000000000001

Solution 3: wrap C's nextafter yourself

C specifies a nextafter function in math.h (see for example section 7.12.11.3 of C99); this is exactly the function that Python >= 3.9 wraps and exposes in its math module. If you don't have Python 3.9 or later, you can either use ctypes or cffi to dynamically call C's nextafter, or alternatively write a simple Cython wrapper or Python C extension that exposes C's nextafter. The details of how to do this are already well-explained elsewhere: in @Endophage's answer to this question, and in this answer to a similar StackOverflow question (the one that this question is closed as a duplicate of).

Solution 4: bit manipulation via the struct module

If you're willing to make the (almost always safe in practice) assumption that Python is using IEEE 754 floating-point, it's quite easy to write a Python function to provide nextafter. A little bit of care is needed to get all the corner cases right.

The IEEE 754 binary floating-point formats are cleverly designed so that moving from one floating-point number to the 'next' one is as simple as incrementing the bit representation. This works for any number in the range [0, infinity), right across exponent boundaries and subnormals. To produce a version of nextUp that covers the complete floating-point range, you also need to deal with negative numbers, infinities, nans, and one special case involving negative zero. Below is a standards compliant version of IEEE 754's nextUp function in Python. It covers all the corner cases.

import math
import struct

def 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]

The implementations of nextDown and nextAfter then look like this. (Note that nextAfter is not a function specified by IEEE 754, so there's a little bit of guesswork as to what should happen with IEEE special values. Here I'm following the IBM Decimal Arithmetic standard that Python's decimal.Decimal class is based on.)

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)

(Partial) solution 5: floating-point operations

If x is a positive not-too-tiny float and you're willing to assume IEEE 754 binary64 format and semantics, there's a surprisingly simple solution: the next float up from x is x / (1 - 2**-53), and the next float down from x is x * (1 - 2**-53).

In more detail, suppose that all of the following are true:

  • You don't care about IEEE 754 corner cases (zeros, infinities, subnormals, nans)
  • You can assume not only IEEE 754 binary64 floating-point format, but also IEEE 754 binary64 semantics: namely that all basic arithmetic operations are correctly rounded according to the current rounding mode
  • You can further assume that the current rounding mode is the IEEE 754 default round-ties-to-even mode.

Then the quantity 1 - 2**-53 is exactly representable as a float, and given a positive non-subnormal Python float x, x / (1 - 2**-53) will match nextafter(x, inf). Similarly, x * (1 - 2**-53) will match nextafter(x, -inf), except in the corner case where x is the smallest positive normal value, 2**-1022.

There's one thing to be careful of when using this: the expression 2**-53 will invoke your pow from your system's math library, and it's generally not safe to expect pow to be correctly rounded. There are many safer ways to compute this constant, one of which is to use float.fromhex. Here's an example:

>>> d = float.fromhex('0x1.fffffffffffffp-1')  # 1 - 2**-53, safely
>>> d
0.9999999999999999
>>> x = 100.0
>>> x / d  # nextup(x), or nextafter(x, inf)
100.00000000000001
>>> x * d  # nextdown(x), or nextafter(x, -inf)
99.99999999999999

These tricks work right across the normal range of floats, including for awkward cases like exact powers of two.

For a sketch of a proof: to show that x / d matches nextafter(x, inf) for positive normal x, we can scale by a power of two without affecting correctness, so in the proof we can assume without loss of generality that 0.5 <= x < 1.0. If we write z for the exact mathematical value of x / d (thought of as a real number, not a floating-point number), then z - x is equal to x * 2**-53 / (1 - 2**-53). Combining with the inequality 0.5 <= x <= 1 - 2**-53, we can conclude that 2**-54 < z - x <= 2**-53, which since floats are spaced exactly 2**-53 apart in the interval [0.5, 1.0], is enough to guaranteed that the closest float to z is nextafter(x, inf). The proof for x * d is similar.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
  • 1
    @wim: Fixed. Though "outdated" is a stretch when `math.nextafter` isn't yet available in any released version of Python. – Mark Dickinson Apr 10 '20 at 08:15
  • Looks good (upvoted). But it might also be worth dropping a mention to `numpy.nextafter` in your answer. I think most users finding this question might prefer to use a battle-tested library solution rather than defining their own function. – wim Apr 10 '20 at 18:17
  • @wim: Added `numpy.nextafter`, along with an evil quick-and-dirty solution that doesn't cover all the corner cases but is sometimes enough in practice. – Mark Dickinson Apr 11 '20 at 12:25
2

UPDATE:

Turns out this is a duplicate question (which comes up in google as result #2 for the search "c++ nextafter python"): Increment a python floating point value by the smallest possible amount

The accepted answer provides some solid solutions.

ORIGINAL ANSWER:

Certainly this isn't the perfect solution but using cython just a few lines will allow you to wrap the existing C++ function and use it in Python. I've compiled the below code and it works on my ubuntu 11.10 box.

First, a .pyx file (I called mine nextafter.pyx) defines your interface to the C++:

cdef extern from "cmath":
    float nextafter(float start, float to)

def pynextafter(start, to):
    cdef float float_start = float(start)
    cdef float float_to = float(to)
    result = nextafter(start, to)
    return result

Then a setup.py defines how to build the extension:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext 

ext_modules=[
    Extension("nextafter",
        ["nextafter.pyx"],
        libraries=[],
        library_dirs=[],
        include_dirs=[],
        language="c++",
    )
]

setup(
    name = "nextafter",
    cmdclass = {"build_ext": build_ext},
    ext_modules = ext_modules
)

Make sure those are in the same directory then build with python setup.py build_ext --inplace. I hope you can see how you would add the other variations of nextafter to the extension (for doubles, etc...). Once built, you should have a nextafter.so. Fire up python in the same directory (or put nextafter.so on your path somewhere) and you should be able to call from nextafter import pynextafter.

Enjoy!

Community
  • 1
  • 1
Endophage
  • 21,038
  • 13
  • 59
  • 90
  • Strangely enough I have an answer on that question. – Mark Ransom May 03 '12 at 01:12
  • I think you need to replace `float` with `double` everywhere in your `.pyx` file. (Or use `nextafterf` if you really want to use floats, but since Python floats correspond to C++ doubles, the double version would make more sense.) – Mark Dickinson May 03 '12 at 19:35
  • @MarkDickinson fair enough, that makes sense. Although from the linked C++ docs nextafter appears to be polymorphic in cmath so I'd really just want to change the `cdef extern...` import to be the double version of nextafter. – Endophage May 03 '12 at 23:33
  • Agreed; that should do it. Without that, you'll be rounding the inputs to C++ floats before doing the computation. E.g., with your current code I get `nextafter(1.3, 2.0)` -> `1.2999999523162844`, a result that's *less* than the original value of `1.3`! – Mark Dickinson May 04 '12 at 06:16
0

Check out http://docs.python.org/library/stdtypes.html#float.hex

Let's try this an implementation that doesn't know much about next after.

First, we need to extract the hex part and the exponent from the hex string:

def extract_parts(hex_val):
    if not hex_val.startswith('0x1.'):
        return None
    relevant_chars = hex_val[4:]
    if not len(relevant_chars) > 14 and relevant_chars[13] == 'p':
        return None
    hex_portion = int(relevant_chars[:13], 16)
    if relevant_chars[14] == '+':
        p_val = int(relevant_chars[15:])
    elif relevant_chars[14] == '-':
        p_val = -int(relevant_chars[15:])
    else:
        return None
    return (hex_portion, p_val)

Then we need a way to increment in positive or negative direction (we'll assume the hex string has already been converted to an integer hex_portion):

def increment_hex(hex_portion, p_val, direction):
    if hex_portion == 0 and direction == -1:
        new_hex = 'f' * 13
        p_val -= 1
    elif hex_portion == int('f' * 13, 16) and direction == 1:
        new_hex = '0' * 13
        p_val += 1
    else:
        new_hex = hex(hex_portion + direction)[2:].rstrip('L').zfill(13)

    if len(new_hex) != 13:
        return None
    return format_hex(new_hex, p_val)

We need a helper function to reformat an acceptable hex string and exponent, which I used above:

def format_hex(hex_as_str, p_val):
    sign = '-' if p_val < 0 else '+'
    return '0x1.%sp%s%d' % (hex_as_str, sign, p_val)

Finally, to implement nextafter:

def nextafter(float_val):
    hex_equivalent = float_val.hex()
    hex_portion, p_val = extract_parts(hex_equivalent)

    direction = 1
    new_hex_equiv = increment_hex(hex_portion, p_val, direction)
    return float.fromhex(new_hex_equiv)
bossylobster
  • 9,993
  • 1
  • 42
  • 61