Các phương pháp hay nhất cho hoạt động dịch chuyển (xoay) vòng tròn trong C ++


93

Các toán tử dịch chuyển trái và phải (<< và >>) đã có sẵn trong C ++. Tuy nhiên, tôi không thể tìm ra cách tôi có thể thực hiện các thao tác dịch chuyển tròn hoặc xoay.

Các thao tác như "Xoay trái" và "Xoay phải" có thể được thực hiện như thế nào?

Xoay phải hai lần tại đây

Initial --> 1000 0011 0100 0010

sẽ dẫn đến:

Final   --> 1010 0000 1101 0000

Một ví dụ sẽ hữu ích.

(lưu ý của người biên tập: Nhiều cách phổ biến để diễn đạt các vòng quay trong C mắc phải hành vi không xác định nếu số vòng quay bằng 0 hoặc biên dịch thành nhiều hơn chỉ một lệnh máy xoay. Câu trả lời của câu hỏi này nên ghi lại các phương pháp hay nhất.)



Câu trả lời:


104

Xem thêm phiên bản trước của câu trả lời này trên một câu hỏi xoay khác với một số chi tiết hơn về những gì asm gcc / clang sản xuất cho x86.

Cách thân thiện nhất với trình biên dịch để thể hiện sự xoay vòng trong C và C ++ mà tránh mọi Hành vi không xác định dường như là cách thực hiện của John Regehr . Tôi đã điều chỉnh nó để xoay theo chiều rộng của loại (sử dụng các loại có chiều rộng cố định như 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 ));
}

Hoạt động cho bất kỳ kiểu số nguyên không dấu nào, không chỉ uint32_t, vì vậy bạn có thể tạo phiên bản cho các kích thước khác.

Xem thêm phiên bản mẫu C ++ 11 với nhiều kiểm tra an toàn (bao gồm kiểm tra static_assertđộ rộng kiểu là lũy thừa của 2) , ví dụ như trường hợp này không xảy ra trên một số DSP 24 bit hoặc máy tính lớn 36 bit.

Tôi khuyên bạn chỉ nên sử dụng mẫu làm back-end cho các trình bao bọc có tên bao gồm chiều rộng xoay một cách rõ ràng. Quy tắc thăng hạng số nguyên có nghĩa là rotl_template(u16 & 0x11UL, 7)sẽ thực hiện xoay vòng 32 hoặc 64 bit, không phải 16 (tùy thuộc vào chiều rộng của unsigned long). Thậm chí uint16_t & uint16_tđược thăng hạng signed intbởi các quy tắc xúc tiến số nguyên của C ++, ngoại trừ trên các nền tảng intkhông rộng hơn uint16_t.


Trên x86 , phiên bản này liên kết với mộtrol r32, cl (hoặc rol r32, imm8) các trình biên dịch tìm kiếm nó, bởi vì trình biên dịch biết rằng các lệnh xoay và chuyển x86 che dấu số đếm theo cách giống như cách nguồn C.

Hỗ trợ trình biên dịch cho thành ngữ tránh UB này trên x86, cho uint32_t xunsigned int ncho sự thay đổi số lượng biến:

  • clang: được công nhận cho các vòng quay đếm biến kể từ clang3.5, nhiều ca + hoặc lót trước đó.
  • gcc: được công nhận cho các vòng quay số lượng biến kể từ gcc4.9 , nhiều ca + hoặc xen kẽ trước đó. gcc5 trở lên cũng tối ưu hóa nhánh và mặt nạ trong phiên bản wikipedia, chỉ sử dụng một rorhoặc rolhướng dẫn cho số lượng biến.
  • icc: được hỗ trợ cho các vòng quay đếm biến kể từ ICC13 trở về trước . Việc sử dụng luân phiên đếm hằng số shld edi,edi,7chậm hơn và chiếm nhiều byte hơn rol edi,7trên một số CPU (đặc biệt là AMD, nhưng cũng có thể là một số Intel), khi BMI2 không có sẵn rorx eax,edi,25để lưu MOV.
  • MSVC: x86-64 CL19: Chỉ được công nhận cho các vòng quay đếm không đổi. (Thành ngữ wikipedia được công nhận, nhưng nhánh và AND không được tối ưu hóa). Sử dụng _rotl/ _rotrnội dung từ <intrin.h>trên x86 (bao gồm cả x86-64).

