I was surprised that converting the smallest negative long long value to double in C changes its sign from negative to positive, so I made a test program.
I'm compiling and running this C program on Linux amd64, linking against glibc:
#include <stdio.h>
int main(int argc, char **argv) {
(void)argc; (void)argv;
printf("sizeof(long long)=%d\n", (int)sizeof(long long));
printf("% .21g is_negative=%d\n", (double)-0x8000000000000000ll, (double)-0x8000000000000000ll < 0.0l);
printf("% .21g is_negative=%d\n", (double)(-0x8000000000000000ll), (double)(-0x8000000000000000ll) < 0.0l);
printf("% .21g is_negative=%d\n", (double)(-0x4000000000000000ll * 2), (double)(-0x4000000000000000ll * 2) < 0.0l);
printf("% .21g\n", (double)(long long)(-0x8000000000000000ll));
printf("% .21g\n", (double)(-1ll << 63)); /* But this is undefined behavior: warning: shifting a negative signed value is undefined [-Wshift-negative-value]. */
printf("% .21g\n", -(double)0x8000000000000000ll);
printf("% .21LgL is_negative=%d\n", (long double)-0x8000000000000000ll, (long double)-0x8000000000000000ll < 0.0l); /* Surprise: positive! */
printf("% .21LgL is_negative=%d\n", (long double)(-0x8000000000000000ll), (long double)(-0x8000000000000000ll) < 0.0l);
printf("% .21LgL is_negative=%d\n", (long double)(-0x4000000000000000ll * 2), (long double)(-0x4000000000000000ll * 2) < 0.0l);
printf("% .21LgL\n", (long double)(long long)(-0x8000000000000000ll));
printf("% .21LgL\n", (long double)(-1ll << 63)); /* But this is undefined behavior: warning: shifting a negative signed value is undefined [-Wshift-negative-value]. */
printf("% .21LgL\n", -(long double)0x8000000000000000ll);
printf("% lldll\n", -1ll << 63);
printf("% lldll\n", -0x4000000000000000ll * 2);
printf("% lldll\n", 0x8000000000000000ll);
printf("% lldll\n", -0x8000000000000000ll);
return 0;
}
I get this output:
sizeof(long long)=8
9223372036854775808 is_negative=0
9223372036854775808 is_negative=0
-9223372036854775808 is_negative=1
-9223372036854775808
-9223372036854775808
-9223372036854775808
9223372036854775808L is_negative=0
9223372036854775808L is_negative=0
-9223372036854775808L is_negative=1
-9223372036854775808L
-9223372036854775808L
-9223372036854775808L
-9223372036854775808ll
-9223372036854775808ll
-9223372036854775808ll
-9223372036854775808ll
The suprising output lines are those not starting with -. On the output I'm expecting all numbers to be negative. Especially that:
For
(double)(-0x8000000000000000ll)I'm getting a positive output by surprise, even if(double)(long long)(-0x8000000000000000ll)gives a negative output (correctly). What's the difference in here?For
(long double)(-0x8000000000000000ll)I'm getting a positive output by surprise, even if(long double)(long long)(-0x8000000000000000ll)gives a negative output (correctly).
Please note that this is not an issue with glibc printf misbehaving on double or long double, that's proven by by the is_negative=0 part of the output.
With GCC on Linux amd64, the double type is the 64-bit IEEE floating point type, with 52 bits for the significand fraction, 11 bits for the exponent and 1 bit for the sign; the long double type is the 80-bit x86 extended precision floating point type, with 64 bits for the significand (1 bit integer, 63 bit fraction), 15 bits for the exponent and 1 bit for the sign.
The integer value 0x8000000000000000 can be represented accurately in both double and long double, because it's a small power of 2. The significand will be 1, the exponent will be 63.
I get the same results with multiple versions of GCC and Clang, both with -m32 (i386) and -m64 (amd64).
I think I know the answer:
0x8000000000000000llisunsigned long long(!).-0x8000000000000000llremainsunsigned long long, and the value is actually 0x8000000000000000 (positive).(double)-0x8000000000000000ll, a positive number gets converted to adouble.Sometimes (but not always, even with
-W -Wall) Clang reports a warning (and GCC reports a similar one): comparison of integers of different signs: 'long long' and 'unsigned long long' [-Wsign-compare]As a proof, the following program prints all 1s: