แนวทางปฏิบัติที่ดีที่สุดสำหรับการดำเนินการกะวงกลม (หมุน) ใน C ++


96

ตัวดำเนินการกะซ้ายและขวา (<< และ >>) มีอยู่แล้วใน C ++ อย่างไรก็ตามฉันไม่พบว่าฉันสามารถดำเนินการกะแบบวงกลมหรือหมุนได้อย่างไร

สามารถดำเนินการเช่น "หมุนซ้าย" และ "หมุนขวา" ได้อย่างไร?

หมุนขวาสองครั้งที่นี่

Initial --> 1000 0011 0100 0010

ควรส่งผลให้:

Final   --> 1010 0000 1101 0000

ตัวอย่างจะเป็นประโยชน์

(หมายเหตุบรรณาธิการ: วิธีทั่วไปหลายอย่างในการแสดงการหมุนใน C ต้องทนทุกข์ทรมานจากพฤติกรรมที่ไม่ได้กำหนดหากจำนวนการหมุนเป็นศูนย์หรือรวบรวมมากกว่าคำสั่งเครื่องหมุนเพียงครั้งเดียวคำตอบของคำถามนี้ควรบันทึกแนวทางปฏิบัติที่ดีที่สุด)



คำตอบ:


106

ดูคำตอบนี้ในเวอร์ชันก่อนหน้านี้ในคำถามหมุนเวียนอื่นพร้อมรายละเอียดเพิ่มเติมเกี่ยวกับสิ่งที่ asm gcc / clang ผลิตขึ้นสำหรับ x86

วิธีคอมไพเลอร์ที่เหมาะที่สุดที่จะแสดงหมุนใน C และ 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 ));
}

ใช้ได้กับประเภทจำนวนเต็มที่ไม่ได้ลงนามไม่ใช่เพียงแค่uint32_tคุณสามารถสร้างเวอร์ชันสำหรับขนาดอื่น ๆ ได้

ดูเวอร์ชันเทมเพลต C ++ 11 ที่มีการตรวจสอบความปลอดภัยมากมาย (รวมถึงstatic_assertความกว้างของประเภทเป็น 2)ซึ่งไม่ใช่กรณีของ DSP แบบ 24 บิตหรือเมนเฟรม 36 บิตบางตัวเช่น

ฉันขอแนะนำให้ใช้เทมเพลตเป็นส่วนหลังสำหรับ Wrapper ที่มีชื่อที่มีความกว้างการหมุนอย่างชัดเจน กฎการส่งเสริมจำนวนเต็มหมายถึงการrotl_template(u16 & 0x11UL, 7)หมุน 32 หรือ 64 บิตไม่ใช่ 16 (ขึ้นอยู่กับความกว้างของunsigned long) แม้uint16_t & uint16_tจะเลื่อนตำแหน่งให้เป็นsigned intตามกฎจำนวนเต็มโปรโมชั่น C ++ 's ยกเว้นบนแพลตฟอร์มที่ไม่มีที่กว้างกว่าintuint16_t


บน x86เวอร์ชันนี้สอดแทรกไปยังrol r32, clrol r32, imm8คอมไพเลอร์เดียว (หรือ) ที่มีคอมไพเลอร์ที่ส่งเสียงดังเนื่องจากคอมไพเลอร์รู้ว่าx86 คำแนะนำในการหมุนและเปลี่ยนจะปิดบังจำนวนกะแบบเดียวกับที่แหล่ง C

การสนับสนุนคอมไพเลอร์สำหรับสำนวนการหลีกเลี่ยง UB บน x86 สำหรับuint32_t xและunsigned int nสำหรับการเปลี่ยนแปลงจำนวนตัวแปร:

  • เสียงดัง: ได้รับการยอมรับสำหรับการนับตัวแปรที่หมุนตั้งแต่ clang3.5, หลายกะ + หรือ insns ก่อนหน้านั้น
  • gcc: ได้รับการยอมรับสำหรับการนับตัวแปรหมุนตั้งแต่ gcc4.9หลายกะ + หรือ insns ก่อนหน้านั้น gcc5 และเพิ่มประสิทธิภาพสาขาและมาสก์ในเวอร์ชันวิกิพีเดียในภายหลังเช่นกันโดยใช้เพียงคำสั่งrorหรือrolคำสั่งสำหรับการนับตัวแปร
  • ICC: การสนับสนุนสำหรับการหมุนตัวแปรนับตั้งแต่ ICC13 หรือก่อนหน้านั้น การนับค่าคงที่จะหมุนการใช้งานshld edi,edi,7ซึ่งช้ากว่าและใช้ไบต์มากกว่าrol edi,7ซีพียูบางตัว (โดยเฉพาะ AMD แต่ยังรวมถึง Intel บางตัวด้วย) เมื่อ BMI2 ไม่สามารถrorx eax,edi,25บันทึก MOV ได้
  • MSVC: x86-64 CL19: ได้รับการยอมรับสำหรับการหมุนจำนวนคงที่เท่านั้น (สำนวนวิกิพีเดียเป็นที่รู้จัก แต่สาขาและ AND ไม่ได้รับการปรับให้เหมาะสมที่สุด) ใช้_rotl/ _rotrintrinsics จาก<intrin.h>บน x86 (รวม x86-64)