gcc cho ARM sử dụng một and r1, r1, #31cho quay biến đếm, nhưng vẫn hiện xoay thực tế với một chỉ dẫn duy nhất : ror r0, r0, r1. Vì vậy, gcc không nhận ra rằng số lần xoay vốn là mô-đun. Như các tài liệu của ARM nói, "ROR với độ dài dịch chuyển n, hơn 32 bằng với ROR với độ dài dịch chuyển n-32" . Tôi nghĩ rằng gcc bị nhầm lẫn ở đây vì dịch chuyển trái / phải trên ARM bão hòa số lượng, do đó, sự thay đổi từ 32 trở lên sẽ xóa sổ đăng ký. (Không giống như x86, trong đó các ca shift che số đếm giống như số lần quay). Nó có thể quyết định rằng nó cần một lệnh AND trước khi nhận ra thành ngữ xoay, vì cách thức hoạt động của các dịch chuyển không tròn trên mục tiêu đó.

Các trình biên dịch x86 hiện tại vẫn sử dụng một lệnh bổ sung để che một số biến cho các vòng quay 8 và 16-bit, có thể vì lý do tương tự mà chúng không tránh AND trên ARM. Đây là một tối ưu hóa bị bỏ qua, vì hiệu suất không phụ thuộc vào số lần xoay trên bất kỳ CPU x86-64 nào. (Mặt nạ số đếm đã được giới thiệu với 286 vì lý do hiệu suất vì nó xử lý các thay đổi lặp đi lặp lại, không phải với độ trễ liên tục như các CPU hiện đại.)

BTW, thích xoay-phải cho các vòng quay đếm biến, để tránh làm cho trình biên dịch 32-nthực hiện xoay trái trên các kiến ​​trúc như ARM và MIPS chỉ cung cấp xoay-phải. (Điều này tối ưu hóa với số lượng hằng số thời gian biên dịch.)

Thực tế thú vị: ARM không thực sự có hướng dẫn thay đổi / xoay chuyên dụng, nó chỉ là MOV với toán hạng nguồn đi qua bộ chuyển dịch thùng ở chế độ ROR : mov r0, r0, ror r1. Vì vậy, một vòng xoay có thể gấp lại thành toán hạng nguồn đăng ký cho một lệnh EOR hoặc một cái gì đó.


Đảm bảo rằng bạn sử dụng các loại không dấu cho nvà giá trị trả về, nếu không nó sẽ không phải là một vòng xoay . (gcc cho các mục tiêu x86 dịch chuyển sang phải số học, dịch chuyển theo các bản sao của bit dấu thay vì số 0, dẫn đến sự cố khi bạn ORchuyển hai giá trị cùng nhau. Dịch sang phải của các số nguyên có dấu âm là hành vi được triển khai xác định trong C.)

Ngoài ra, hãy đảm bảo số lượng dịch chuyển là kiểu không dấu , vì (-n)&31với kiểu có dấu có thể là phần bù hoặc dấu / độ lớn của một người và không giống với mô-đun 2 ^ n mà bạn nhận được với phần bù không dấu hoặc hai. (Xem bình luận trên bài đăng trên blog của Regehr). unsigned inthoạt động tốt trên mọi trình biên dịch mà tôi đã xem xét, cho mọi chiều rộng của x. Một số kiểu khác thực sự đánh bại khả năng nhận dạng thành ngữ đối với một số trình biên dịch, vì vậy đừng chỉ sử dụng cùng kiểu như x.


