为什么最小可能整数的否定会产生自身?

Why does the negation of the minimum possible integer yield itself?

所以我写了一个测试下溢和上溢的小实验,使用 c 和 64 位机器。对于 int 类型,min/max 值是:

   int tmax = 2147483647;
   int tmin = -2147483648;

我知道补码是如何工作的,这不是我的问题。

所以我想,如果我做一些负 tmin 会发生什么?即:

   int tmin = -2147483648;
   int negativeTmin = -tmin;

结果还是tmin。 (即负Tmin为-2147483648)

我的问题是为什么会这样? 因为正数 2,147,483,648 不能用 int 表示,我当然理解为什么不是这样,但它不是这样似乎很奇怪完全改变,因为这使它成为唯一在应用 - 时不会改变的非零整数。我并不是说我对它应该是什么有了更好的了解,我只是好奇为什么 -tmin == tmin。它是否与按位运算有关,或者在计算机中如何进行减法运算,或者它默认这样做是因为我试图做的事情是未定义的,还是其他什么?

我的代码:

#include <stdio.h>
int main() {
   int tmax = 2147483647;
   printf("tmax Before: %d\n", tmax);
   tmax++;
   printf("tmax After: %d\n\n", tmax);

   int tmin = -2147483648;
   printf("tmin Before: %d\n", tmin);
   tmin--;
   printf("tmin After: %d\n\n", tmin);

   int tmin2 = -2147483648;
   int negativeTmin = -tmin2;
   printf("negative tmin: %d\n\n", negativeTmin);

   return 0;
}

输出:

tmax Before: 2147483647 tmax After: -2147483648

tmin Before: -2147483648 tmin After: 2147483647

negative tmin: -2147483648

其实我相信我刚刚意识到答案;正如我们所知,2147483648 不能用 int 表示,-2147483648 也是如此,所以当你制作 -(-2147483648) 时,它是 2147483648,但不能像上面描述的那样表示为 -2147483648。因此为什么 -tmin == tmin.

您的代码 int tmin2 = -2147483648; int negativeTmin = -tmin2 由于整数溢出引入了未定义的行为,因此它可能会产生 任何 结果。因此,考虑任何规则为什么会发生这种情况以及它是否与二进制补码有关是没有意义的,而且实际上是错误的。

积分溢出是未定义行为的示例,因为它在标准的“未定义行为”定义中作为示例提到(3.4.3 - undefined behavior):

1 undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

3 EXAMPLE An example of undefined behavior is the behavior on integer overflow.

正如其他人在此处发布的那样,从技术上讲,您正在做的事情会导致未定义的行为,因为 C 中有符号整数的上溢或下溢会导致未定义的行为。

另一方面,在大多数 Intel 系统上,整数上溢或下溢只是环绕整数值并设置一些处理器标志,以便以后的指令可以检测到溢出。在这些系统上,有理由问 - 为什么 do 你在计算 -Tmin[=50= 时得到 Tmin ]?

在带符号的二进制补码系统中,值得注意的是表达式 -x 等价于 ~x + 1。假设您有 Tmin,它看起来像这样:

10000000 00000000 00000000 00000000

如果你计算 ~Tmin,你会得到

01111111 11111111 11111111 11111111

这正好是Tmax。如果你加一个,你会得到一个巨大的纹波进位,一直传播到最后,产生

10000000 00000000 00000000 00000000

这就是我们的起点。所以这就是为什么你可能会看到 Tmin 回来了。

另一种理解方式:您知道有符号 32 位整数的 Tmin 是 -231。 -Tmin 的值应满足 Tmin + -Tmin = 0 (mod232)。那么 [-231, 231 - 1] 范围内的哪个值恰好有这个 属性?是-231,这就是为什么Tmin = -Tmin.

所以您问题的最佳答案可能是 "technically what you're doing is undefined behavior, but on a reasonable Intel system and a compiler that isn't set to do aggressive optimizations, it comes down to the mechanics of how signed 32-bit integer arithmetic works and how negation is defined."

-X是X的2s补码
这就是硬件对否定所做的 https://c9x.me/x86/html/file_module_x86_id_216.html

INT_MIN = -2147483648 = 0x80000000
-2147483648 的 2 的补码 = 0x80000000

您可以将 2 的补码计算为翻转位并加 1
参见 https://en.wikipedia.org/wiki/Two%27s_complement
翻转 0x80000000 的位给出 0x7fffffff。
0x7fffffff + 1 = 0x80000000 = -2147483648

(gdb) p /x (int)(-2147483648)
$14 = 0x80000000
(gdb) p /x (int)-(-2147483648)
$15 = 0x80000000
(gdb) p /x ~(0x80000000)
$16 = 0x7fffffff
(gdb) p /x ~(0x80000000) + 1
$17 = 0x80000000

另一种思考方式。

int 类型的数据用 32 位表示。取tmin = -2147483648,那么,当然是-tmin = 2147483648。在二进制补码运算中,tmin的二进制表示是

10000000 00000000 00000000 00000000

并且,对于 -tmin

0 10000000 00000000 00000000 00000000

但只允许使用 32 位,因此截断消除了最高有效位(在本例中为第一个零),我们得到

10000000 00000000 00000000 00000000

这是 tmin