Search code examples
c++floating-pointunspecified-behavior

How to check if the result of std::frexp is specified under the current compiler?


std::frexp is a function that removes exponent from a floating point variable and puts it into a variable exp of type int. It uses int regardless of how many bits the exponent really needs and according to the linked page:

If the value to be stored in *exp is outside the range of int, the behavior is unspecified.

So, how do I check whether "*exp is outside the range of int"?

I am thinking about adding a static_assert to my code that compares FLT_MIN_EXP & FLT_MAX_EXP to INT_MIN & INT_MAX. However, I'm afraid of making an off-by-one error because I don't fully comprehend descriptions of these constants.

FLT_MIN_EXP:

minimum negative integer such that FLT_RADIX raised by power one less than that integer is a normalized float...

FLT_MAX_EXP:

maximum positive integer such that FLT_RADIX raised by power one less than that integer is a representable finite float...

(I already have static_assert(FLT_RADIX == 2); in the code so radix equal to 10 is not a concern in my case.)


Solution

  • Assertion 1: This operation usually works fine, and you don't need to worry about anything.

    Assertion 2: For peace of mind, if there's every a system where this doesn't work, we want to know about it. We'll revisit a solution when we find one of those systems.

    Assertion 3: The largest and smallest exponents returned from std::frexp can be represented by the system's int type if the operation is reversible.

    You reverse std::frexp by its complement std::ldexp.

    Note that if int cannot hold a floating point's exponent, the result is not undefined (it's merely unspecified), so we can rely on the test being well formed.

    Write a unit test that tests if this is reversible

    TEST_CASE("The largest exponent returned by std::frexp can be represented by int") {
        long double largest_ld = std::numeric_limits<long double>::max();
    
        int exponent;
        long double fraction = std::frexp(largest_ld, &exponent);
    
        errno = 0;
        REQUIRE(std::ldexp(fraction, exponent) == largest_ld);
        REQUIRE(errno == 0); // just in case?
    }
    
    TEST_CASE("The smallest exponent returned by std::frexp can be represented by int") {
        long double smallest_ld = std::numeric_limits<long double>::min();
    
        int exponent;
        long double fraction = std::frexp(smallest_ld, &exponent);
    
        errno = 0;
        REQUIRE(std::ldexp(fraction, exponent) == smallest_ld);
        REQUIRE(errno == 0); // just in case?
    }
    

    Can you do better with compile time checks?

    Probably, but I'm having trouble understanding the same part of the language you were having trouble with, so I'm optimizing developer speed over compile time checks.

    As a first order approximation, may I suggest:

    static_assert(sizeof(int) >= 32);
    static_assert(std::numeric_limits<long double>::is_eec559);
    

    And of course, revisit when you hit a platform where this isn't true.