Một số trình biên dịch cung cấp bản chất cho các vòng quay , điều này tốt hơn nhiều so với inline-asm nếu phiên bản di động không tạo mã tốt trên trình biên dịch mà bạn đang nhắm mục tiêu. Không có bản chất nền tảng đa nền tảng nào cho bất kỳ trình biên dịch nào mà tôi biết. Đây là một số tùy chọn x86:

  • Các tài liệu Intel <immintrin.h>cung cấp _rotl_rotl64nội dung , và tương tự cho sự thay đổi đúng đắn. MSVC yêu cầu<intrin.h> , trong khi gcc yêu cầu <x86intrin.h>. An #ifdefchăm sóc gcc so với icc, nhưng clang dường như không cung cấp chúng ở bất kỳ đâu, ngoại trừ trong chế độ tương thích với MSVC-fms-extensions -fms-compatibility -fms-compatibility-version=17.00 . Và asm mà nó phát ra đối với họ thật tệ (thêm mặt nạ và CMOV).
  • MSVC: _rotr8_rotr16 .
  • gcc và icc (không phải tiếng kêu): <x86intrin.h>cũng cung cấp __rolb/ __rorbcho 8-bit xoay trái / phải, __rolw/ __rorw(16-bit), __rold/ __rord(32-bit), __rolq/ __rorq(64-bit, chỉ được xác định cho các mục tiêu 64-bit). Đối với các vòng quay hẹp, việc triển khai sử dụng __builtin_ia32_rolhihoặc ...qi, nhưng các vòng quay 32 và 64 bit được xác định bằng cách sử dụng shift / hoặc (không có bảo vệ chống lại UB, vì mã trong ia32intrin.hchỉ phải hoạt động trên gcc cho x86). GNU C dường như không có bất kỳ __builtin_rotatechức năng đa nền tảng nào theo cách của nó __builtin_popcount(mở rộng đến bất kỳ chức năng tối ưu nào trên nền tảng đích, ngay cả khi nó không phải là một lệnh đơn lẻ). Hầu hết thời gian bạn nhận được mã tốt từ nhận dạng thành ngữ.

// 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

Có lẽ một số trình biên dịch không phải x86 cũng có bản chất, nhưng chúng ta đừng mở rộng câu trả lời wiki cộng đồng này để bao gồm tất cả chúng. (Có thể làm điều đó trong câu trả lời hiện có về bản chất ).


(Phiên bản cũ của câu trả lời này đã đề xuất asm nội tuyến cụ thể cho MSVC (chỉ hoạt động cho mã 32bit x86) hoặc http://www.devx.com/tips/Tip/14043 cho phiên bản C. Các nhận xét đang trả lời điều đó .)

Inline asm đánh bại nhiều tối ưu hóa , đặc biệt là kiểu MSVC vì nó buộc các đầu vào phải được lưu trữ / tải lại . Xoay nội tuyến GNU C được viết cẩn thận sẽ cho phép số đếm trở thành toán hạng ngay lập tức cho các số lượng dịch chuyển hằng thời gian biên dịch, nhưng nó vẫn không thể tối ưu hóa hoàn toàn nếu giá trị được dịch chuyển cũng là hằng số thời gian biên dịch sau khi nội tuyến. https://gcc.gnu.org/wiki/DontUseInlineAsm .


1
Tò mò, tại sao không bits = CHAR_BIT * sizeof(n);c &= bits - 1;return ((n >> c) | (n << (bits - c))), đó là những gì tôi muốn sử dụng?
mirabilos

@mirabilos: Phiên bản của bạn có UB với bit = 32, count = 32, trong sự thay đổi bằng bits - c= 32 - 0. (Tôi không nhận được ping từ điều này vì tôi chỉ chỉnh sửa wiki, không viết nó ngay từ đầu.)
Peter Cordes

@PeterCordes 0 < count < bitslà một yêu cầu liên tục của hầu hết các CPU và ngôn ngữ lập trình thực hiện xoay (đôi khi 0 ≤ count < bits, nhưng việc dịch chuyển theo lượng bit chính xác hầu như luôn không được xác định hoặc làm tròn xuống một điểm thay vì xóa giá trị và xoay.)
mirabilos

@mirabilos: Đúng vậy, nhưng mục tiêu của chúng tôi là viết một hàm cung cấp số lượng dịch chuyển trực tiếp cho một lệnh asm duy nhất, nhưng tránh UB ở mức C đối với bất kỳ số lượng dịch chuyển nào có thể xảy ra. Vì C không có toán tử hoặc hàm xoay, chúng tôi muốn tránh UB trong bất kỳ thành phần nào của thành ngữ này. Chúng tôi không muốn dựa vào trình biên dịch xử lý một dịch chuyển C theo cách giống như hướng dẫn dịch chuyển asm trên mục tiêu mà nó biên dịch. (Và BTW, ARM làm bằng không thanh ghi với số lượng thay đổi nhiều hơn chiều rộng thanh ghi, lấy số đếm từ byte dưới cùng của thanh ghi. Liên kết trong câu trả lời.)
Peter Cordes

1
Tôi đã định nói "chỉ sử dụng đoạn mã di động" nhưng sau đó tôi đã kiểm tra mã và có vẻ như (a) gọi UB cho số lần dịch bằng 0 và (b) chỉ sử dụng bản chất trên MSVC . Nói chung dù có đó là "mã tham chiếu" compilable cho những gì làm việc với tất cả các hacks cụ thể trình biên dịch và nền tảng có vẻ như là một ý tưởng tốt đẹp ...
BeeOnRope

33

Vì nó là C ++, hãy sử dụng một hàm nội tuyến:

template <typename INT> 
INT rol(INT val) {
    return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1));
}

