Home > database >  How do I perform a narrowing conversion from double to float safely?
How do I perform a narrowing conversion from double to float safely?

Time:11-16

I am getting some -Wnarrowing conversion errors when doubles are narrowed to floats. How can I do this in a well defined way? Preferably with an option in a template I can toggle to switch behavior from throwing exceptions, to clamping to the nearest value, or to simple truncation. I was looking at the gsl::narrow cast, but it seems that it just performs a static cast under the hood and a comparison follow up: Understanding gsl::narrow implementation. I would like something that is more robust, as according to What are all the common undefined behaviours that a C programmer should know about? static_cast<> is UB if the value is unpresentable in the target type. I also really liked this implementation, but it also relies on a static_cast<>: Can a static_cast<float> from double, assigned to double be optimized away? I do not want to use boost for this. Are there any other options? It's best if this works in c 03, but c 0x(experimental c 11) is also acceptable... or 11 if really needed...

Because someone asked, here's a simple toy example:

#include <iostream>

float doubleToFloat(double num) {
    return static_cast<float>(num);
}

int main( int, char**){
    double source = 1; // assume 1 could be any valid double value
    try{
        float dest = doubleToFloat(source);
        std::cout << "Source: (" << source << ") Dest: (" << dest << ")" << std::endl;
    }
    catch( std::exception& e )
    {
        std::cout << "Got exception error: " << e.what() << std::endl;
    }
}

My primary interest is in adding error handling and safety to doubleToFloat(...), with various custom exceptions if needed.

CodePudding user response:

As long as your floating-point types can store infinities (which is extremely likely), there is no possible undefined behavior. You can test std::numeric_limits<float>::has_infinity if you really want to be sure.

Use static_cast to silence the warning, and if you want to check for an overflow, you can do something like this:

template <typename T>
bool isInfinity(T f) {
    return f == std::numeric_limits<T>::infinity()
        || f == -std::numeric_limits<T>::infinity();
}

float doubleToFloat(double num) {
    float result = static_cast<float>(num);
    if (isInfinity(result) && !isInfinity(num)) {
        // overflow happened
    }
    return result;
}

Any double value that doesn't overflow will be converted either exactly or to one of the two nearest float values (probably the nearest). You can explicitly set the rounding direction with std::fesetround.

CodePudding user response:

It depends on what you mean by "safely". There will most likely be a drop of precision in most cases. Do you want detect if this happens? Assert, or just know about it and notify the user?

A possible path of solution would be to static casting the double to a float, and then back to double, and compare the before and after. Equality is unlikely, but you could assert that the loss of precision is within your tolerance.

float doubleToFloat(double a_in, bool& ar_withinSpec, double a_tolerance) 
{
    auto reducedPrecision = static_cast<float>(a_in);
    auto roundTrip = static_cast<double>(reducedPrecision);
    ar_withinSpec = (roundTrip < a_tolerance);
    return reducedPrecision;
}
  • Related