Search code examples
cdouble

floor(x) does not equal x where x is a double


I have a simple code to check if an INTEGER n is a power of 2

bool isPowerOfTwo(int n)
{
    double x = log(n)/log(2);
    return floor(x) == x ;
}

Most of the test cases are fine until n = 536870912 = 2^29.

In this case, the function return FALSE (which is not correct). I used printf to print floor(x) and x, both of them give 29.000000. But still, it returns FALSE. I hexdump x and floor(x)

x:

01 00 00 00 00 00 3D 40

floor(x):

00 00 00 00 00 00 3D 40

Why would floor(x) change a certain bit of x when x is a round number (29)? How to fix this problem ? Thanks in advance.

FYI:

  • gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
  • Target: x86_64-linux-gnu

EDIT: interestingly, if log() is replaced with log10(), it works fine.


Solution

  • Why would floor(x) change a certain bit of x when x is a round number (29)?

    Because double you are using has no precision to store the result of calculations exact, so it rounds, and because the result of calculation is away from a whole number floor rounds to the whole number. On your platform double is Double precision IEEE 745.

    calculation real result rounded to double
    log(536870912) 20.10126823623841397309 20.10126823623841474387
    log(2) .69314718055994530941 .69314718055994528623
    20.10126823623841474387/.69314718055994528623 29.00000000000000208209 29.00000000000000355271
    (double)log(536870912)/(double)log(2) 29.00000000000000000028 29.00000000000000355271

    The result of calculation of ln(536870912)/ln(2) is 29.00000000000000355271 (i.e. 0x1.d000000000001p+4). Floor then truncates the 1 and gives exact 29.

    Note how the result of (double)log(536870912)/(double)log(2) is closer to 29.00000000000000355271 then to 29. You can play around with https://www.binaryconvert.com/convert_double.html# and https://www.h-schmidt.net/FloatConverter/IEEE754.html to learn floats better.

    How to fix this problem ?

    The usuall solutions: increase precision by using greater datatype (i.e. use long double) or use an arbitrary-precision arithmetic library. In this case, do not use floating point numbers at all, stick to integers.