Biến thể C ++ 11:

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));
}

5
Cảnh báo: Mã này bị hỏng nếu INTlà số nguyên có dấu và dấu được đặt! Kiểm tra ví dụ như rol<std::int32_t>(1 << 31)sẽ chuyển sang 1 nhưng thực sự trở thành -1(vì dấu hiệu được giữ lại).
Nobody

9
@Nobody: Tôi đã nhận xét cách đây 5 năm rằng bạn không nên sử dụng các kiểu số nguyên có dấu. Xoay vòng không có ý nghĩa đối với các loại số nguyên có dấu.
MSalters

2
Bạn có thể sử dụng std::numeric_limits<INT>::digitsthay thế CHAR_BIT * sizeof. Tôi quên nếu các loại không dấu được phép có phần đệm không sử dụng (ví dụ: số nguyên 24 bit được lưu trữ trong 32 bit), nhưng nếu vậy thì digitssẽ tốt hơn. Xem thêm gist.github.com/pabigot/7550454 để biết phiên bản có thêm kiểm tra cho sự thay đổi số lượng biến.
Peter Cordes

1
@PeterCordes: Đúng vậy. Tôi nghĩ rằng Cray đã làm (đã sử dụng thanh ghi dấu chấm động với phần đệm ở đó trường số mũ sẽ là).
MSalters

1
@ fake-name '> vì vậy, phiên bản C ++ 11 sẽ không hoạt động trên windows trừ khi bạn thay đổi điều đó thành thứ khác ...' Vâng, hãy thay đổi điều đó thành linux. :)
Slava

20

Hầu hết các trình biên dịch đều có bản chất cho điều đó. Visual Studio ví dụ _rotr8, _rotr16


ồ! cách dễ dàng hơn thì câu trả lời được chấp nhận. btw, đối với DWORD (32-bit) sử dụng _rotr và _rotl.
Gabe Halsmer

15

Chắc chắn là:

template<class T>
T ror(T x, unsigned int moves)
{
  return (x >> moves) | (x << sizeof(T)*8 - moves);
}

6
Đó có phải là 8lỗi chính tả của CHAR_BIT(không cần chính xác 8)?
Toby Speight

2
Vì đây là câu trả lời giống với câu trả lời của tôi (ngoại trừ việc hoán đổi từ phải sang trái), nên nhận xét của Peter Cordes về câu trả lời của tôi cũng áp dụng ở đây: sử dụng std::numeric_limits<T>::digits.
MSalters

14

C ++ 20 std::rotlstd::rotr

Nó có đến! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html và nên thêm nó vào <bit>tiêu đề.

cppreference nói rằng cách sử dụng sẽ như sau:

#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';
}

đưa ra đầu ra:

i          = 00011101
rotl(i,0)  = 00011101
rotl(i,1)  = 00111010
rotl(i,4)  = 11010001
rotl(i,9)  = 00111010
rotl(i,-1) = 10001110

Tôi sẽ dùng thử khi hỗ trợ đến GCC, GCC 9.1.0 với g++-9 -std=c++2a vẫn không hỗ trợ.

Đề xuất nói:

Tiêu đề:

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;

và:

25.5.5 Xoay [bitops.rot]

Trong các mô tả sau đây, chúng ta hãy N biểu thị std::numeric_limits<T>::digits.

template<class T>
  [[nodiscard]] constexpr T rotl(T x, int s) noexcept;

Ràng buộc: T là kiểu số nguyên không dấu (3.9.1 [basic.fundaries]).

Gọi r là s% N.

Trả về: Nếu r là 0, x; nếu r là số dương , (x << r) | (x >> (N - r)); nếu r là âm rotr(x, -r),.

template<class T>
  [[nodiscard]] constexpr T rotr(T x, int s) noexcept;

Ràng buộc: T là kiểu số nguyên không dấu (3.9.1 [basic.fundaries]). Gọi r là s% N.

Trả về: Nếu r là 0, x; nếu r là số dương , (x >> r) | (x << (N - r)); nếu r là âm rotl(x, -r),.

A std::popcountcũng được thêm vào để đếm số lượng bit 1: Làm thế nào để đếm số lượng bit thiết lập trong số nguyên 32 bit?


