将最小的负 long long 值转换为 double 会改变其符号

问题描述 投票:0回答:1

令我惊讶的是,在 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 printfdoublelong 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)。

c floating-point long-long
1个回答
0
投票

我想我知道答案:

  • 0x8000000000000000ll
    的类型是
    unsigned long long
    (!)。
  • -0x8000000000000000ll
    的类型仍然是
    unsigned long long
    ,其值实际上是0x8000000000000000(正数)。
  • (double)-0x8000000000000000ll
    中,正数会转换为
    double
© www.soinside.com 2019 - 2024. All rights reserved.