大整数位旋转

时间:2012-06-10 22:55:32

标签: c++ bit-manipulation endianness

我想在整数类中实现无符号左旋转。但是,因为它是一个模板类,所以它可以是128位的任何大小并继续;所以我不能使用需要相同大小的临时算法,因为如果类型变大,就会发生堆栈溢出(特别是如果这样的函数在调用链中)。

因此,为了解决这个问题,我将其最小化为一个问题:我只需要使用4位旋转32位数就需要采取哪些步骤。好吧,如果你想一想,32位数字包含8组,每组4位,所以如果要旋转的位数是4,那么在组0 and 41 and 5之间会发生交换, 2 and 63 and 7,之后轮换完成。

如果要旋转小于4且大于0的位,则只需保留最后N位并启动shift-Or循环即可。假设我们将数字0x9CE2向左旋转3位,我们将执行以下操作:

little endian binary中的数字是1001 1100 1110 0010,每个半字节从右到左从0到3索引,我们将调用此数字N和一个组中的位数{{1} }

B

结果为[1] x <- N[3] >> 3 x <- 1001 >> 3 x <- 0100 y <- N[0] >> (B - 3) y <- N[0] >> (4 - 3) y <- 0010 >> 1 y <- 0001 N[0] <- (N[0] << 3) | x N[0] <- (0010 << 3) | 0100 N[0] <- 0000 | 0100 N[0] <- 0100 [2] x <- y x <- 0001 y <- N[1] >> (B - 3) y <- N[1] >> (4 - 3) y <- 1110 >> 1 y <- 0111 N[1] <- (N[1] << 3) | x N[1] <- (1110 << 3) | 0001 N[1] <- 0000 | 0001 N[1] <- 0001 [3] x <- y x <- 0111 y <- N[2] >> (B - 3) y <- N[2] >> (4 - 3) y <- 1100 >> 1 y <- 0110 N[2] <- (N[2] << 3) | x N[2] <- (1100 << 3) | 0111 N[2] <- 0000 | 0111 N[2] <- 0111 [4] x <- y x <- 0110 y <- N[3] >> (B - 3) y <- N[3] >> (4 - 3) y <- 1001 >> 1 y <- 0100 N[3] <- (N[3] << 3) | x N[3] <- (1001 << 3) | 0110 N[3] <- 1000 | 0110 N[3] <- 1110 1110 0111 0001 0100为十六进制,这是正确的答案;并且,如果您尝试将其应用于任何具有任何精度的数字,您只需要一个变量,该类型是形成该bignum类型的数组的任何元素的类型。

现在真正的问题是当旋转的数字位数大于一个组或大于该类型的一半大小时(即在此示例中大于4位或8位)。

通常,我们将位从最后一个元素移位到第一个元素,依此类推;但现在,转移后 第一个元素的最后一个元素,结果必须重新定位到新的位置,因为要旋转的位数大于元素(即0xE714)。移位开始的起始索引是最后一个索引(本例中为3),对于目标索引,我们使用等式:> 4 bits,其中dest_index = int(bits_count/half_bits) + 1是位数的一半,位于这个示例half_bits,所以如果half_bits = 8然后bits_count = 7,那意味着第一次转换的结果必须重新定位到目标索引2 - 这是我的问题,因为我无法想到为这种情况编写算法的方法。

感谢。

2 个答案:

答案 0 :(得分:2)

这只是一种提示,可以通过一种方式来实现这一目标。你可以考虑两次通过。

  • 第一次通过,仅在4位边界上旋转
  • 第二遍,在1位边界上旋转

因此,顶级伪代码可能如下所示:

rotate (unsigned bits) {
  bits %= 32; /* only have 32 bits */
  if (bits == 0) return;
  rotate_nibs(bits/4);
  rotate_bits(bits%4);
}

因此,要旋转13位,首先旋转3个半字节,然后旋转1位以获得总共13位旋转。

如果将半字节数组视为循环缓冲区,则可以完全避免半字节旋转。然后,半字节旋转只是改变0索引的数组中的起始位置。

如果你必须轮换,那可能会很棘手。如果您正在旋转一个8项阵列并且只想使用1项存储开销进行旋转,那么要旋转3个项目,您可能会这样做:

   orig: A B C D E F G H
 step 1: A B C A E F G H  rem: D
      2: A B C A E F D H  rem: G
      3: A G C A E F D H  rem: B
      4: A G C A B F D H  rem: E
      5: A G C A B F D E  rem: H
      6: A G H A B F D E  rem: C
      7: A G H A B C D E  rem: F
      8: F G H A B C D E  done

但是,如果您尝试使用相同的技术进行2,4或6项旋转,则循环不会遍历整个数组。因此,您必须知道旋转计数和数组大小是否具有公约数,并使算法考虑到该因子。如果您逐步完成6步旋转,则会有更多线索掉出来。

   orig: A B C D E F G H
                     A
                 G
             E
         C                 cycled back to A's position
                       B
                   H
               F
           D               done

请注意,GCD(6,8)为2,这意味着我们应该期望每次传递4次迭代。然后,N项数组的旋转算法可能如下所示:

rotate (n) {
  G = GCD(n, N)
  for (i = 0; i < G; ++i) {
    p = arr[i];
    for (j = 1; j < N/G; ++j) {
      swap(p, arr[(i + j*n) % N]);
    }
    arr[i] = p;
  }
}

你可以做一个优化来避免每次迭代的交换,我将把它留作练习。

答案 1 :(得分:2)

我建议为位旋转调用汇编语言函数。

许多汇编语言具有更好的通过进位和旋转进位来旋转位的工具。

很多时候汇编语言没有C或C ++函数那么复杂。

缺点是每个{different}平台需要一个每个汇编函数的实例。