Làm thế nào mà các phép quay bit lại mất nhiều thời gian như vậy để hạ cánh trong c ++ hiện đại? Ngay cả trong LLVM clang, chỉ mới có bản chất cách đây vài năm => reviews.llvm.org/D21457 Tôi nghĩ ARM đã có cách xoay vòng trước năm 2010, vì vậy đáng lẽ họ phải có từ c ++ 11.
sandthorn

7

Làm thế nào abt một cái gì đó như thế này, sử dụng bộ bit tiêu chuẩn ...

#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,


Cần sửa đổi nó để giải thích cho các dịch chuyển lớn hơn độ dài của tập bit.
H. Green

Đã thêm vào m %= N;tài khoản cho ca làm việc >= N.
Milania

7

Nếu x là giá trị 8 bit, bạn có thể sử dụng:

x=(x>>1 | x<<7);

2
Có thể sẽ hoạt động sai nếu xđược ký.
sam hocevar

6

Về chi tiết, bạn có thể áp dụng logic sau.

Nếu mẫu bit là 33602 trong số nguyên

1000 0011 0100 0010

và bạn cần Roll over với 2 shifs bên phải sau đó: đầu tiên tạo một bản sao của mẫu bit và sau đó dịch sang trái nó: Length - RightShift tức là chiều dài là 16 giá trị dịch phải là 2 16 - 2 = 14

Sau 14 lần dịch chuyển sang trái bạn nhận được.

1000 0000 0000 0000

Bây giờ chuyển sang phải giá trị 33602, 2 lần theo yêu cầu. Bạn lấy

0010 0000 1101 0000

Bây giờ lấy OR giữa giá trị dịch sang trái 14 lần và giá trị dịch sang phải 2 lần.

1000 0000 0000 0000
0010 0000 1101 0000
===================
1010 0000 1101 0000
===================

Và bạn nhận được giá trị di chuyển đã thay đổi của mình. Hãy nhớ rằng các thao tác khôn ngoan nhanh hơn và điều này thậm chí không yêu cầu bất kỳ vòng lặp nào.


1
Tương tự với các chương trình con trên ... b = b << m | b >> (Nm);
SM Kamran

Đó không phải là XOR, không phải HOẶC? 1 ^ 0 = 1, 0 ^ 0 = 0, v.v. Nếu nó HOẶC thì nó không phải là độc quyền, do đó nó sẽ luôn là 1
BK

5

Giả sử bạn muốn dịch sang phải theo Ltừng bit và đầu vào xlà một số có Ncác bit:

unsigned ror(unsigned x, int L, int N) 
{
    unsigned lsbs = x & ((1 << L) - 1);
    return (x >> L) | (lsbs << (N-L));
}

3

Câu trả lời đúng là sau:

#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 ) ) ) )

Có thể sẽ hoạt động sai nếu valđược ký.
sam hocevar

0

Mã nguồn x số bit

int x =8;
data =15; //input
unsigned char tmp;
for(int i =0;i<x;i++)
{
printf("Data & 1    %d\n",data&1);
printf("Data Shifted value %d\n",data>>1^(data&1)<<(x-1));
tmp = data>>1|(data&1)<<(x-1);
data = tmp;  
}

0

một gợi ý khác

template<class T>
inline T rotl(T x, unsigned char moves){
    unsigned char temp;
    __asm{
        mov temp, CL
        mov CL, moves
        rol x, CL
        mov CL, temp
    };
    return x;
}

0

Dưới đây là phiên bản cải tiến một chút về câu trả lời của Dídac Pérez , với cả hai hướng được triển khai, cùng với bản trình diễn cách sử dụng của các hàm này bằng cách sử dụng ký tự không dấu và giá trị dài dài không dấu. Một số lưu ý:

  1. Các chức năng được đưa ra để tối ưu hóa trình biên dịch
  2. Tôi đã sử dụng một cout << +valuethủ thuật để xuất ra một ký tự số không dấu mà tôi tìm thấy ở đây: https://stackoverflow.com/a/28414758/1599699
  3. Tôi khuyên bạn nên sử dụng <put the type here>cú pháp rõ ràng để rõ ràng và an toàn.
  4. Tôi đã sử dụng char unsigned cho tham số shiftNum vì những gì tôi tìm thấy trong phần Chi tiết bổ sung ở đây :

