【问题标题】:How to compute the integer absolute value如何计算整数绝对值
【发布时间】:2012-08-16 00:25:31
【问题描述】:

如何在不使用if 条件的情况下计算整数绝对值。 我想我们需要使用一些按位运算。 有人可以帮忙吗?

【问题讨论】:

  • 是否允许使用三元运算符?
  • 不,我想你也不能使用它。
  • 动机是什么?表现?您是否试图通过消除分支来解决分支预测失败?求知欲?在家工作?还有什么?
  • ABS(INT_MIN) 的正确答案是什么? (为简单起见使用 8 位):理想情况下 ABS(-128) 应该是 128。但最大有符号的 8 位 int 仅为 127!

标签: algorithm bit-manipulation


【解决方案1】:

与现有答案相同,但有更多解释:

让我们假设一个二进制补码数(因为这是通常的情况,你不会说别的),让我们假设 32 位:

首先,我们执行算术右移 31 位。这会将所有1s 转换为负数或所有0s 转换为正数(但请注意,C 或C++ 中>>-operator 的实际行为是为负数定义的实现,但通常也会执行算术移位,但让我们假设伪代码或实际的硬件指令,因为无论如何这听起来像是家庭作业):

mask = x >> 31;

所以我们得到的是111...111 (-1) 表示负数,000...000 (0) 表示正数

现在我们用x 对它进行异或,得到mask=111...111(负)的NOT 和mask=000...000(正)的空操作:

x = x XOR mask;

最后减去我们的掩码,这意味着 +1 表示负数,+0/no-op 表示正数:

x = x - mask;

因此,对于正数,我们使用 0 和减法 0 执行 XOR,从而得到相同的数字。对于负数,我们得到了(NOT x) + 1,当使用二进制补码表示时,这正是-x

【讨论】:

  • 赞成清楚的解释,比公认的答案好得多!
  • 在 C 或 C++ 中,这不会导致未定义的行为吗?如果 x 是 INT_MIN,那么 XOR 操作将使 x == INT_MAX,并且将 1 加到 INT_MAX 是溢出错误(也就是有符号整数的未定义行为)
  • @JoshuaWise 好吧,不过,我并没有假设特定的编程语言。在这种情况下,签名的右移已经是实现定义的行为。
  • 如果我来到这里寻找改进我在 Hacker Rank 上编写的解决方案的方法,并且不知道这个答案中的任何内容是什么意思,我应该从哪里开始?对于上下文:在我的 HR 解决方案中,我通过检查 if 来实现 i 的绝对值,该值小于 0,如果是,则执行 i = i + (i * -2)
  • 如果您不了解此答案中的任何内容的含义,恐怕您可能不得不寻找其他地方。您的绝对值解决方案也可能有效。不过,这不是被问到的。一般来说,有关按位运算如何工作的更多信息,关于这些的一般学习资源可能比尝试爬取 SO 答案更好。
【解决方案2】:
  1. 将掩码设置为整数右移 31(假设整数存储为二进制补码 32 位值并且右移运算符进行符号扩展)。

    mask = n>>31 
    
  2. 将掩码与数字异或

    mask ^ n 
    
  3. 从第 2 步的结果中减去掩码并返回结果。

    (mask^n) - mask 
    

【讨论】:

  • 请记住,并非所有语言都以相同的方式解释整数。有些支持正零和负零,其中abs(~int)-abs(int) 导致0,而您的解决方案需要:abs(abs(~int)-abs(int))beeing 1。
  • @dhein 什么语言有-0 整数?在这一点上,我还没有听说过任何这样的语言。也许一些非常旧的硬件有这样的,因为工程师需要一点时间来决定使用哪种方案,但我也不知道有什么硬件可以做到这一点。附带说明一下,JavaScript 没有整数。他们在内部使用double,并在某些情况下将值作为整数处理。
【解决方案3】:

假设 int 是 32 位的。

int my_abs(int x)
{
    int y = (x >> 31);
    return (x ^ y) - y;
}