GCC สำหรับ ARM ใช้and r1, r1, #31สำหรับหมุนตัวแปรนับ แต่ยังคงไม่หมุนที่เกิดขึ้นจริงกับคำสั่งเดียวror r0, r0, r1 : ดังนั้น gcc จึงไม่ทราบว่าจำนวนการหมุนนั้นเป็นแบบแยกส่วนโดยเนื้อแท้ ในฐานะที่เป็นเอกสาร ARM กล่าวว่า"มีความยาว ROR กะnมากกว่า 32 เป็นเช่นเดียวกับที่มีความยาว ROR กะn-32 " ฉันคิดว่า gcc สับสนที่นี่เพราะการเลื่อนซ้าย / ขวาบน ARM ทำให้การนับอิ่มตัวดังนั้นการเลื่อน 32 ขึ้นไปจะล้างการลงทะเบียน (ไม่เหมือน x86 ที่กะจะปกปิดการนับเหมือนกับการหมุน) มันอาจจะตัดสินใจว่ามันต้องการคำสั่ง AND ก่อนที่จะรับรู้สำนวนการหมุนเนื่องจากการกะแบบไม่เป็นวงกลมทำงานกับเป้าหมายนั้นอย่างไร

คอมไพเลอร์ x86 ปัจจุบันยังคงใช้คำสั่งพิเศษเพื่อปกปิดจำนวนตัวแปรสำหรับการหมุน 8 และ 16 บิตอาจเป็นเพราะเหตุผลเดียวกับที่พวกเขาไม่หลีกเลี่ยง AND บน ARM นี่เป็นการเพิ่มประสิทธิภาพที่ไม่ได้รับเนื่องจากประสิทธิภาพไม่ได้ขึ้นอยู่กับจำนวนการหมุนของ CPU x86-64 ใด ๆ (การกำบังการนับถูกนำมาใช้ด้วย 286 ด้วยเหตุผลด้านประสิทธิภาพเนื่องจากมีการจัดการกะซ้ำ ๆ ไม่ใช่ด้วยความหน่วงคงที่เหมือนซีพียูสมัยใหม่)

BTW ชอบการหมุนขวาสำหรับการหมุนจำนวนตัวแปรเพื่อหลีกเลี่ยงการทำให้คอมไพเลอร์ทำ32-nเพื่อใช้การหมุนซ้ายบนสถาปัตยกรรมเช่น ARM และ MIPS ที่ให้เฉพาะการหมุนขวาเท่านั้น (สิ่งนี้จะเพิ่มประสิทธิภาพด้วยการนับค่าคงที่เวลาคอมไพล์)

สนุกจริง: ARM ไม่ได้จริงๆต้องทุ่มเทกะ / คำแนะนำหมุนก็เพียง MOV กับแหล่งที่มาถูกดำเนินการจะผ่านบาร์เรลจำแลงในโหมด RORmov r0, r0, ror r1 : ดังนั้นการหมุนสามารถพับเป็นตัวดำเนินการรีจิสเตอร์ซอร์สสำหรับคำสั่ง EOR หรืออะไรก็ได้


ให้แน่ใจว่าคุณใช้ชนิดที่ได้รับการรับรองสำหรับการnและค่าตอบแทนหรืออื่น ๆ มันจะไม่หมุน (เป้าหมาย gcc สำหรับ x86 จะเปลี่ยนขวาทางเลขคณิตโดยเปลี่ยนสำเนาของบิตเครื่องหมายแทนที่จะเป็นศูนย์ซึ่งนำไปสู่ปัญหาเมื่อคุณORทั้งสองค่าที่เลื่อนเข้าด้วยกันการเลื่อนทางขวาของจำนวนเต็มที่ลงชื่อเชิงลบเป็นพฤติกรรมที่กำหนดไว้ใน C)

