【问题标题】:get absolute value without using abs function nor if statement在不使用 abs 函数或 if 语句的情况下获取绝对值
【发布时间】:2012-04-04 01:36:23
【问题描述】:

我在考虑如何在不使用if 语句或abs() 的情况下获取整数的绝对值。起初我使用左移位(<<),试图让负号超出范围,然后将位右移回原来的位置,但不幸的是它对我不起作用。请让我知道为什么它不起作用以及其他替代方法。

【问题讨论】:

  • 如果您知道要处理的 int 的大小,只需使用逐位“与”清除最高位。
  • @MarcB:这将适用于符号/幅度表示(这是相当不寻常的),但对于 1 的补码或(迄今为止最常见的)2 的补码会失败。
  • @MarcB:它比 2 的补码稍微复杂一些。
  • 这不是作业,而是我的编译器课程讲师提出的问题。我发现这是一个有趣的问题,因为我以前从未这样做过。顺便说一句,解决这个问题不会提高我的课程成绩,但肯定会提高我的编码技能。 ^__^
  • @adembudak 这是一个关于无分支编程的问题,这是一种无需控制流(因此没有 if / else、三元或循环)的部分代码的编程技术。 OP 想知道 如何 它是如何完成的,而不是执行它的函数的名称。

标签: c++ c bit-manipulation


【解决方案1】:

来自Bit Twiddling Hacks

int v;           // we want to find the absolute value of v
unsigned int r;  // the result goes here 
int const mask = v >> sizeof(int) * CHAR_BIT - 1;

r = (v + mask) ^ mask;

【讨论】:

  • v >> sizeof(int) * CHAR_BIT - 1 你能解释一下这是做什么的吗?
  • @codeymodey:我没有写原文,但这取决于 2 的补码表示。如果设置了符号位,则它使掩码等于全 1(因为它正在右移,这通常是算术移位,所以会发生符号扩展)。这相当于根据符号位将掩码设置为-1或0。
  • @codeymodey: CHAR_BIT 是 char 中的位数,通常为 8。它在 limits.h 中定义。对于 32 位整数,此表达式返回 31
  • 不是右移有符号整数实现定义了吗?基本上,我们将 mask = 0x0 设置为正数,将 mask=0xffffffff 设置为负数。 "-((unsigned)num>>31)" 是不是正确还是速度较慢?
  • @ZxcvMnb:是的,有符号整数右移是实现定义的。正如我在之前的评论中提到的,这通常是算术右移(例如,GCC defines this as such)。我不知道您的变体是否较慢,尽管它可能需要更多操作(即逻辑移位、取反而不是单个算术移位),但无论如何,两种变体都需要 2 的补码表示。链接页面有更多讨论,您可能会发现这些讨论相关。
【解决方案2】:
int abs(int v) 
{
  return v * ((v>0) - (v<0));
}

此代码将v 的值与-11 相乘得到abs(v)。因此,括号内将是-11 之一。

如果v 为正,则表达式(v&gt;0) 为真,并且将具有值1,而(v&lt;0) 为假(值为0 表示假)。因此,当v 为正时((v&gt;0) - (v&lt;0)) = (1-0) = 1。整个表达式是:v * (1) == v

如果v 为负数,则表达式(v&gt;0) 为假并且将具有值0(v&lt;0) 为真(值1)。因此,对于否定的v((v&gt;0) - (v&lt;0)) = (0-1) = -1。整个表达式是:v * (-1) == -v

v == 0 时,(v&lt;0)(v&gt;0) 都将计算为 0,留下:v * 0 == 0

【讨论】:

  • 只做v * ((v&gt;0) - (v&lt;0)) 会等效且更易于阅读,不是吗?
【解决方案3】:

无分支:

int abs (int n) {
    const int ret[2] = { n, -n };
    return ret [n<0];
}

注 4.7 积分转换 / 4: [...] If the source type is bool, the value false is converted to zero and the value true is converted to one.

