代码之家  ›  专栏  ›  技术社区  ›  sigjuice

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

  •  21
  • sigjuice  · 技术社区  · 15 年前

    我刚开始读 Hacker's Delight 它定义了abs(-2 三十一 )砷-2 三十一 .为什么?

    我试过 printf("%x", abs(0x80000000)) 在几个不同的系统上,我在所有系统上都得到了0x8000000。

    9 回复  |  直到 11 年前
        1
  •  14
  •   tanascius    15 年前

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

        2
  •  42
  •   James McNellis    15 年前

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

    这个 abs , labs llabs 函数计算整数的绝对值 j .如果无法表示结果,则行为未定义。

    及其脚注:

    最大负数的绝对值不能用两个补数表示。

        3
  •  10
  •   John Gietzen    15 年前

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

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

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

    Math.Abs((long)int.MinValue) == (long)int.MaxValue + 1
    
        4
  •  8
  •   Alok Singhal    11 年前

    显然,数学上,−2 三十一 是2 三十一 . 如果我们有32位来表示整数,我们最多可以表示2 三十二 数字。如果我们想要一个对称于0的表示,我们需要做一些决定。

    在下面的问题中,我假设32位宽的数字。必须至少为0使用一个位模式。所以我们只剩下2个 三十二 -其余数字的位模式为1或更少。这个数字是奇数,所以我们可以用一个不完全对称于零的表示,或者用两个不同的表示来表示一个数字。

    • 如果我们使用 符号震级 表示法,最高有效位表示数字的符号,其余位表示数字的大小。在这个方案中, 0x80000000 为“负零”(即零),且 0x00000000 是“正零”或规则零。在这个方案中,最大的正数是 0x7fffffff (2147483647)最负数是 0xffffffff (−2147483647)。该方案具有易于“解码”和对称性的优点。这个方案在计算上有缺点 a + b 什么时候 a b 不同的标志是一种特殊情况,必须经过特殊处理。
    • 如果我们使用 一的补语 表示,最重要的位仍然表示符号。正数的位为0,其余的位组成数字的大小。对于负数,您只需从对应的正数表示形式中反转位(用一个长的一系列的补码,因此命名为 一的补语 )。在这个方案中,最大正数仍然是 0x7FFFFFFF (2147483647),最大负数为 0x800万 (−2147483647)。还有两种0的表示形式:正零是 0x00亿 负零是 0xFFFFFFFF . 该方案还存在涉及负数的计算问题。
    • 如果我们使用 二的补码 方案中,取其补码表示并加上 1 对它。在这个方案中,只有一个0,即 0x00亿 . 最大的正数是 0x7FFFFFFF (2147483647)最负数是 0x800万 (−2147483648)。这种表现形式是不对称的。这个方案的优点是不需要处理负数的特殊情况。只要结果不溢出,表示就会为您提供正确的答案。因此,大多数当前硬件都表示此表示中的整数。

    在二的补码表示中,不能表示二 三十一 . 实际上,如果您查看编译器的 limits.h 或等效文件,您可能会看到 INT_MIN 以这种方式:

    #define INT_MIN (-2147483647 - 1)
    

    这样做而不是

    #define INT_MIN -2147483648
    

    因为2147483648太大,无法放入 int 在32位二进制补码表示中。当一元减号运算符“获取”要操作的数字时,已经太迟了:溢出已经发生,您无法修复它。

    所以,为了回答你最初的问题,在一个二的补码表示中,最大负数的绝对值不能用这个编码表示。另外,从上面,要在二的补码表示中从一个负值得到一个正值,您需要取其一的补码,然后加1。所以,为了 0x800万 :

    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
    

    你把原来的号码拿回来。

        5
  •  3
  •   Sparky    15 年前

    这又回到了数字的存储方式。

    负数是用二的补码来存储的。算法就像…

    翻转所有位,然后加1。

    使用8位数字作为例子…

    +0=0

    00000000->11111111、11111111+1=10000000

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

    还有…

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

    10000000->0111111、0111111+1=10000000

    希望这有帮助。

        6
  •  3
  •   Jonathan Leffler    15 年前

    两个补数的表示法中,最重要的一位是负数。0x8000000是1后跟31个零,前1代表-2^31而不是2^31。因此,无法表示2^31,因为最大的正数是0x7fffffff,它是0,后面是31,等于2^31-1。

    因此,abs(0x8000000)在二者的补码中是未定义的,因为它太大了,因此机器会放弃并再次给予你0x8000000。通常至少。

        7
  •  1
  •   codaddict    15 年前

    我想办法 abs 工作首先要检查 sign bit 数字的。如果清除,则不执行任何操作,因为数字已经 +ve 否则返回 2's complement 数字的。在你的情况下,号码是 -ve 我们需要找到它 2补码 . 但是2的补充 0x80000000 恰好是 0x800万 本身。

        8
  •  1
  •   josefx    15 年前

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

    • 先得到正值 对变量中的所有位求反, 导致01111…
    • 然后加1, 这又导致了1000…这个 0x8000…我们开始

    现在这又是一个负数,我们不想要它,原因是溢出,请尝试数字0x9000…是10010…

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

    使用这个数字,溢出被右边的0位停止。

        9
  •  0
  •   sandun dhammika    15 年前

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

    在汇编语言编程的艺术书籍中,他们这样说。

    如果操作数为零,则其符号为 不改变,尽管这清除了 进位标志。否定任何其他价值 设置进位标志。否定字节 包含-128,一个包含 -32768或包含-2147483648的双字不会更改操作数,但会设置溢出 旗帜。neg总是更新a,s,p, 和Z标志,就好像你在使用 子指令

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