【问题标题】:Integer subtraction with wrap around for N bits整数减法与环绕 N 位
【发布时间】:2012-01-08 17:17:32
【问题描述】:

基本上,当用减法溢出整数时得到的行为,但对于给定的位数。显而易见的方法,假设一个 signed 整数:

template <int BITS>
int sub_wrap(int v, int s) {
  int max = (1<<(BITS));
  v -= s;
  if (v < -max) v += max*2;
  // or if branching is bad, something like:
  // v += (max*2) * (v < -max)
  return v;
}

// For example subtracting 24 from -16 with 5 bit wrap,
// with a range of -32, 31
sub_wrap<5>(-16, 28); -> 20

有没有一种简洁的方法,它比上面的方法不那么难看,最好更快?

更新:很抱歉造成混乱。我不假思索地包含了使用不包括叹息位的位数的令人困惑的符号。因此,在上面,将 5 位替换为 6 位会更加合理。

【问题讨论】:

  • 您还需要检查v &gt;= max
  • -32 到 31 的范围需要 6 位,而不是 5 位。
  • 这完全取决于您的观点。我只是习惯于在我当前使用的代码中表示不包括符号的位数,但我想这只是令人困惑。

标签: c++ math bit-manipulation subtraction


【解决方案1】:

我想这应该可行:

 struct bits
 {
     signed int field : 5;
 };

 bits a = { -16 };     
 bits b = {  28 };

 bits c = { a.field - b.field };
 std::cout << c.field << std::endl;

我很确定字段宽度不适用于 const 模板参数...因此这不太通用。但是,它应该避免手动修补。即将发布测试

更新 事实证明我的回答并没有错。只是样本输入(28)不能用 5 位(有符号)表示。以上结果为 -12(参见http://ideone.com/AUrXy)。

为了完整起见,这里毕竟是一个模板版本:

template<int bits>
int sub_wrap(int v, int s)
{
    struct helper { signed int f: bits; } tmp = { v };
    return (tmp.f -= s);
}

【讨论】:

  • 另一方面,从struct 分配并返回该字段将起作用。这可能是有符号值的最简单解决方案。
  • @JamesKanze:我不知道你到底是什么意思,但这似乎表明它不起作用:ideone.com/AUrXy
  • 正如写的那样,它不起作用,但是如果您声明位域signed int,将计算值赋回其中,然后从位域返回值,它应该可以工作(并为我做)。
  • 位域默认有符号还是无符号由实现定义。
  • @interjay:现场(第 9.6 节,第 3 节)。固定;
【解决方案2】:

以下是我在没有条件分支和乘法的情况下的做法:

#include <stdio.h>

// Assumptions:
// - ints are 32-bit
// - signed ints are 2's complement
// - right shifts of signed ints are sign-preserving arithmetic shifts
// - signed overflows are harmless even though strictly speaking they
//   result in undefined behavior
//
// Requirements:
// - 0 < bits <= 32
int sub_wrap(int v, int s, unsigned bits)
{
  int d = v - s;
  unsigned m = ~0u >> (32 - bits);
  int r = d & m | -((d >> (bits - 1)) & 1) & ~m;
  return r;
}

#define BITS 2

int main(void)
{
  int i, j;
  for (i = -(1 << (BITS - 1)); i <= (1 << (BITS - 1)) - 1; i++)
    for (j = -(1 << (BITS - 1)); j <= (1 << (BITS - 1)) - 1; j++)
      printf("%d - %d = %d\n", i, j, sub_wrap(i, j, BITS));
  return 0;
}

输出:

-2 - -2 = 0
-2 - -1 = -1
-2 - 0 = -2
-2 - 1 = 1
-1 - -2 = 1
-1 - -1 = 0
-1 - 0 = -1
-1 - 1 = -2
0 - -2 = -2
0 - -1 = 1
0 - 0 = 0
0 - 1 = -1
1 - -2 = -1
1 - -1 = -2
1 - 0 = 1
1 - 1 = 0

【讨论】:

    【解决方案3】:

    对于无符号算术,并屏蔽结果,例如:

    template<int bits>
    unsigned
    sub_wrap( unsigned v, unsigned s )
    {
        return (v - s) & ((1 << bits) - 1);
    }
    

    更一般地,您可以使用模运算符:

    template<int modulo>
    unsigned
    sub_wrap( unsigned v, unsigned s )
    {
        return (v - s) % modulo;
    }
    

    (包裹在n 位上相当于模2^n。)

    对于有符号算术,它有点复杂;使用掩码,您必须签署扩展结果(假设 2 的补码)。

    编辑:使用 sehe 对有符号算术的建议:

    template<int bits>
    int
    sub_wrap( int v, int s )
    {
        struct Bits { signed int r: bits; } tmp;
        tmp.r = v - s;
        return tmp.r;
    }
    

    鉴于此,sub_wrap&lt;5&gt;( -16, 28 ) 给出-12(这是正确的——注意28 不能表示为 5 位的有符号整数); sub_wrap&lt;6&gt;( -16, 28 ) 给了20

    【讨论】:

    • 您的代码永远不会产生负值,这似乎是 OP 想要的。
    • @Alex 从他的例子来看,这就是他想要的,但根据他写的,并不清楚。我的代码是我认为的“经典”解决方案,对于[0,2^n) 范围内的模运算,但对于模运算,我将使用无符号整数类型。不过,@sehe 的建议非常聪明,也适用于有符号类型。
    • +1 用于尝试更多,并且 (a) 指出我不应该从 OP 中以面值 (b) 显示位域 可以 变化基于 const 模板参数(weehoo:永远不要低估 C++ 的力量)
    • @JamesKanze:从 OP 的代码可以看出,结果范围也应该包括负数。见if (v &lt; -max) v += max*2;
    • 感谢这个干净的答案,很抱歉这个令人困惑的例子。我提出了another question关于这将如何执行。
    【解决方案4】:

    这模拟了一个 n 位整数运算:

    #include <iostream>
    #include <cstdlib>
    
    template< typename T >
    T sub_wrap(T a, T b, int nBits)
    {
            T topBit, mask, tmp;
    
            topBit=T(1) << (nBits-1);
            mask=(topBit << 1)-1;
            tmp=((a&mask)+((~b+1)&mask))&mask;
            if (tmp & topBit) tmp=-((~tmp&mask)+1);
    
            return tmp;
    }
    
    int main(int argc, char* argv[])
    {
            std::cout << sub_wrap< int >(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]))
                    << std::endl;
            return 0;
    }
    

    结果:

    $ ./sim 5 6 4
    -1
    $ ./sim 7 3 4
    4
    $ ./sim 7 -1 4
    -8
    $ ./sim -16 28 4
    4
    $ ./sim -16 28 5
    -12
    $ ./sim -16 28 6
    20
    

    您似乎将字体大小误算了 1 位。

    【讨论】:

      猜你喜欢
      • 2013-01-08
      • 2019-04-16
      • 2012-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-01
      • 2018-05-27
      • 2011-01-04
      相关资源
      最近更新 更多