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

我刚刚开始阅读Hacker’s Delight ,它将abs(-2 31 )定义为-2 31 。 这是为什么?

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

对于32位数据类型,没有+ 2 ^ 31的表达式,因为最大数字是2 ^ 31-1 …阅读更多关于这两个补码 …

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

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

及其脚注:

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

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

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

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

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

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

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

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

在二进制补码表示中,没有办法表示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 

你得到原来的号码。

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

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

翻转所有位,然后加1。

使用八位数作为例子……

+0 = -0

00000000 – > 11111111,111111111 + 1 = 100000000

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

和…

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

10000000 – > 01111111,01111111 + 1 = 10000000

希望这可以帮助。

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

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

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

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

  • 为了获得正值,它首先否定变量中的所有位,导致01111 ……
  • 然后加1,再次导致1000 …… 0x8000 ……我们开始了

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

  • 否定位导致01101 …在01110中添加一个结果…
  • 这是0xE000 ……一个正数

使用此数字,溢出将由右侧的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所以它会设置溢出标志并静默。这就是原因。