นอกจากนี้ตรวจสอบให้แน่ใจว่าจำนวนกะเป็นประเภทที่ไม่ได้ลงชื่อเนื่องจากประเภทที่(-n)&31มีการลงนามอาจเป็นส่วนเติมเต็มหรือเครื่องหมาย / ขนาดและไม่เหมือนกับโมดูลาร์ 2 ^ n ที่คุณได้รับจากส่วนเสริมที่ไม่ได้ลงนามหรือสอง (ดูความคิดเห็นในบล็อกโพสต์ของ Regehr) unsigned intทำได้ดีกับทุกคอมไพเลอร์ที่ฉันดูสำหรับทุกความกว้างของxไฟล์. ประเภทอื่น ๆ บางประเภทเอาชนะการจดจำสำนวนสำหรับคอมไพเลอร์บางตัวดังนั้นอย่าใช้ประเภทเดียวกับx.


คอมไพเลอร์บางตัวมีอินทรินซิคสำหรับการหมุนซึ่งดีกว่าอินไลน์ - asm มากหากเวอร์ชันพกพาไม่ได้สร้างโค้ดที่ดีบนคอมไพเลอร์ที่คุณกำหนดเป้าหมาย ไม่มีอินทรินข้ามแพลตฟอร์มสำหรับคอมไพเลอร์ใด ๆ ที่ฉันรู้จัก นี่คือบางส่วนของตัวเลือก x86:

  • เอกสารของ Intel ที่<immintrin.h>จัดเตรียม_rotlและ_rotl64เนื้อแท้และเหมือนกันสำหรับการเปลี่ยนที่ถูกต้อง MSVC ต้อง<intrin.h>ในขณะที่ GCC <x86intrin.h>ต้อง #ifdefดูแล gcc กับ ICC แต่เสียงดังกราวดูเหมือนจะไม่ให้พวกเขาได้ทุกที่ยกเว้นในโหมดที่เข้ากันกับ MSVC -fms-extensions -fms-compatibility -fms-compatibility-version=17.00และ asm ที่มันปล่อยออกมามันห่วย (การกำบังพิเศษและ CMOV)
  • MSVC: _rotr8และ_rotr16 .
  • gcc และ icc (ไม่ใช่เสียงดัง): <x86intrin.h>ยังมี__rolb/ __rorbสำหรับการหมุน 8 บิตไปทางซ้าย / ขวา, __rolw/ __rorw(16 บิต), __rold/ __rord(32 บิต), __rolq/ __rorq(64 บิตกำหนดไว้สำหรับเป้าหมาย 64 บิตเท่านั้น) สำหรับการหมุนแบบแคบการใช้งานจะใช้__builtin_ia32_rolhiหรือ...qiแต่การหมุน 32 และ 64 บิตถูกกำหนดโดยใช้ shift / หรือ (โดยไม่มีการป้องกัน UB เนื่องจากโค้ดในia32intrin.hต้องทำงานบน gcc สำหรับ x86 เท่านั้น) GNU C ดูเหมือนจะไม่มี__builtin_rotateฟังก์ชั่นข้ามแพลตฟอร์มอย่างที่มันทำ__builtin_popcount(ซึ่งขยายไปสู่สิ่งที่เหมาะสมที่สุดบนแพลตฟอร์มเป้าหมายแม้ว่าจะไม่ใช่คำสั่งเดียวก็ตาม) เวลาส่วนใหญ่คุณจะได้รหัสที่ดีจากการจดจำสำนวน

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

สันนิษฐานว่าคอมไพเลอร์ที่ไม่ใช่ x86 บางตัวมีอยู่ภายในด้วยเช่นกัน แต่อย่าขยายคอมมิวนิตี้วิกิคำตอบนี้เพื่อรวมไว้ (อาจทำเช่นนั้นในคำตอบที่มีอยู่เกี่ยวกับเนื้อแท้ )


