【问题标题】:Clean, efficient algorithm for wrapping integers in C++用于在 C++ 中包装整数的干净、高效的算法
【发布时间】:2010-10-16 23:33:15
【问题描述】:
/**
  * Returns a number between kLowerBound and kUpperBound
  * e.g.: Wrap(-1, 0, 4); // Returns 4
  * e.g.: Wrap(5, 0, 4); // Returns 0      
  */
int Wrap(int const kX, int const kLowerBound, int const kUpperBound)
{
    // Suggest an implementation?
}

【问题讨论】:

  • 这个函数应该做什么?它是如何在第一种情况下达到 4 而在第二种情况下达到 0 的?
  • 这是一个“换行”功能。任何不在两个边界之间的数字然后“环绕”到另一侧并根据它所在的一侧开始递减/递增。
  • 群编程。太棒了。
  • :) 是的。我现在的代码库中有一个糟糕的解决方案,所以我可以继续工作,但这是我能想到的非专有代码的最佳伙伴检查。 :)

标签: c++ algorithm math word-wrap integer


【解决方案1】:

在下限为零的特殊情况下,此代码避免了除法、取模和乘法。上限不必是二的幂。这段代码过于冗长,看起来很臃肿,但编译成 3 条指令:减法、移位(按常数)和“与”。

#include <climits>       // CHAR_BIT

// -------------------------------------------------------------- allBits
// sign extend a signed integer into an unsigned mask:
//   return all zero bits (+0) if arg is positive,
//       or all one  bits (-0) for negative arg
template <typename SNum>
static inline auto allBits (SNum arg) {
  static constexpr auto argBits = CHAR_BIT * sizeof( arg);
  static_assert( argBits < 256, "allBits() sign extension may fail");
  static_assert( std::is_signed< SNum>::value, "SNum must be signed");
  typedef typename std::make_unsigned< SNum>::type UNum;
  // signed shift required, but need unsigned result
  const UNum mask = UNum( arg >> (argBits - 1));
  return mask;
}

// -------------------------------------------------------------- boolWrap
// wrap reset a counter without conditionals:
//   return arg >= limit? 0 : arg
template <typename UNum>
static inline auto boolWrap (const UNum arg, const UNum limit) {
  static_assert( ! std::is_signed< UNum>::value, "UNum assumed unsigned");
  typedef typename std::make_signed< UNum>::type SNum;
  const SNum negX  = SNum( arg) - SNum( limit);
  const auto signX = allBits( negX);    // +0 or -0
  return arg & signX;
}
// example usage:
for (int j= 0; j < 15; ++j) {
   cout << j << boolWrap( j, 11);
}

