Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterate though all possible floating-point values, starting from lowest

I am writing a unit test for a math function and I would like to be able to "walk" all possible floats/doubles.

Due to IEEE shenanigans, floating types cannot be incremented (++) at their extremities. See this question for more details. That answer states :

one can only add multiples of 2^(n-N)

But never mentions what little n is.

A solution to iterate all possible values from +0.0 to +infinity is given in this great blog post. The technique involves using a union with an int to walk the different values of a float. This works due to the following properties explained in the post, though they are only valid for positive numbers.

  1. Adjacent floats have adjacent integer representations
  2. Incrementing the integer representation of a float moves to the next representable float, moving away from zero

His solution for +0.0 to +infinity (0.f to std::numeric_limits<float>::max()) :

union Float_t {
    int32_t RawExponent() const { return (i >> 23) & 0xFF; }
    int32_t i;
    float f;
};

Float_t allFloats;
allFloats.f = 0.0f;
while (allFloats.RawExponent() < 255) {
    allFloats.i += 1;
}

Is there a solution for -infinity to +0.0 (std::numeric_limits<float>::lowest() to 0.f)?

I've tested std::nextafter and std::nexttoward and couldn't get them to work. Maybe this is an MSVC issue?

I would be ok with any sort of hack since this is a unit test. Thanks!

like image 355
scx Avatar asked Nov 01 '25 15:11

scx


2 Answers

You can walk all 32-bit bit representations by using all values of a 32-bit unsigned int. Then you will walk really all representations, positive and negative, including both nulls (there are two) and also all the not a number representations (NaN). You may or may not want to filter out the NaN representations, or just filter out the signaling ones and leave the non signaling ones in. This depends on your use case.

Example:

for (uint32_t i = 0;;)
{
    float f;
    // Type punning: Force the bit representation of i into f.
    // Type punning is hard because mostly undefined in C/C++. 
    // Using memcpy() usually avoids any type punning warning.
    memcpy(&f, &i, sizeof(f));

    // Use f here.
    // Warning: Using signaling NaNs may throw exceptions or raise signals.

    i++;
    if (i == 0)
        break;
}

Instead you can also walk a 32-bit int from -2**31 to +(2**31-1). This makes no difference.

like image 62
Johannes Overmann Avatar answered Nov 03 '25 05:11

Johannes Overmann


Pascal Cuoq correctly points out std::nextafter is the right solution. I had a problem elsewhere in my code. Sorry for the unnecessary question.

#include <cassert>
#include <cmath>
#include <limits>

float i = std::numeric_limits<float>::lowest();
float hi = std::numeric_limits<float>::max();
float new_i = std::nextafterf(i, hi);
assert(i != new_i);

double d = std::numeric_limits<double>::lowest();
double hi_d = std::numeric_limits<double>::max();
double new_d = std::nextafter(d, hi_d);
assert(d != new_d);

long double ld = std::numeric_limits<long double>::lowest();
long double hi_ld = std::numeric_limits<long double>::max();
long double new_ld = std::nextafterl(ld, hi_ld);
assert(ld != new_ld);


for (float d = std::numeric_limits<float>::lowest();
        d < std::numeric_limits<float>::max();
        d = std::nextafterf(
                d, std::numeric_limits<float>::max())) {
    // Wait a lifetime?
}
like image 43
scx Avatar answered Nov 03 '25 03:11

scx