(คำตอบรุ่นเก่าแนะนำ MSVC-specific inline asm (ซึ่งใช้ได้กับโค้ด 32 บิต x86 เท่านั้น) หรือhttp://www.devx.com/tips/Tip/14043สำหรับเวอร์ชัน C ความคิดเห็นกำลังตอบกลับไปว่า .)

asm Inline เอาชนะการเพิ่มประสิทธิภาพจำนวนมาก , โดยเฉพาะอย่างยิ่ง MSVC สไตล์เพราะมันบังคับให้ปัจจัยการผลิตจะถูกเก็บไว้ การหมุนแบบอินไลน์ - asm ของ GNU C ที่เขียนอย่างระมัดระวังจะช่วยให้การนับเป็นตัวดำเนินการในทันทีสำหรับการนับจำนวนกะค่าคงที่ของเวลาคอมไพล์ แต่ก็ยังไม่สามารถปรับให้เหมาะสมได้ทั้งหมดหากค่าที่จะเปลี่ยนเป็นค่าคงที่เวลาคอมไพล์ด้วย หลังจากซับใน https://gcc.gnu.org/wiki/DontUseInlineAsm


1
อยากรู้อยากเห็นทำไมไม่bits = CHAR_BIT * sizeof(n);และc &= bits - 1;และreturn ((n >> c) | (n << (bits - c)))ซึ่งเป็นสิ่งที่ฉันต้องการใช้?
mirabilos

@mirabilos: รุ่นของคุณมี UB กับบิต = 32 นับ = 32 ในการเปลี่ยนแปลงโดย=bits - c 32 - 0(ฉันไม่ได้รับ ping จากสิ่งนี้เพราะฉันแก้ไขเพียงวิกิเท่านั้นไม่ได้เขียนไว้ตั้งแต่แรก)
Peter Cordes

@PeterCordes 0 < count < bitsเป็นข้อกำหนดที่คงที่ของซีพียูและภาษาโปรแกรมเกือบทั้งหมดที่ใช้การหมุน (บางครั้ง0 ≤ count < bitsแต่การเปลี่ยนตามจำนวนบิตที่แน่นอนนั้นแทบจะไม่ได้กำหนดหรือปัดลงไปที่ nop แทนที่จะล้างค่าและหมุนด้วย)
mirabilos

@mirabilos: ถูกต้อง แต่เป้าหมายของเราคือการเขียนฟังก์ชันที่ป้อนจำนวนกะโดยตรงไปยังคำสั่ง asm เดียว แต่หลีกเลี่ยง UB ในระดับ C สำหรับจำนวนกะที่เป็นไปได้ เนื่องจาก C ไม่มีตัวดำเนินการหรือฟังก์ชันการหมุนเราจึงต้องการหลีกเลี่ยง UB ในส่วนส่วนประกอบใด ๆ ของสำนวนนี้ เราไม่ต้องการพึ่งพาคอมไพเลอร์ที่ปฏิบัติต่อ C shift ในลักษณะเดียวกับคำแนะนำ asm shift บนเป้าหมายที่รวบรวมไว้ (และ BTW, ARM จะทำให้รีจิสเตอร์เป็นศูนย์โดยมีการเปลี่ยนแปลงจำนวนตัวแปรมากกว่าความกว้างของรีจิสเตอร์โดยใช้การนับจากไบต์ล่างสุดของรีจิสเตอร์ลิงก์ในคำตอบ)
Peter Cordes

1
ฉันจะพูดว่า "just use portable-snippets" แต่จากนั้นฉันก็ตรวจสอบโค้ดและดูเหมือนว่า (a) เรียกใช้ UB สำหรับการนับค่ากะเป็นศูนย์และ (b) ใช้เฉพาะ intrinsics บน MSVCเท่านั้น โดยทั่วไปแล้วแม้ว่าการมี "รหัสอ้างอิง" ที่คอมไพล์ได้สำหรับสิ่งที่ใช้ได้กับแฮ็กเฉพาะของคอมไพเลอร์และแพลตฟอร์มทั้งหมดดูเหมือนจะเป็นความคิดที่ดี ...
BeeOnRope

33

เนื่องจากเป็น C ++ ให้ใช้ฟังก์ชันอินไลน์:

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

ตัวแปร 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
คำเตือน: รหัสนี้จะใช้งานไม่ได้หากINTเป็นจำนวนเต็มที่ลงนามและมีการตั้งค่าเครื่องหมาย! ทดสอบตัวอย่างrol<std::int32_t>(1 << 31)ที่ควรพลิกเป็น 1 แต่กลายเป็นจริง-1(เพราะยังคงป้ายไว้)
ไม่มีใครย้ายออกจาก SE

9
@ ไม่มีใคร: ฉันเคยแสดงความคิดเห็นเมื่อ 5 ปีที่แล้วว่าคุณไม่ควรใช้ประเภทจำนวนเต็มที่มีลายเซ็น การหมุนไม่สมเหตุสมผลกับประเภทจำนวนเต็มที่ลงชื่อแล้ว
MSalters

2
คุณสามารถใช้std::numeric_limits<INT>::digitsแทนCHAR_BIT * sizeof. ฉันลืมไปว่าหากประเภทที่ไม่ได้ลงชื่อได้รับอนุญาตให้มีช่องว่างภายในที่ไม่ได้ใช้ (เช่นจำนวนเต็ม 24 บิตที่เก็บไว้ใน 32 บิต) แต่ถ้าเป็นเช่นนั้นdigitsจะดีกว่า โปรดดูที่gist.github.com/pabigot/7550454สำหรับเวอร์ชันที่มีการตรวจสอบการเปลี่ยนแปลงการนับตัวแปรเพิ่มเติม
Peter Cordes

1
@PeterCordes: พวกเขาคือ. ฉันคิดว่า Cray ทำ (ใช้การลงทะเบียนจุดลอยตัวพร้อมช่องว่างภายในซึ่งจะเป็นฟิลด์เลขชี้กำลัง)
MSalters

2
@ ชื่อปลอม '> ดังนั้นเวอร์ชัน C ++ 11 จะไม่ทำงานบน windows เว้นแต่คุณจะเปลี่ยนเป็นอย่างอื่น ... ' ใช่เปลี่ยนเป็น linux :)
Slava

