Search code examples
variablesfloating-pointinequality

Comparing IEEE floats and doubles for equality


What is the best method for comparing IEEE floats and doubles for equality? I have heard of several methods, but I wanted to see what the community thought.


Solution

  • The best approach I think is to compare ULPs.

    bool is_nan(float f)
    {
        return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
    }
    
    bool is_finite(float f)
    {
        return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
    }
    
    // if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
    // if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
    #define UNEQUAL_NANS 1
    // if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
    // if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
    #define INFINITE_INFINITIES 1
    
    // test whether two IEEE floats are within a specified number of representable values of each other
    // This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
    bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
    {
    #ifdef UNEQUAL_NANS
        if(is_nan(lhs) || is_nan(rhs))
        {
            return false;
        }
    #endif
    #ifdef INFINITE_INFINITIES
        if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
        {
            return false;
        }
    #endif
        signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
        // transform signed magnitude ints into 2s complement signed ints
        if(left < 0)
        {
            left = 0x80000000 - left;
        }
        signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
        // transform signed magnitude ints into 2s complement signed ints
        if(right < 0)
        {
            right = 0x80000000 - right;
        }
        if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
        {
            return true;
        }
        return false;
    }
    

    A similar technique can be used for doubles. The trick is to convert the floats so that they're ordered (as if integers) and then just see how different they are.

    I have no idea why this damn thing is screwing up my underscores. Edit: Oh, perhaps that is just an artefact of the preview. That's OK then.