【讨论】:

  • C 中的“无分支”,可能不会被编译一次。有趣的是,“无分支”实际上是目标代码的属性,而不是源代码的属性。
  • @SteveJessop:但更严重的是:可能,使用任何半体面的编译器。但是,这在代码结构中也是无分支的 :)
  • 好吧,假设我说“很可能一次编译”。我是对还是错,这有关系吗? ;-)
  • 不,我的“可能”是classy laconian "If." 的风格。我认为这个问题没有太多价值,我的回答更像是一个故意粗暴的演示:P
  • 硬件如何实现布尔到整数的转换?这是在没有条件分支的情况下完成的吗?
【解决方案4】:

我在 C 中尝试了这段代码,它可以工作。

int abs(int n){
   return n*((2*n+1)%2); 
}

希望这个答案会有所帮助。

【讨论】:

  • 最佳答案在这里!!
  • 导致大 n 溢出。
  • 它工作得非常好,简单而强大的逻辑。
  • @KiranChuahan 不,2*n + 1 会溢出,它不适用于大量数据
  • 为什么不只是n * (n % 2);
【解决方案5】:

假设 32 位有符号整数(Java),你可以这样写:

public static int abs(int x)
{
    return (x + (x >> 31)) ^ (x >> 31);
}

没有乘法,没有分支。

顺便说一句,return (x ^ (x &gt;&gt; 31)) - (x &gt;&gt; 31); 也可以,但它已获得专利。是的!

注意:此代码可能比条件语句(8 位版本)长 10 倍以上。这可能对硬件编程 System C 等有用

【讨论】:

  • 你如何为这样的东西申请专利?
  • 问题是针对c,而不是java。 -1.
  • 此代码对 c 和对 java 一样有效。将 int 替换为 int32_t
【解决方案6】:

尝试以下方法:

int abs(int n) 
{
  return sqrt(n*n);
}

【讨论】:

  • sqrt 非常昂贵,此外它接受 double 作为参数,因此您有 2 次转换(int 到 double)和(double 到 int)
  • 这实际上几乎让我找到了一个解决方案,我需要一个不支持函数的表达式(ADO.Net DataColumn 表达式中的计算字段)。它也可以写成 (n*n)^(1/2)。不幸的是,也不支持电源 (^)...
  • 除了速度慢之外,n 的较大值会溢出,如果浮点类型不包含两倍于 int 的精度,它将无法正常工作跨度>
【解决方案7】:

没看到这个。对于二进制补码表示和 32 位 int

( n >> 31 | 1 ) * n

【讨论】:

  • 很好的解决方案!这是一个更好的版本- ( n >> sizeof(int)*8-1 | 1 ) * n
【解决方案8】:

没有分支或乘法:

int abs(int n) {
    int mask = n >> 31;
    return (mask & -n) | (~mask & n);
}

