为什么abs(0x80000000)== 0x80000000?

时间:2010-03-29 15:34:33

标签: c integer bit-manipulation 32-bit

我刚刚开始阅读Hacker's Delight并将abs(-2 31 )定义为-2 31 。那是为什么?

我在几个不同的系统上尝试了printf("%x", abs(0x80000000)),并且我在所有系统上都返回了0x80000000。

9 个答案:

答案 0 :(得分:42)

实际上,在C中,行为是未定义的。根据C99标准,§7.20.6.1/ 2:

  

abslabsllabs函数计算整数j的绝对值。如果无法表示结果,则行为未定义。

及其脚注:

  

最负数的绝对值不能用二进制补码表示。

答案 1 :(得分:14)

对于32位数据类型,没有+ 2 ^ 31的表达式,因为最大数字是2 ^ 31-1 ...详细了解two's complement ...

答案 2 :(得分:10)

因为整数作为二进制补码二进制数存储在内存中,所以最小值的正数溢出回负值。

也就是说(在.NET中,但仍然适用):

int.MaxValue + 1 == int.MinValue  // Due to overflow.

Math.Abs((long)int.MinValue) == (long)int.MaxValue + 1

答案 3 :(得分:8)

显然,在数学上,| -2 31 |是2 31 。如果我们有32位来表示整数,我们最多可以表示2个 32 数。如果我们想要一个关于0的对称表示,我们做出一些决定。

对于以下内容,如您的问题,我假设32位宽数字。必须使用至少一个位模式为0.因此,对于其余数字,我们留下2 32 -1或更少位模式。这个数字是奇数,所以我们可以有一个关于零不完全对称的表示,或者有一个数字用两个不同的表示来表示。

  • 如果我们使用 sign-magnitude 表示,则最高有效位表示数字的符号,其余位表示数字的大小。在该方案中,0x80000000是“负零”(即,零),并且0x00000000是“正零”或常规零。在此方案中,最正数为0x7fffffff(2147483647),最负数为0xffffffff(-2147483647)。该方案的优点是我们很容易“解码”,并且它是对称的。该方案的缺点在于,当a + ba具有不同符号时计算b是一种特殊情况,必须特别处理。
  • 如果我们使用 1'补码表示,最重要的位仍代表符号。正数将该位设为0,其余位构成数字的大小。对于负数,您只需将相应正数表示的位反转(使用长序列的一个补码 - 因此名称​​ 1'补充)。在此方案中,最大正数仍为0x7fffffff(2147483647),最大负数为0x80000000( - 2147483647)。还有两个0的表示:正零是0x00000000,负零是0xffffffff。该方案还存在涉及负数的计算问题。
  • 如果我们使用二进制方案,则通过采用补码表示并向其添加1来获得负数。在该方案中,只有一个0,即0x00000000。最正数是0x7fffffff(2147483647),最负数是0x80000000( - 2147483648)。这种表现形式存在不对称性。这种方案的优点是人们不必处理负数的特殊情况。只要结果不溢出,表示就会给你正确的答案。因此,大多数当前硬件都表示此表示中的整数。

在二进制补码表示中,无法表示2 31 。实际上,如果您查看编译器的limits.h或等效文件,您可能会以这种方式看到INT_MIN的定义:

#define INT_MIN (-2147483647 - 1)

这样做而不是

#define INT_MIN -2147483648

因为2147483648太大而不适合32位二进制补码表示中的int。当一元减号运算符“获取”要操作的数字时,为时已晚:溢出已经发生,你无法解决它。

因此,为了回答您的原始问题,二进制补码表示中最负数的绝对值不能用该编码表示。另外,从上面的两个补码表示中得到负值到正值,你可以得到它的补码,然后加1.这样,0x80000000

1000 0000 0000 0000 0000 0000 0000 0000   original number
0111 1111 1111 1111 1111 1111 1111 1111   ones' complement
1000 0000 0000 0000 0000 0000 0000 0000   + 1

你得到原始号码。

答案 4 :(得分:3)

这可以追溯到数字的存储方式。

使用二进制补码存储负数。算法就像......

翻转所有位,然后加1。

使用八位数作为例子......

+0 = -0

00000000 - > 11111111,111111111 + 1 = 100000000

(但由于位的限制,这变为00000000)。

和...

-128 [aka - (2 ^ 7)]等于 - ( - 128)

10000000 - > 01111111,01111111 + 1 = 10000000

希望这有帮助。

答案 5 :(得分:3)

二进制补码的表示最高位为负数。 0x80000000是1后跟31个零,第一个1代表-2 ^ 31而不是2 ^ 31。因此,无法表示2 ^ 31,因为最高正数为0x7FFFFFFF,即0,后跟31个,等于2 ^ 31-1。

abs(0x80000000)因此在二进制补码中未定义,因为它太大,因此机器只是放弃并再次给你0x80000000。通常至少。

答案 6 :(得分:1)

我认为abs的工作方式是先检查号码的sign bit。如果数字已明确无效,则+ve否则返回该数字的2's complement。在您的情况下,该数字为-ve,我们需要找到其2's complement。但0x80000000的2的补码恰好是0x80000000本身。

答案 7 :(得分:1)

0x8000 ..存储为10000 ....(二进制)。这被称为二进制补码,这意味着最高位(左边的位)用于存储值的符号,负值用负二进制存储 - 1. abs()函数现在检查signbit,看到它被设置并计算正值。

  • 首先获得正值 否定变量中的所有位, 结果是01111 ...
  • 然后加1, 这再次导致了1000 ... 0x8000 ......我们从
  • 开始

现在这是我们不想要的负数,原因是溢出,尝试数字0x9000 ......这是10010 ......

  • 否定比特结果在01101 ... 在01110中添加一个结果......
  • 是0xE000 ...正数

使用此数字,右边的0位停止溢出

答案 8 :(得分:0)

因为它使用neg指令来执行此操作。

在汇编语言程序设计书中,他们就这样说过。

  

如果操作数为零,则其符号为   没有改变,虽然这清除了   携带国旗。否定任何其他价值   设置进位标志。否定一个字节   包含-128,包含一个单词   -32,768,或包含-2,147,483,648的双字不会更改操作数,但会设置溢出   旗。 Neg总是更新A,S,P,   和Z标志,就好像你正在使用   子指令

来源:http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_6/CH06-2.html#HEADING2-313 所以它会设置溢出标志并且是静默的。这就是原因。