'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 |