【问题标题】:Why is abs(0x80000000) == 0x80000000?为什么 abs(0x80000000) == 0x80000000?
【发布时间】:2011-02-02 02:36:56
【问题描述】:

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

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

【问题讨论】:

  • +1 阅读 Hacker's Delight
  • @Paul 谢谢!我几乎没有完成第 1 章。
  • 读完这本书后,请访问网站以获取更多同样的好东西:hackersdelight.org

标签: c integer bit-manipulation 32-bit


【解决方案1】:

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

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

及其脚注:

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

【讨论】:

  • 绝对 +1 指出了整个事情的不确定性,而不是竭尽全力解释某个平台碰巧做了什么。
【解决方案2】:

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

【讨论】:

  • 谢谢。我知道了。但是,您的意思是说“没有 2^31 的表达式”吗?
  • @sigjuice:32 位数据类型的范围是 -2^31 到 2^31-1 ...所以,是的,没有 2^31 的表达式 - 它会导致溢出
【解决方案3】:

由于整数作为二进制补码存储在内存中,因此最小值的正版本溢出回负。

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

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

和

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

【讨论】:

    【解决方案4】:

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

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

    • 如果我们使用 sign-magnitude 表示,最高有效位表示数字的符号,其余位表示数字的大小。在此方案中,0x80000000 是“负零”(即零),0x00000000 是“正零”或常规零。在这个方案中,最大的正数是0x7fffffff(2147483647),最大的负数是0xffffffff(-2147483647)。这种方案的优点是我们很容易“解码”,而且是对称的。该方案的缺点是当a和b的符号不同时计算a + b是一种特殊情况,需要特殊处理。
    • 如果我们使用 ones' 补码 表示,最高有效位仍表示符号。正数的该位为 0,其余位构成数字的大小。对于负数,您只需将相应正数表示中的位反转(取一长串一的补码——因此得名 ones'补码)。在这个方案中,最大正数仍然是0x7fffffff(2147483647),最大负数是0x80000000(-2147483647)。 0 又有两种表示形式:正零是0x00000000,负零是0xffffffff。此方案在涉及负数的计算方面也存在问题。
    • 如果我们使用二进制补码方案,负数是通过取一个补码表示并添加1 得到的。在这个方案中,只有一个0,即0x00000000。最大的正数是0x7fffffff (2147483647),最大的负数是0x80000000 (-2147483648)。这种表示存在不对称性。这种方案的优点是不必处理负数的特殊情况。只要结果不溢出,表示就会为您提供正确的答案。出于这个原因,当前的大多数硬件都以这种表示形式表示整数。

    在二进制补码表示中,没有办法表示 231。事实上,如果您查看编译器的 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
    

    你会找回原来的号码。

    【讨论】:

    • 这是一个非常好的评论,@gbarry++(这个评论否定了一些东西;我只是不确定是什么)。
    【解决方案5】:

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

    使用二进制补码存储负数。算法是这样的......

    翻转所有位,然后加 1。

    以八位数字为例...

    +0 = -0

    00000000 -> 11111111, 11111111 + 1 = 100000000

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

    和...

    -128 [又名 -(2^7)] 等于 -(-128)

    10000000 -> 01111111, 01111111 + 1 = 10000000

    希望这会有所帮助。

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

        我认为abs 的工作方式是首先检查号码的sign bit。如果它明确什么都不做,因为号码已经是+ve,否则返回号码的2's complement。在您的情况下,号码是-ve,我们需要找到它的2's complement。但是0x80000000 的 2 的补码恰好是 0x80000000 本身。

        【讨论】:

        • 这种检查不太可能发生。这样的检查是完全没有用的——结果是相同的——同时为每次调用消耗额外的时间。成本和收益之间的权衡不是很好。
        • 嗯,你的意思是检查数字是否已经是正数?但是如果你取一个正数的 2 的补码,你会得到负数,而不是绝对值。
        【解决方案8】:

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

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

        现在又是一个我们不想要的负数,原因是溢出,试试数字 0x9000... 也就是 10010...

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

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

        【讨论】:

          【解决方案9】:

          因为它使用 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 所以它会设置溢出标志并保持沉默。这就是原因。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-03-14
            • 2015-01-19
            • 2021-02-15
            • 1970-01-01
            • 2017-09-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多