为什么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:
abs
,labs
和llabs
函数计算整数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)。 该方案的优点是我们很容易“解码”,并且它是对称的。 该方案的缺点在于,当a
和b
具有不同符号时计算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所以它会设置溢出标志并静默。这就是原因。