Best way of checking if a floating point is an integer

后端 未结 12 1966
陌清茗
陌清茗 2020-12-25 13:07

[There are a few questions on this but none of the answers are particularly definitive and several are out of date with the current C++ standard].

My research shows

12条回答
  •  挽巷
    挽巷 (楼主)
    2020-12-25 13:45

    I'd go deep into the IEE 754 standard and keep thinking only in terms of this type and I'll be assuming 64 bit integers and doubles.

    The number is a whole number iff:

    1. the number is zero (regardless on the sign).
    2. the number has mantisa not going to binary fractions (regardless on the sing), while not having any undefined digits for least significant bits.

    I made following function:

    #include 
    
    int IsThisDoubleAnInt(double number)
    {
        long long ieee754 = *(long long *)&number;
        long long sign = ieee754 >> 63;
        long long exp = ((ieee754 >> 52) & 0x7FFLL);
        long long mantissa = ieee754 & 0xFFFFFFFFFFFFFLL;
        long long e = exp - 1023;
        long long decimalmask = (1LL << (e + 52));
        if (decimalmask) decimalmask -= 1;
        if (((exp == 0) && (mantissa != 0)) || (e > 52) || (e < 0) || ((mantissa & decimalmask) != 0))
        {
            return 0;
        }
        else
        {
            return 1;
        }
    }
    

    As a test of this function:

    int main()
    {
        double x = 1;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 1.5;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 2;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 2.000000001;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 1e60;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 1e-60;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 1.0/0.0;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = x/x;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 0.99;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = 1LL << 52;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
        x = (1LL << 52) + 1;
        printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
    }
    

    The result is following:

    x = 1.000000e+00 is int.
    x = 1.500000e+00 is not int.
    x = 2.000000e+00 is int.
    x = 2.000000e+00 is not int.
    x = 1.000000e+60 is not int.
    x = 1.000000e-60 is not int.
    x = inf is not int.
    x = nan is not int.
    x = 9.900000e-01 is not int.
    x = 4.503600e+15 is int.
    x = 4.503600e+15 is not int.
    

    The condition in the method is not very clear, thus I'm posting the less obfuscated version with commented if/else structure.

    int IsThisDoubleAnIntWithExplanation(double number)
    {
        long long ieee754 = *(long long *)&number;
        long long sign = ieee754 >> 63;
        long long exp = ((ieee754 >> 52) & 0x7FFLL);
        long long mantissa = ieee754 & 0xFFFFFFFFFFFFFLL;
        if (exp == 0)
        {
            if (mantissa == 0)
            {
                // This is signed zero.
                return 1;
            }
            else
            {
                // this is a subnormal number
                return 0;
            }
        }
        else if (exp == 0x7FFL)
        {
            // it is infinity or nan.
            return 0;
        }
        else
        {
            long long e = exp - 1023;
            long long decimalmask = (1LL << (e + 52));
            if (decimalmask) decimalmask -= 1;
            printf("%f: %llx (%lld %lld %llx) %llx\n", number, ieee754, sign, e, mantissa, decimalmask);
            // number is something in form (-1)^sign x 2^exp-1023 x 1.mantissa
            if (e > 63)
            {
                // number too large to fit into integer
                return 0;
            }
            else if (e > 52)
            {
                // number too large to have all digits...
                return 0;
            }
            else if (e < 0)
            {
                // number too large to have all digits...
                return 0;
            }
            else if ((mantissa & decimalmask) != 0)
            {
                // number has nonzero fraction part.
                return 0;
            }
        }
        return 1;
    }
    

提交回复
热议问题