21

คอมไพเลอร์ส่วนใหญ่มีเนื้อแท้สำหรับสิ่งนั้น Visual Studio เช่น_rotr8, _rotr16


ว้าว! คำตอบที่ยอมรับได้ง่ายขึ้น btw สำหรับ DWORD (32 บิต) ให้ใช้ _rotr และ _rotl
Gabe Halsmer

16

C ++ 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ยังไม่รองรับ

ข้อเสนอกล่าวว่า:

หัวข้อ:

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 การหมุน [bitops.rot]

std::numeric_limits<T>::digitsในรายละเอียดต่อให้ไม่มีแสดงว่า

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

ข้อ จำกัด : T เป็นประเภทจำนวนเต็มไม่ได้ลงนาม (3.9.1 [basic.fundamental])

ให้ r เป็น s% N

ผลตอบแทน: ถ้า r เป็น 0, x; ถ้า r เป็นบวก(x << r) | (x >> (N - r)); ถ้า r rotr(x, -r)คือลบ

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

ข้อ จำกัด : T เป็นประเภทจำนวนเต็มไม่ได้ลงนาม (3.9.1 [basic.fundamental]) ให้ r เป็น s% N

ผลตอบแทน: ถ้า r เป็น 0, x; ถ้า r เป็นบวก(x >> r) | (x << (N - r)); ถ้า r rotl(x, -r)คือลบ

std::popcountนอกจากนี้ยังเพิ่มA เพื่อนับจำนวน 1 บิต: จะนับจำนวนบิตที่กำหนดในจำนวนเต็ม 32 บิตได้อย่างไร


การหมุนบิตใช้เวลานานแค่ไหนในการลงจอดใน c ++ สมัยใหม่? แม้แต่ใน LLVM เสียงดัง แต่ก็มีอยู่ภายในเมื่อไม่กี่ปีที่ผ่านมา => reviews.llvm.org/D21457ฉันคิดว่า ARM มีวิธีการหมุนก่อนปี 2010 ดังนั้นจึงควรอยู่ที่นั่นตั้งแต่ c ++ 11
sandthorn

15

แน่นอน:

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

6
นั่น8เป็นการสะกดผิดCHAR_BIT(ซึ่งไม่จำเป็นต้องเป็น 8 เป๊ะ ๆ )?
Toby Speight

2
ตั้งแต่นี้เป็นคำตอบเช่นเดียวกับระเบิด (ยกเว้นการแลกเปลี่ยนที่เหมาะสมสำหรับซ้าย) แสดงความคิดเห็นปีเตอร์คอร์ดในคำตอบของฉันยังใช้ที่นี่: std::numeric_limits<T>::digitsการใช้งาน
MSalters

7

เกี่ยวกับสิ่งนี้โดยใช้บิตเซ็ตมาตรฐาน ...

#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

7

ถ้า x เป็นค่า 8 บิตคุณสามารถใช้สิ่งนี้:

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

