为什么 -INT_MIN = INT_MIN 在有符号的 2 补码表示中?

Why does -INT_MIN = INT_MIN in a signed, two's complement representation?

本文关键字:MIN 补码 表示 -INT INT 为什么 符号      更新时间:2023-10-16

我仍然没有找到最低有符号负数没有等效有符号正数的原因?我的意思是,为了简单起见,用3位数的二进制数100是-4?但我们不能用带符号的形式得到正4,因为我们不能。它溢出。那么我们如何知道二的补码1000是-4 1000 0000是-128等等?我们没有原始的正数

一种思考方法是,有符号的二次方补码格式的工作原理是为每个位分配二次方,然后翻转最后一次二次方的符号。例如,让我们看看-4,它表示为100。这意味着该值为

-1 x 2^2 + 0 x 2^1 + 0 x 2^0

如果我们想得到这个值的正版本,我们必须否定它才能得到

 1 x 2^2 - 0 x 2^1 - 0 x 2^0

请注意,该值等于

 1 x 2^2 + 0 x 2^1 + 0 x 2^0

换句话说,这个值的正常二进制表示是100。然而,我们在这里遇到了麻烦,因为我们使用的是有符号二的补码表示,这意味着我们专门保留了4的位作为符号位。因此,当我们试图将比特模式100解释为有符号的三比特二的补码值时,它的结果与我们开始时的结果相同。钻头的短缺是这里的问题。

更一般地说,给定n个比特,其中第一个是2的补码表示中的符号比特,尝试计算-1000…00将返回相同的值,因为存储大正值所需的比特具有赋予它的特殊含义

那为什么要这么做呢?原因是如果你只有n个比特,你就不能存储值-2n-1到2n-1.因为这里有2n+1个不同的数字,只有2^n个不同的比特模式。因此,排除最大的正数使得可以将所有不同的数字保持在指定的比特模式中。

但为什么要降低高值而不是低值呢?这是为了保持二进制与无符号整数的兼容性。在无符号整数中,值0到2n-1-1都使用标准的基二表示进行编码。因此,为了使无符号整数和有符号整数完全一致,无符号整数被设计为与前2个n-1无符号整数逐位等效,这些无符号整数的范围从0到2个n-1-1,包括0。之后,无符号值需要最高有效位来编码数字,但有符号值将其用作符号位。

希望这能有所帮助!

-INT_MIN是一个整数溢出,在C中是未定义的行为。

只有当有符号整数溢出包装时,才保证-INT_MIN等于INT_MIN。这可以通过gcc启用,例如通过-fwrapv选项启用。

编译器通常利用整数溢出是C中未定义的行为这一事实来执行一些优化。依赖有符号整数溢出,换行是不安全的。

编译器优化的一个众所周知的例子是下面的

#define ABS(x) ((x) > 0 ? (x) : -(x)) 
void foo(int x){  
    if (ABS(x) >= 0) {
        // some code
    }
}

现在大多数启用了优化选项的编译器(gccicc)都会根据-INT_MIN是未定义行为这一事实来优化测试。

A。n位二进制数有偶数种可能性,所以我们不能表示正数和负数的相同范围。

B。我们希望每个以1开头的数字都是负数,每个以0开头的数字也都不是负数。(不是相反,因为我们希望在有符号和无符号中对正和零进行相同的表示。正因为如此,0在正的一半,所以它们少了一个位置。

二的补码的替代项有这样一个性质,它被称为一的补码
在补码形式中,最低可能值也有一个有效的正形式


补码的工作原理是简单地反转数字本身的所有位
例如,我们知道0110 == 6和一个人的补码1001 == -6。使用补码,我们得到的正数和负数一样多。

但是比特表示1111呢?通过观察它,我们可以看出它是零(0000 = 0; 1111 = -0)的"负"形式,但这样的数字没有任何意义,而且有点浪费。

相反,我们使用二的补码,这与一的补码类似,但在反转位后,我们加一。如果我们知道0110 = 6,那么一方的补码是1001,二方的补号是1001 + 1 == 1010。使用二的补码,我们没有"负零",因为它会导致溢出。

另一种看待它的方式是"如果设置了最高位,那么数字就是负数"。这意味着正的范围是[0 .. 2^(bits - 1)],负的范围是其他的一切。正数的数量与负数的数量相同,但因为(在这种格式中)零被认为是正数,所以负数的范围被移一到[-1 .. (neg) 2^(bits - 1)]


让我们假设我们处理的是一个3位有符号的数字,在2的补码中。这会给我们下表:

BITS  VALUE
000       0
001       1
010       2
011       3
100      -4
101      -3
110      -2
111      -1

你可以看到,正数和负数的数量是一样的,只是负数不像正数那样从0开始。

缺少的数字是0。从数学意义上讲,0既不是正的也不是负的。但在二进制意义上,由于0没有负比特,所以它被认为是正的。换句话说,如果你想要-128到128,就不可能有0。

因为你必须计数0。整数范围[-4,-1](或等效为-4,-3,-2和-1)包含4个数字,范围[0,3](或等价为0,1,2和3)的其余部分包含4个数,总共8个,3位二进制数具有3(=8)个可能组合的2次方。

这样想吧。任何形式为[-n,+n]的整数范围都必须具有奇数大小(2*n+1个整数)。无论你使用什么整数二进制表示,都会有不同数量的负数和正数,因为组合的数量总是偶数(2的幂)。

那么我们怎么知道二的补码1000是-4 1000 0000是-128呢等等我们没有原始的正数

你的错误是认为我们需要正数的二补表示来计算负数的二补表达。

求负数的二补码的过程是:

从要表示的数字的绝对值的正规非二补表示开始。所以对于-4,取|-4|100的非二补码表示。

翻转所有位:100->011(或…11111011,其中一位无限期地向左继续)。

添加一个:011->100(或…11111100)

现在截断为您正在使用的位数(这消除了进位位或1的无限字符串)。因此,100是-4的3位2的补码表示。

换一种方式,取二的补码表示(100)翻转位(011)并加一(100),现在得到的是|-4|的非二补码表示。1*2^2+0*2^1+0*2^0=4。因此,我们知道,我们从100开始的表示是-4的3位2的补码表示。

这个答案只是一个总结。

在N位2的补码中:

  • 负范围是[-2^{N-1},-1],其基数为2^{N}/2
  • 正范围为[0,2^{N-1}-1]其再次具有基数2^{N}/2

以及整个范围[-2^{N-1},2^{N-1}-1]必须具有基数2^{N}。对超出此范围的任何数字执行N位操作都会导致溢出。

请注意,当这个有符号范围内的所有数字加上偏置2^{N-1}时,我们得到了一个无符号范围[0,2^{N-1}]。

Two的补码通过将最高位保留为负数来表示负数。这意味着你不能再使用最高的比特作为正数了。

所有其他(较低)位都是正的,但无论你如何将它们相加,总和永远不会达到最高位,因为它被视为负数。

我们应该知道如何制作x到-x:

  1. 翻转x中的所有位。像5是0101,我们在这一步中得到1010
  2. 在我们上一步得到的基础上加1。这次我们得到了1010 + 1 = 1011

在实际机器中,负数总是以2的补码格式显示,因此1011表示-5(即-8 + 2 + 1 = -5)。

现在回到问题上来,真实机器中的INT_MIN是具有31个连续01

所以在第一步之后,你会得到一个数字,它是0,有31个连续的1,在C语言中是INT_MAX

在第二步中,将1与我们从上一步得到的相加,结果是1,其中有31个连续的0,也就是INT_MIN

因此INT_MIN = -INT_MIN