令我惊讶的是,在 C 中将最小的负 long long 值转换为 long double 会将其符号从负变为正,因此我编写了一个测试程序。
我正在 Linux amd64 上编译并运行这个 C 程序,链接到 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;
}
我得到这个输出:
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
令人惊讶的输出行是那些不以
-
开头的输出行。在输出中,我预计所有数字均为负数。尤其是:
对于
(double)(-0x8000000000000000ll)
,即使(double)(long long)(-0x8000000000000000ll)
给出负输出(正确),我也会惊讶地得到正输出。 这里有什么区别?
对于
(long double)(-0x8000000000000000ll)
,即使(long double)(long long)(-0x8000000000000000ll)
(正确地)给出了负输出,我也意外地得到了正输出。
请注意,这不是 glibc printf 在 double 或 long double 上行为不当的问题,输出的
is_negative=0
部分证明了这一点。
对于 Linux amd64 上的 GCC,
double
类型是 64 位 IEEE 浮点类型,其中 52 位用于有效数字,11 位用于指数,1 位用于符号; long double
类型是 80 位 x86 扩展精度 浮点类型,其中 64 位用于有效数(1 位整数,63 位小数),15 位用于指数,1 位用于符号。
整数值 0x8000000000000000 可以在
double
和 long double
中准确表示,因为它是 2 的小幂。有效位数为 1,指数为 63。
我使用多个版本的 GCC 和 Clang 得到相同的结果,无论是使用
-m32
(i386) 还是 -m64
(amd64)。
我想我知道答案:
0x8000000000000000ll
的类型是unsigned long long
(!)。-0x8000000000000000ll
的类型仍然是unsigned long long
,其值实际上是0x8000000000000000(正数)。(double)-0x8000000000000000ll
中,正数会转换为 double
。