【讨论】:

    【解决方案9】:

    这是另一种没有abs()、if 也没有任何逻辑/条件表达式的方法: 假设 int 在这里是 32 位整数。这个想法很简单:(1 - 2 * sign_bit) 将转换为sign_bit = 1 / 0 to -1 / 1

    unsigned int abs_by_pure_math( int a ) {
       return (1 - (((a >> 31) & 0x1) << 1)) * a;
    }
    

    【讨论】:

      【解决方案10】:

      以您认为的方式对有符号整数进行位移是未定义的行为,因此不是一种选择。相反,您可以这样做:

      int abs(int n) { return n > 0 ? n : -n; }
      

      没有if 语句,只是一个条件表达式。

      【讨论】:

      • 虽然从技术上讲这回答了问题,但三元组实际上只是一个紧凑的 if 语句,因此它可能不是 OP 正在寻找的。​​span>
      • 它使用不同的语法,并返回一个值(与 if 不同),但编译后仍然包含一个分支,这通常是人们想要避免 if 语句时所谈论的。这可能会编译为与明显的 if 实现相同的机器代码。
      • @AaronDufour:但标准并未将三元运算符定义为 if 语句。实际上,与 if 语句不同,三元运算符有一个值,它可以产生一个左值(例如x?y:z = 0;)。它编译成什么是无关紧要的。 switch 语句可以编译为查找表,if 语句可能完全消失,只有程序的可见行为不会改变(RVO 除外)
      • @phresnel 但是对于这样一个人为的问题,唯一合理的解释是尽量避免条件结构,其中包括三元和if 语句。否则问题是微不足道的,如this answer所示。这就是我在谈论编译到分支时试图传达的内容。
      • @AaronDufour:标题上写着without using abs function nor if statement,对我来说听起来像是if statementsabs-一系列要避免的函数...
      【解决方案11】:

      如果您的语言允许 bool 转换为 int cast(类似 C/C++):

      float absB(float n) {
          return n - n * 2.0f * ( n < 0.0f );
      }
      

      【讨论】:

        【解决方案12】:

        有多种原因将符号位左移出并右移回原位 (v &lt;&lt; 1 &gt;&gt; 1):

        • 左移带负值的有符号类型具有未定义的行为,因此根本不应该使用它。
        • 将值转换为unsigned 会产生预期的效果:(unsigned)v &lt;&lt; 1 &gt;&gt; 1 确实去掉了符号位,如果没有填充位,但结果值是 v 的绝对值,仅在具有符号+幅度表示,现在已经很少见了。在普遍存在的 2 的补码架构上,负 v 的结果值为 INT_MAX+1-v

        不幸的是,Hasturkun 的解决方案具有实现定义的行为。

        下面是一个完全定义的变体,用于有符号值的 2 补码表示的系统:

        int v;           // we want to find the absolute value of v
        unsigned int r;  // the result goes here 
        unsigned int mask = -((unsigned int)v >> (sizeof(unsigned int) * CHAR_BIT - 1));
        
        r = ((unsigned int)v + mask) ^ mask;
        

        【讨论】:

          【解决方案13】:

          这个呢:

          #include <climits>
          
          long abs (int n) { // we use long to avoid issues with INT MIN value as there is no positive equivalents.
              const long ret[2] = {n, -n};
              return ret[n >> (sizeof(int) * CHAR_BIT - 1)];    // we use the most significant bit to get the right index.
          }
          

          【讨论】:

            【解决方案14】:

            位移(原则上)是实现定义的,但转换为更广泛的有符号整数类型将扩展符号位。如果您将高位解释为整数,它们将为 0 或 -1,这将让您反转 2 的补码:

            int32_t abs(int32_t in)
            {
              int64_t in64 = (int64_t)in;
              int32_t* ptr = (int32_t*)&in64;
              int32_t hi = *(++ptr); // assumes little-endian
              int32_t out = (in ^ hi) - hi;
              return out;
            }
            

            上述机制是在打开优化的情况下编译朴素实现的结果:

            mov         eax,ecx  
            cdq  
            xor         eax,edx  
            sub         eax,edx  
            

            【讨论】:

              【解决方案15】:

              怎么样:

              value = value > 0 ? value: ~value + 1
              

              它基于这样一个事实,即负数存储为正等价的 2 的补码,并且可以通过首先构建 1 的补码并加 1 来构建 2 的补码,所以

               5 ->   0000 0101b
              -5 ->  (1111 1010b) + 1 -> 1111 1011b 
              

              我所做的基本上是为了扭转这一点,所以

              -5 ->  1111 1011b
               5 -> (0000 0100b) + 1 -> 0000 0101b
              

              我知道这有点晚了,但我遇到了同样的问题并登陆这里,希望这会有所帮助。

              【讨论】:

                【解决方案16】:

                使用除法(和更广泛的数学)来形成“如果”。也许效率不高,但没有分支。

                int abs_via_division(int v) {
                  // is_neg:0 when v >= 0
                  //        1 when v < 0
                  int is_neg = (int) ((4LL * v) / (4LL * v + 1));
                  return  v * (1 - is_neg*2);
                }
                

                long longint 宽时适用于所有int,除了|INT_MIN| 的常见问题。

                【讨论】:

                  【解决方案17】:

                  使用三元运算符:

                  y = condition ? value_if_true : value_if_false;
                  

                  【讨论】:

                    猜你喜欢
                    • 2021-06-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-06-23
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-09-29
                    • 2016-02-13
                    相关资源
                    最近更新 更多