Kết quả của một phép toán shift là không xác định nếu biểu thức cộng là âm hoặc nếu biểu thức cộng lớn hơn hoặc bằng số bit trong biểu thức dịch chuyển (được thăng cấp) .

Đây là mã tôi đang sử dụng:

#include <iostream>

using namespace std;

template <typename T>
inline T rotateAndCarryLeft(T rotateMe, unsigned char shiftNum)
{
    static const unsigned char TBitCount = sizeof(T) * 8U;

    return (rotateMe << shiftNum) | (rotateMe >> (TBitCount - shiftNum));
}

template <typename T>
inline T rotateAndCarryRight(T rotateMe, unsigned char shiftNum)
{
    static const unsigned char TBitCount = sizeof(T) * 8U;

    return (rotateMe >> shiftNum) | (rotateMe << (TBitCount - shiftNum));
}

void main()
{
    //00010100 == (unsigned char)20U
    //00000101 == (unsigned char)5U == rotateAndCarryLeft(20U, 6U)
    //01010000 == (unsigned char)80U == rotateAndCarryRight(20U, 6U)

    cout << "unsigned char " << 20U << " rotated left by 6 bits == " << +rotateAndCarryLeft<unsigned char>(20U, 6U) << "\n";
    cout << "unsigned char " << 20U << " rotated right by 6 bits == " << +rotateAndCarryRight<unsigned char>(20U, 6U) << "\n";

    cout << "\n";


    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum)
    {
        cout << "unsigned char " << 21U << " rotated left by " << +shiftNum << " bit(s) == " << +rotateAndCarryLeft<unsigned char>(21U, shiftNum) << "\n";
    }

    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum)
    {
        cout << "unsigned char " << 21U << " rotated right by " << +shiftNum << " bit(s) == " << +rotateAndCarryRight<unsigned char>(21U, shiftNum) << "\n";
    }


    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum)
    {
        cout << "unsigned long long " << 3457347ULL << " rotated left by " << +shiftNum << " bit(s) == " << rotateAndCarryLeft<unsigned long long>(3457347ULL, shiftNum) << "\n";
    }

    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum)
    {
        cout << "unsigned long long " << 3457347ULL << " rotated right by " << +shiftNum << " bit(s) == " << rotateAndCarryRight<unsigned long long>(3457347ULL, shiftNum) << "\n";
    }

    cout << "\n\n";
    system("pause");
}

0
--- Substituting RLC in 8051 C for speed --- Rotate left carry
Here is an example using RLC to update a serial 8 bit DAC msb first:
                               (r=DACVAL, P1.4= SDO, P1.5= SCLK)
MOV     A, r
?1:
MOV     B, #8
RLC     A
MOV     P1.4, C
CLR     P1.5
SETB    P1.5
DJNZ    B, ?1

Here is the code in 8051 C at its fastest:
sbit ACC_7  = ACC ^ 7 ; //define this at the top to access bit 7 of ACC
ACC     =   r;
B       =   8;  
do  {
P1_4    =   ACC_7;  // this assembles into mov c, acc.7  mov P1.4, c 
ACC     <<= 1;
P1_5    =   0;
P1_5    =   1;
B       --  ; 
    } while ( B!=0 );
The keil compiler will use DJNZ when a loop is written this way.
I am cheating here by using registers ACC and B in c code.
If you cannot cheat then substitute with:
P1_4    =   ( r & 128 ) ? 1 : 0 ;
r     <<=   1;
This only takes a few extra instructions.
Also, changing B for a local var char n is the same.
Keil does rotate ACC left by ADD A, ACC which is the same as multiply 2.
It only takes one extra opcode i think.
Keeping code entirely in C keeps things simpler sometimes.

-1

Quá tải một chức năng:

unsigned int rotate_right(unsigned int x)
{
 return (x>>1 | (x&1?0x80000000:0))
}

unsigned short rotate_right(unsigned short x) { /* etc. */ }

-1
#define ROTATE_RIGHT(x) ( (x>>1) | (x&1?0x8000:0) )

bạn nên đặt x vào trong dấu ngoặc đơn để tránh những bất ngờ khó chịu với các biểu thức làm đối số cho macro.
Joey

3
Nếu giá trị không phải là 16-bit, bạn có âm thầm lấy vô nghĩa
James Hopkin

Nếu xác định nó là một macro, thì người ta cũng cần phải cẩn thận để tránh chuyển một biểu thức với các hiệu ứng phụ làm đối số.
Phil Miller
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.