【讨论】:

    【解决方案4】:

    上述操作也可以如下进行:

    return n*(((n>0)<<1)-1);
    

    其中n是绝对需要计算的数字。

    【讨论】:

      【解决方案5】:

      在发现这个问题之前,我自己写了。

      我的回答可能比较慢,但仍然有效:

      int abs_of_x = ((x*(x >> 31)) | ((~x + 1) * ((~x + 1) >> 31)));
      

      【讨论】:

        【解决方案6】:

        如果您不允许使用减号,您可以这样做:

        int absVal(int x) {
          return ((x >> 31) + x) ^ (x >> 31);
        }
        

        【讨论】:

          【解决方案7】:

          在 C 中,您可以使用联合对双精度数执行位操作。以下将在 C 中工作,可用于整数、浮点数和双精度数。

          /**
          * Calculates the absolute value of a double.
          * @param x An 8-byte floating-point double
          * @return A positive double
          * @note Uses bit manipulation and does not care about NaNs
          */
          double abs(double x)
          {
              union{
                  uint64_t bits;
                  double dub;
              } b;
          
              b.dub = x;
          
              //Sets the sign bit to 0
              b.bits &= 0x7FFFFFFFFFFFFFFF;
          
              return b.dub;
          }
          

          请注意,这假定双精度为 8 个字节。

          【讨论】:

            【解决方案8】:

            对于汇编最有效的方法是将一个值初始化为 0,减去整数,然后取最大值:

            pxor mm1, mm1 ; set mm1 to all zeros
            psubw mm1, mm0 ; make each mm1 word contain the negative of each mm0 word
            pmaxswmm1, mm0 ; mm1 will contain only the positive (larger) values - the absolute value
            

            【讨论】:

            • SSSE3(Core2及更新版本)直接有pabsw
            • 确实是的,但问题被标记为“算法”,所以我认为作者不想要一个自动完成工作的库函数或指令;)
            【解决方案9】:

            C#中,您可以在不使用任何局部变量的情况下实现abs()

            public static long abs(long d) => (d + (d >>= 63)) ^ d;
            
            public static int abs(int d) => (d + (d >>= 31)) ^ d;
            

            注意:关于 0x80000000 (int.MinValue) 0x8000000000000000 (long.MinValue): em>

            与本页显示的所有其他按位/非分支方法一样,这给出了单个非数学结果abs(int.MinValue) == int.MinValue(同样适用于long.MinValue)。这些表示 only 结果值为负数的情况,即two's-complement 结果的MSB1 -- 并且也是返回输入值的唯一情况不变。我认为本页其他地方没有提到这一点。

            上面显示的代码取决于 xor右侧 侧使用的 d 的值,即在计算 左侧。对于 C# 程序员来说,这似乎很明显。他们习惯于看到这样的代码,因为 .NET 正式合并了一个强大的memory model,它严格保证了正确的获取顺序。我之所以提到这一点是因为在CC++ 中可能需要更加谨慎。后者的内存模型更加宽松,这可能允许某些编译器优化发出乱序提取。显然,在这种情况下,获取顺序的敏感性将代表正确性风险。

            【讨论】:

              【解决方案10】:

              如果您不想在右位移时依赖符号扩展的实现,您可以修改计算mask的方式:

              mask = ~((n >> 31) & 1) + 1
              

              然后按照前面的答案中已经证明的那样进行:

              (n ^ mask) - mask
              

              【讨论】:

                【解决方案11】:

                您使用的是什么编程语言?在 C# 中,您可以使用 Math.Abs 方法:

                int value1 = -1000;
                int value2 = 20;
                int abs1 = Math.Abs(value1);
                int abs2 = Math.Abs(value2);
                

                【讨论】:

                • 好吧,我很确定这也被这个问题排除了。当然,使用预建函数总是最简单的;)
                • 我现在可以看到了。值得尝试帮助。还好他得到了答案。
                • 总是值得一提的是显而易见的。有时我发现应该更快的位掩码或无分支黑客攻击确实更慢。一切都取决于处理器和编译器。
                猜你喜欢
                • 2010-10-22
                • 1970-01-01
                • 1970-01-01
                • 2015-11-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多