【讨论】:

    【解决方案2】:

    我个人发现,如果范围是互斥的并且除数被限制为正值,这些类型的函数的解决方案会更简洁。

    int ifloordiv(int x, int y)
    {
        if (x > 0)
            return x / y;
        if (x < 0)
            return (x + 1) / y - 1;
        return 0
    }
    
    int iwrap(int x, int y)
    {   return x - y * ifloordiv(x, y);
    }
    

    集成。

    int iwrap(int x, int y)
    {
        if (x > 0)
            return x % y;
        if (x < 0)
            return (x + 1) % y + y - 1;
        return 0;
    }
    

    同一个家庭。为什么不呢?

    int ireflect(int x, int y)
    {
        int z = iwrap(x, y*2);
        if (z < y)
            return z;
        return y*2-1 - z;
    }
    
    int ibandy(int x, int y)
    {
        if (y != 1)
            return ireflect(abs(x + x / (y - 1)), y);
        return 0;
    }
    

    可以为所有功能实现远程功能,

    // output is in the range [min, max).
    int func2(int x, int min, int max)
    {
        // increment max for inclusive behavior.
        assert(min < max);
        return func(x - min, max - min) + min;
    }
    

    【讨论】:

      【解决方案3】:

      为什么不使用扩展方法。

      public static class IntExtensions
      {
          public static int Wrap(this int kX, int kLowerBound, int kUpperBound)
          {
              int range_size = kUpperBound - kLowerBound + 1;
      
              if (kX < kLowerBound)
                  kX += range_size * ((kLowerBound - kX) / range_size + 1);
      
              return kLowerBound + (kX - kLowerBound) % range_size;
          }
      }
      

      用法:currentInt = (++currentInt).Wrap(0, 2);

      【讨论】:

      • 因为问题是关于 C++,而不是关于 C#?
      【解决方案4】:

      请不要忽视这篇文章。 :)

      这样好吗?

      int Wrap(N,L,H){
        H=H-L+1; return (N-L+(N<L)*H)%H+L;
      }
      

      这适用于负输入,只要 L 小​​于 H,所有参数都可以是负数。

      背景...(注意这里H是重用变量,设置为原始H-L+1)。

      我在递增时一直使用(N-L)%H+L,但与几个月前开始学习 C 之前我在 Lua 中使用的不同,如果我使用低于下限的输入,这将不起作用,更不用说负输入。 (Lua 是用 C 构建的,但我不知道它在做什么,而且它可能不会很快......)

      我决定添加+(N&lt;L)*H 来生成(N-L+(N&lt;L)*H)%H+L,因为C 似乎被定义为true=1 和false=0。它对我来说效果很好,并且似乎巧妙地回答了原始问题。如果有人知道如何在没有 MOD 运算符 % 的情况下使其速度惊人,请这样做。我现在不需要速度,但有一段时间我会的,毫无疑问。

      编辑:

      如果NL 低超过H-L+1,则该函数会失败,但这不会:

      int Wrap(N,L,H){
        H-=L; return (N-L+(N<L)*((L-N)/H+1)*++H)%H+L;
      }
      

      我认为它会在任何系统中在整数范围的负极端处中断,但应该适用于大多数实际情况。它增加了一个额外的乘法和除法,但仍然相当紧凑。

      (这个编辑只是为了完成,因为我想出了一个更好的方法,在这个线程的一个更新的帖子中。)

      乌鸦。

      【讨论】:

        【解决方案5】:

        我的另一篇文章很糟糕,所有“纠正性”乘法和除法都失控了。在查看了 Martin Stettner 的帖子以及我自己的 (N-L)%H+L 起始条件后,我想出了这个:

        int Wrap(N,L,H){
          H=H-L+1; N=(N-L)%H+L; if(N<L)N+=H; return N;
        }
        

        在整数范围的极端负端,它会像我的另一个那样中断,但它会更快,并且更容易阅读,并且避免了其他的讨厌。

        乌鸦。

        【讨论】:

          【解决方案6】:

          我也遇到过这个问题。这是我的解决方案。

          template <> int mod(const int &x, const int &y) {
              return x % y;
          }
          template <class T> T mod(const T &x, const T &y) {
              return ::fmod((T)x, (T)y);
          }
          template <class T> T wrap(const T &x, const T &max, const T &min = 0) {
              if(max < min)
                  return x;
              if(x > max)
                  return min + mod(x - min, max - min + 1);
              if(x < min)
                  return max - mod(min - x, max - min + 1);
              return x;
          }
          

          我不知道它是否好,但我想我会分享,因为我在对这个问题进行谷歌搜索时被引导到这里,发现上述解决方案缺乏我的需求。 =)

          【讨论】:

            【解决方案7】:

            一个具有一定对称性的答案,并且很明显,当 kX 在范围内时,它会原封不动地返回。

            int Wrap(int const kX, int const kLowerBound, int const kUpperBound)
            {
                int range_size = kUpperBound - kLowerBound + 1;
            
                if (kX < kLowerBound)
                    return kX + range_size * ((kLowerBound - kX) / range_size + 1);
            
                if (kX > kUpperBound)
                    return kX - range_size * ((kX - kUpperBound) / range_size + 1);
            
                return kX;
            }
            

            【讨论】:

              【解决方案8】:

              a % b 的符号仅在 ab 均为非负数时定义。

              int Wrap(int kX, int const kLowerBound, int const kUpperBound)
              {
                  int range_size = kUpperBound - kLowerBound + 1;
              
                  if (kX < kLowerBound)
                      kX += range_size * ((kLowerBound - kX) / range_size + 1);
              
                  return kLowerBound + (kX - kLowerBound) % range_size;
              }
              

              【讨论】:

              • 第一个适用于负数的解决方案,无需依赖未指定的行为或循环。
              • 我希望如此...考虑到这个问题看起来多么“简单”,如果我犯了错误我不会感到惊讶。它确实依赖于 kUpperBound >= kLowerBound。
              • 对于 Wrap(-1, 1, 4)) 它给出 3。我认为它应该返回 4。
              • 对于 1 -> 4 的范围,肯定是 0 == 4 和 -1 == 3?
              • 好吧,你是对的。我的回答将 kX 视为某种下限和上限的索引。
              【解决方案9】:

              最快的解决方案,最不灵活:利用将在硬件中进行包装的本机数据类型。

              包装整数的绝对最快的方法是确保您的数据缩放为 int8/int16/int32 或任何本机数据类型。然后当你需要你的数据来包装本机数据类型时,将在硬件中完成!比这里看到的任何软件封装实现都非常轻松且速度快几个数量级

              作为一个案例研究:

              当我需要使用 sin/cos 实现的查找表快速实现 sin/cos 时,我发现这非常有用。基本上,您可以缩放数据,使 INT16_MAX 为 pi,而 INT16_MIN 为 -pi。那你准备好了吗?

              附带说明一下,缩放数据会增加一些前期有限计算成本,通常看起来像:

              int fixedPoint = (int)( floatingPoint * SCALING_FACTOR + 0.5 )
              

              随意将 int 换成您想要的其他东西,例如 int8_t / int16_t / int32_t。


              下一个最快的解决方案,更灵活:mod 操作很慢,尽可能尝试使用位掩码!

              我浏览的大多数解决方案在功能上都是正确的……但它们依赖于 mod 操作。

              mod 操作非常慢,因为它本质上是在做一个hardware division。外行人对 mod 和除法为什么慢的解释是将除法操作等同于一些伪代码 for(quotient = 0;inputNum&gt; 0;inputNum -= divisor) { quotient++; }quotient and divisor 的 def )。如您所见,如果相对于除数而言,硬件除法可能很快...但是如果它比除数大得多,除法也可能非常慢。

              如果您可以将数据缩放到 2 的幂,那么您可以使用将在一个周期内执行的位掩码(在所有平台的 99% 上),并且您的速度将提高大约一个数量级(至少快 2 到 3 倍)

              实现包装的C代码:

              #define BIT_MASK (0xFFFF)
              int wrappedAddition(int a, int b) {
                  return ( a + b ) & BIT_MASK;
              }
              int wrappedSubtraction(int a, int b) {
                  return ( a - b ) & BIT_MASK;
              }
              

              随意将#define 设置为运行时。并随意将位掩码调整为您需要的任何二的幂。比如 0xFFFFFFFF 或您决定实施的 2 的幂。


              附言我强烈建议在处理包装/溢出条件时阅读有关定点处理的信息。我建议阅读:

              Fixed-Point Arithmetic: An Introduction by Randy Yates August 23, 2007

              【讨论】:

                【解决方案10】:

                实际上,因为 -1 % 4 在我使用过的每个系统上都返回 -1,所以简单的 mod 解决方案不起作用。我会尝试:

                int range = kUpperBound  - kLowerBound +1;
                kx = ((kx - kLowerBound) % range) + range;
                return (kx % range) + kLowerBound;
                

                如果 kx 是正数,你修改,添加范围,然后修改回来,撤消添加。如果 kx 是负数,你修改,添加使其成为正数的范围,然后再次修改,这不会做任何事情。

                【讨论】:

                • 这比我的'while'好:)
                • 尝试 lb = 3, ub = 8, kx = 5。显然 kx 在范围内,所以应该给出 5 但是... 第 2 行:kx = (5 % 6) + 6 == 11,然后在第 3 行: (kx % 6) + 3 = 5 + 3 == 8 != 5.
                • 仅仅因为它在您知道的每个系统上都这样做,并不意味着它是正确的。
                • 必须在第二行读取kx=((kx-kLowerBound)%range)+range。
                【解决方案11】:

                我会给出最常见情况下界=0,上界=N-1 的入口点。并在一般情况下调用此函数。在我已经在范围内的地方没有进行任何 mod 计算。它假设upper>=lower,或n>0。

                int wrapN(int i,int n)
                {
                  if (i<0) return (n-1)-(-1-i)%n; // -1-i is >=0
                  if (i>=n) return i%n;
                  return i; // In range, no mod
                }
                
                int wrapLU(int i,int lower,int upper)
                {
                  return lower+wrapN(i-lower,1+upper-lower);
                }
                

                【讨论】:

                  【解决方案12】:

                  以下内容应独立于 mod 运算符的实现:

                  int range = kUpperBound - kLowerBound + 1;
                  kx = ((kx-kLowerBound) % range);
                  if (kx<0)
                    return kUpperBound + 1 + kx;
                  else
                    return kLowerBound + kx;
                  

                  相对于其他解决方案的一个优势是,它只使用一个 %(即除法),这使得它非常高效。

                  注意(离题):

                  这是一个很好的例子,为什么有时定义区间是明智的,上限是不在范围内的第一个元素(例如对于 STL 迭代器...)。在这种情况下,两个“+1”都会消失。

                  【讨论】:

                    【解决方案13】:

                    我会建议这个解决方案:

                    int Wrap(int const kX, int const kLowerBound, int const kUpperBound)
                    {
                        int d = kUpperBound - kLowerBound + 1;
                        return kLowerBound + (kX >= 0 ? kX % d : -kX % d ? d - (-kX % d) : 0);
                    }
                    

                    ?: 运算符的 if-then-else 逻辑确保 % 的两个操作数都是非负数。

                    【讨论】:

                      【解决方案14】:

                      对于负kX,可以加:

                      int temp = kUpperBound - kLowerBound + 1;
                      while (kX < 0) kX += temp;
                      return kX%temp + kLowerBound;
                      

                      【讨论】:

                      • 工作但不优雅。试试“{temp2 = -kX/temp;kX += temp2+1;}”
                      • 应该是“如果 (kX
                      • 这不适用于非零下限。例如。 lb = 3, ub = 8, kX = 5。显然 5 已经在范围内,但 temp = 6 和 5 % 6 + 3 = 8 != 5。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-12-11
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多