C++中已经提供了左移和右移运算符(<<和>>)。但是我找不到如何执行循环移位或旋转操作。
如何执行"向左旋转"和"向右旋转"等操作?
这里旋转右两次
Initial --> 1000 0011 0100 0010
应该产生以下结果:
Final --> 1010 0000 1101 0000
能举个例子会更有帮助。
(编辑注:在C语言中,许多常见的旋转表达方式如果旋转次数为零则存在未定义的行为,或者编译成不止一条旋转的机器指令。这个问题的答案应该记录最佳实践。)
C++中已经提供了左移和右移运算符(<<和>>)。但是我找不到如何执行循环移位或旋转操作。
如何执行"向左旋转"和"向右旋转"等操作?
这里旋转右两次
Initial --> 1000 0011 0100 0010
应该产生以下结果:
Final --> 1010 0000 1101 0000
能举个例子会更有帮助。
(编辑注:在C语言中,许多常见的旋转表达方式如果旋转次数为零则存在未定义的行为,或者编译成不止一条旋转的机器指令。这个问题的答案应该记录最佳实践。)
uint32_t
)。#include <stdint.h> // for uint32_t
#include <limits.h> // for CHAR_BIT
// #define NDEBUG
#include <assert.h>
static inline uint32_t rotl32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1); // assumes width is a power of 2.
// assert ( (c<=mask) &&"rotate by type width or more");
c &= mask;
return (n<<c) | (n>>( (-c)&mask ));
}
static inline uint32_t rotr32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);
// assert ( (c<=mask) &&"rotate by type width or more");
c &= mask;
return (n>>c) | (n<<( (-c)&mask ));
}
On x86, this version inlines to a single rol r32, cl
(or rol r32, imm8
) with compilers that grok it, because the compiler knows that x86 rotate and shift instructions mask the shift-count the same way the C source does.
uint32_t x
和unsigned int n
。
ror
或rol
指令进行可变计数。shld edi,edi,7
,在某些CPU上(尤其是AMD,但也包括一些Intel)比rol edi,7
更慢且占用更多字节,当无法使用rorx eax,edi,25
来保存MOV时。<intrin.h>
中的_rotl
/ _rotr
内嵌函数。and r1, r1, #31
,但仍然通过一条指令执行实际旋转:ror r0, r0, r1
。因此,gcc没有意识到旋转计数本质上是模数运算。正如ARM文档所说,"带有移位长度n
大于32的ROR与带有移位长度n-32
的ROR相同"。我认为gcc在这里会感到困惑,因为ARM上的左/右移操作会饱和计数,所以移位32或更多会清除寄存器。(与x86不同,x86的移位与旋转一样,会屏蔽计数)。它可能会在识别旋转习惯用法之前决定需要一个AND指令,因为非循环移位在该目标上的工作方式不同。<immintrin.h>
提供了_rotl
和_rotl64
内嵌函数,右移同理。MSVC需要<intrin.h>
,而gcc需要<x86intrin.h>
。通过#ifdef
来处理gcc与icc的问题。Clang 9.0也有这些函数,但在此之前似乎没有提供,除非使用MSVC兼容模式,使用-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
。而且它们生成的汇编代码很糟糕(额外的掩码和一个CMOV指令)。_rotr8
和_rotr16
。<x86intrin.h>
还提供了__rolb
/__rorb
用于8位左/右旋转,__rolw
/__rorw
(16位),__rold
/__rord
(32位),__rolq
/__rorq
(64位,仅对64位目标定义)。对于窄旋转,实现使用__builtin_ia32_rolhi
或...qi
,但32位和64位旋转使用移位/或运算(没有保护不确定行为,因为ia32intrin.h
中的代码只需要在gcc上针对x86工作)。GNU C似乎没有任何跨平台的__builtin_rotate
函数,就像它对__builtin_popcount
所做的那样(它会展开为目标平台上最优的代码,即使它不是一个单一指令)。大多数情况下,通过惯用识别可以得到良好的代码。__builtin_rotateleft8
,__builtin_rotateright8
,以及其他宽度的类似函数。请参阅Clang的语言扩展文档。还有其他位操作的内建函数/内嵌函数,例如__builtin_bitreverse32
(在ARM / AArch64上可以编译为rbit
)。您可以使用__has_builtin
进行测试。// For real use, probably use a rotate intrinsic for MSVC, or this idiom for other compilers. This pattern of #ifdefs may be helpful
#if defined(__x86_64__) || defined(__i386__)
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h> // Not just <immintrin.h> for compilers other than icc
#endif
uint32_t rotl32_x86_intrinsic(rotwidth_t x, unsigned n) {
//return __builtin_ia32_rorhi(x, 7); // 16-bit rotate, GNU C
return _rotl(x, n); // gcc, icc, msvc. Intel-defined.
//return __rold(x, n); // gcc, icc.
// can't find anything for clang
}
#endif
bits = CHAR_BIT * sizeof(n);
和 c &= bits - 1;
以及 return ((n >> c) | (n << (bits - c)))
,这是我会用的方法? - mirabilosbits - c
= 32 - 0
进行移位时,当bits=32
且count=32
时存在未定义行为。 (我没有收到来自此消息的提醒,因为我只编辑了维基百科,而不是最初的编写者。) - Peter Cordes0
产生x << 32
来让恶魔从你的鼻子里飞出来,那么它们是允许的。C确实说这是未定义行为,而不仅仅是实现定义的结果值或其他什么。 - Peter CordesC++20 std::rotl
和std::rotr
它来了!http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html,应该将其添加到<bit>
头文件中。
cppreference网站表示使用方法如下:
#include <bit>
#include <bitset>
#include <cstdint>
#include <iostream>
int main()
{
std::uint8_t i = 0b00011101;
std::cout << "i = " << std::bitset<8>(i) << '\n';
std::cout << "rotl(i,0) = " << std::bitset<8>(std::rotl(i,0)) << '\n';
std::cout << "rotl(i,1) = " << std::bitset<8>(std::rotl(i,1)) << '\n';
std::cout << "rotl(i,4) = " << std::bitset<8>(std::rotl(i,4)) << '\n';
std::cout << "rotl(i,9) = " << std::bitset<8>(std::rotl(i,9)) << '\n';
std::cout << "rotl(i,-1) = " << std::bitset<8>(std::rotl(i,-1)) << '\n';
}
i = 00011101
rotl(i,0) = 00011101
rotl(i,1) = 00111010
rotl(i,4) = 11010001
rotl(i,9) = 00111010
rotl(i,-1) = 10001110
当GCC支持时,我会尝试一下,目前GCC 9.1.0与g++-9 -std=c++2a
仍不支持。
提案指出:
Header:
namespace std { // 25.5.5, rotating template<class T> [[nodiscard]] constexpr T rotl(T x, int s) noexcept; template<class T> [[nodiscard]] constexpr T rotr(T x, int s) noexcept;
并且:
25.5.5 Rotating [bitops.rot]
In the following descriptions, let N denote
std::numeric_limits<T>::digits
.
template<class T> [[nodiscard]] constexpr T rotl(T x, int s) noexcept;
Constraints: T is an unsigned integer type (3.9.1 [basic.fundamental]).
Let r be s % N.
Returns: If r is 0, x; if r is positive,
(x << r) | (x >> (N - r))
; if r is negative,rotr(x, -r)
.
template<class T> [[nodiscard]] constexpr T rotr(T x, int s) noexcept;
Constraints: T is an unsigned integer type (3.9.1 [basic.fundamental]). Let r be s % N.
Returns: If r is 0, x; if r is positive,
(x >> r) | (x << (N - r))
; if r is negative,rotl(x, -r)
.
还添加了std::popcount
以计算1位的数量:如何计算32位整数中设置位的数量?
(x >> r) | (x << (N-r))
)并将其编译为旋转指令的想法非常乐观,因此基本语言不需要包括popcount或rotate。当然,考虑到这种态度,我不知道为什么他们会在整数中包括一个*
乘法运算符,当你可以在任何地方编写移位加循环时。看起来C++20是某个人终于讲了一些有意义的话,并添加了大多数CPU都具有的东西,例如popcnt和bit-scan;易于模拟。 - Peter Cordes由于这是C++,所以请使用内联函数:
template <typename INT>
INT rol(INT val) {
return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1));
}
使用C++11的variant类型:
template <typename INT>
constexpr INT rol(INT val) {
static_assert(std::is_unsigned<INT>::value,
"Rotate Left only makes sense for unsigned types");
return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1));
}
INT
是有符号整数并且符号被设置,则此代码将出现错误!例如测试rol<std::int32_t>(1 << 31)
应该翻转为1,但实际上变成了-1
(因为保留了符号)。 - Nobody moving away from SEstd::numeric_limits<INT>::digits
代替 CHAR_BIT * sizeof
。我忘记了无符号类型是否允许有未使用的填充(例如在32位中存储的24位整数),但如果是这样,那么 digits
将更好。另请参见 https://gist.github.com/pabigot/7550454,其中包含更多检查变量计数移位的版本。 - Peter Cordes大多数编译器都有内置函数来实现这个操作。 例如,Visual Studio中有 _rotr8、_rotr16 等。
明确地说:
template<class T>
T ror(T x, unsigned int moves)
{
return (x >> moves) | (x << sizeof(T)*8 - moves);
}
8
是否拼错了,实际上是指 CHAR_BIT
(并非一定为 8)? - Toby Speightstd::numeric_limits<T>::digits
。 - MSaltersx=(x>>1 | x<<7);
x
是有符号的,可能会出现错误行为。 - sam hocevar#include <bitset>
#include <iostream>
template <std::size_t N>
inline void
rotate(std::bitset<N>& b, unsigned m)
{
b = b << m | b >> (N-m);
}
int main()
{
std::bitset<8> b(15);
std::cout << b << '\n';
rotate(b, 2);
std::cout << b << '\n';
return 0;
}
HTH,
m %= N;
来处理位移 >= N
的情况。 - Milania以下是详细步骤:
如果整数中的位模式为33602
1000 0011 0100 0010
并且您需要向右移2位:
首先复制位模式,然后左移:长度-右移值左移14次后,您将获得:
1000 0000 0000 0000
现在按要求将值33602向右移动2次。
您会得到:0010 0000 1101 0000
现在在14次左移值和2次右移值之间取一个OR(或)。
1000 0000 0000 0000 0010 0000 1101 0000 =================== 1010 0000 1101 0000 ===================
您就得到了移位后的值。请记住,位运算速度更快,这甚至不需要任何循环。
L
位向右移动,并且输入的x
是具有N
位的数字:unsigned ror(unsigned x, int L, int N)
{
unsigned lsbs = x & ((1 << L) - 1);
return (x >> L) | (lsbs << (N-L));
}
#define BitsCount( val ) ( sizeof( val ) * CHAR_BIT )
#define Shift( val, steps ) ( steps % BitsCount( val ) )
#define ROL( val, steps ) ( ( val << Shift( val, steps ) ) | ( val >> ( BitsCount( val ) - Shift( val, steps ) ) ) )
#define ROR( val, steps ) ( ( val >> Shift( val, steps ) ) | ( val << ( BitsCount( val ) - Shift( val, steps ) ) ) )
val
是有符号的,可能会出现问题。 - sam hocevar