2
อาจจะประพฤติมิชอบหากxมีการลงนาม
sam hocevar

6

ในรายละเอียดคุณสามารถใช้ตรรกะต่อไปนี้

ถ้า Bit Pattern คือ 33602 ในจำนวนเต็ม

1000 0011 0100 0010

และคุณต้องโรลโอเวอร์ด้วย 2 shif ด้านขวาจากนั้น: ก่อนอื่นให้ทำสำเนารูปแบบบิตจากนั้นเลื่อนไปทางซ้าย: ความยาว - RightShift คือความยาว 16 ค่ากะขวาคือ 2 16 - 2 = 14

หลังจากเปลี่ยนไปแล้ว 14 ครั้งคุณจะได้รับ

1,000 0000 0000 0000

ตอนนี้เลื่อนค่า 33602 ไปทางขวา 2 ครั้งตามต้องการ คุณได้รับ

0010 0000 1101 0000

ตอนนี้ใช้ OR ระหว่างค่าที่เลื่อนไปทางซ้าย 14 ครั้งและค่าที่เลื่อนไปทางขวา 2 เท่า

1,000 0000 0000 0000
0010 0000 1101 0000
===================
1010 0000 1101 0000
===================

และคุณจะได้รับค่าโรลโอเวอร์ที่เปลี่ยนไป จำไว้ว่าการดำเนินการที่ชาญฉลาดของบิตนั้นเร็วกว่าและไม่จำเป็นต้องวนซ้ำใด ๆ


1
คล้ายกับรูทีนย่อยด้านบน ... b = b << m | b >> (Nm);
SM Kamran

นั่นไม่ควรเป็น XOR ไม่ใช่หรือ 1 ^ 0 = 1, 0 ^ 0 = 0 เป็นต้นหากเป็นหรือไม่เป็นเอกสิทธิ์จึงจะเป็น 1 เสมอ
BK

5

สมมติว่าคุณต้องการเลื่อนไปทางขวา Lบิตและอินพุตxเป็นตัวเลขที่มีNบิต:

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

4

คำตอบที่ถูกต้องมีดังต่อไปนี้:

#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

0

ซอร์สโค้ด x หมายเลขบิต

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

ด้านล่างนี้เป็นคำตอบของDídacPérezเวอร์ชันปรับปรุงเล็กน้อยโดยนำทั้งสองทิศทางไปใช้พร้อมกับการสาธิตการใช้งานของฟังก์ชันเหล่านี้โดยใช้ถ่านที่ไม่ได้ลงชื่อและค่ายาวที่ไม่ได้ลงนาม หมายเหตุหลายประการ:

  1. ฟังก์ชันถูกแทรกไว้สำหรับการปรับแต่งคอมไพลเลอร์
  2. ฉันใช้ไฟล์ cout << +valueกลอุบายในการส่งออกอักขระที่ไม่ได้ลงนามโดยรวมที่ฉันพบที่นี่: https://stackoverflow.com/a/28414758/1599699
  3. ขอแนะนำให้ใช้<put the type here>ไวยากรณ์ที่ชัดเจนเพื่อความชัดเจนและปลอดภัย
  4. ฉันใช้ถ่านที่ไม่ได้ลงชื่อสำหรับพารามิเตอร์ shiftNum เนื่องจากสิ่งที่ฉันพบในส่วนรายละเอียดเพิ่มเติมที่นี่ :

ผลลัพธ์ของการดำเนินการ shift ไม่ได้กำหนดไว้ว่านิพจน์ส่วนเติมเป็นค่าลบหรือหากนิพจน์ส่วนเสริมมีค่ามากกว่าหรือเท่ากับจำนวนบิตในนิพจน์ shift (เลื่อนระดับ)กะแสดงออก

นี่คือรหัสที่ฉันใช้:

#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

เกินฟังก์ชั่น:

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

คุณควรใส่ x ไว้ในวงเล็บเพื่อหลีกเลี่ยงความประหลาดใจที่น่ารังเกียจโดยใช้นิพจน์เป็นอาร์กิวเมนต์ของมาโคร
Joey

3
หากค่าไม่ใช่ 16 บิตคุณจะไร้สาระไปอย่างเงียบ ๆ
James Hopkin

หากกำหนดเป็นมาโครก็ต้องระมัดระวังเพื่อหลีกเลี่ยงการส่งนิพจน์ที่มีผลข้างเคียงเป็นอาร์กิวเมนต์
Phil Miller
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.