'Is there a standard way to get the nth `nextafter` floating-point value in C++

C++ has std::nextafter(), which returns the next representable value after a given floating-point value f. In my case, I'd like to allow for n bits of slop in the lower mantissa bits, so 3 bits of slop would require getting the 8th next value after some given value f. I could call nextafter() eight times, but is there a better way to handle this?

For most values, you could get by with casting the FP value to uint_64, adding the tolerance (1<<3 for 3 bits of slop), and then casting back to double, thanks to the layout of IEEE 754. However, that relies on IEEE 754 floating point (a good assumption, but not rock solid either).

(For background, I want to use this to hoist ray-surface intersection points, which are occasionally located inside the surface due to FP imprecision. Those familiar with robust floating-point will understand why epsilon is a terrible solution.)



Solution 1:[1]

A naive approach could be to multiply by 8 the distance between a value and the next representable float, instead of calling 8 times std::nextafter

double advance_float(double x, int d)
{
    double step = std::copysign((std::nextafter(x, x + d) - x) * d, d);
    return x + step;
}

Here there are some tests, but it's up to you to determine if this is suitable for your use case.

Edit

As noted by Steve Hollash, x may be so big that x + d == d. Daniel Jour suggested to take advantage of frexp (and ldexp), but in the following attempt, I'll use a different approach to determine the direction.

double advance_float(double x, int d)
{
    const double to = std::copysign(std::numeric_limits<double>::infinity(), d);
    const double next = std::nextafter(x, to);
    return x + std::copysign(d * (next - x), d);
}

Note that it assumes that std::numeric_limits<double>::has_infinity == true, otherwise ::lowest() and ::max() must be used.

Those are some results

         x    d             previous                     x                   next
------------------------------------------------------------------------------------------
           1  1     0x1.fffffffffffffp-1                   0x1p+0     0x1.0000000000001p+0
           1  8     0x1.ffffffffffff8p-1                   0x1p+0     0x1.0000000000008p+0
     3.14159  8      0x1.921fb54442d1p+1     0x1.921fb54442d18p+1      0x1.921fb54442d2p+1
      100.01  8     0x1.900a3d70a3d69p+6     0x1.900a3d70a3d71p+6     0x1.900a3d70a3d79p+6
     -100.01  8    -0x1.900a3d70a3d79p+6    -0x1.900a3d70a3d71p+6    -0x1.900a3d70a3d69p+6
       1e+67  8   0x1.7bd29d1c87a11p+222   0x1.7bd29d1c87a19p+222   0x1.7bd29d1c87a21p+222
       1e-59  8    0x1.011c2eaabe7dp-196   0x1.011c2eaabe7d8p-196    0x1.011c2eaabe7ep-196
           0  8 -0x0.0000000000008p-1022                   0x0p+0  0x0.0000000000008p-1022
4.94066e-324  8 -0x0.0000000000007p-1022  0x0.0000000000001p-1022  0x0.0000000000009p-1022

Solution 2:[2]

As far as I know, there is no standard function for this. Boost has you covered though: See boost::math::float_advance. If you're using this for comparing two floats, you probably want boost::math::float_distance instead.

Solution 3:[3]

Using a little endian architecture, when i know my inputs i can do it :

#include <string.h>
#include <stdio.h>
#include <math.h>

double sunday_crafts_nextafter(double d, long long int step) {
    long long int tmp;
    memcpy(&tmp, &d, sizeof(d));
    tmp += d > 0 ? step : -step;
    memcpy(&d, &tmp, sizeof(d));
    return d;
}

double real_nextafter(double d, int step) {
    double b = step > 0 ? 1./0 : (step = -step, -1./0);
    for (int i = 0; i < step; ++i)
        d = nextafter(d, b);
    return d;
}

int main() {
    int step = 1 << 26;
    if (real_nextafter(1234, step) == sunday_crafts_nextafter(1234, step))
        if (real_nextafter(1234, -step) == sunday_crafts_nextafter(1234, -step))
            if (real_nextafter(-1234, step) == sunday_crafts_nextafter(-1234, step))
                if (real_nextafter(-1234, -step) == sunday_crafts_nextafter(-1234, -step))
                    puts("it works");
}


// to me "it works", res is 1234.000015258789100

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 John Kugelman
Solution 3