วิธีใดที่เร็วที่สุด / มีประสิทธิภาพที่สุดในการค้นหาบิตชุดสูงสุด (msb) ในจำนวนเต็มใน C


119

ถ้าฉันมีจำนวนเต็ม n และฉันต้องการทราบตำแหน่งของบิตที่มีนัยสำคัญที่สุด (นั่นคือถ้าบิตที่มีนัยสำคัญน้อยที่สุดอยู่ทางขวาฉันต้องการทราบตำแหน่งของบิตทางซ้ายที่ไกลที่สุดซึ่งเป็น 1) วิธีใดที่เร็วที่สุด / มีประสิทธิภาพที่สุดในการค้นหา

ฉันรู้ว่า POSIX สนับสนุนffs()วิธีการใน strings.h เพื่อค้นหาบิตชุดแรก แต่ดูเหมือนจะไม่มีfls()วิธีที่เกี่ยวข้อง

มีวิธีที่ชัดเจนในการทำสิ่งนี้ที่ฉันพลาดไปหรือไม่?

แล้วในกรณีที่คุณไม่สามารถใช้ฟังก์ชัน POSIX เพื่อการพกพาได้ล่ะ?

แก้ไข: สิ่งที่เกี่ยวกับโซลูชันที่ใช้ได้กับสถาปัตยกรรมทั้ง 32 และ 64 บิต (รายการรหัสหลายรายการดูเหมือนว่าจะใช้ได้เฉพาะกับ 32 บิตเท่านั้น)


มีการใช้งานบางอย่างที่นี่: graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear (แก้ไข: หลังจากอ่านคำถามของคุณอีกครั้งฉันทราบว่าลิงก์ด้านบนมีไว้สำหรับค้นหาบิตที่ตั้งค่าขวาสุดไม่ใช่ซ้ายสุดตามที่คุณต้องการแม้ว่าจะไม่มี ความรู้สึกของขนาดคำมันเป็นเรื่องยากที่จะตอบ)
ใช้จ่าย

2
โปรดดูที่ " จำนวนขั้นตอนวิธีการศูนย์ชั้นนำ " ในการดีไลท์ของแฮกเกอร์
Darius Bacon

นับว่าศูนย์บนขวา ; คำถามเกี่ยวกับเลขศูนย์ทางด้านซ้าย อย่างน้อยในระยะสั้นฉันไม่เห็นมันที่นั่น
Darius Bacon

2
คุณต้องการหมายเลขบิต 'n' เป็นพิเศษหรือ 2 ^ n เพียงพอหรือไม่
Alnitak

1
ดูอัลกอริทึม "Log Base 2" - ดังที่ Anderson กล่าวไว้ในบทความ: "ฐานบันทึก 2 ของจำนวนเต็มเหมือนกับตำแหน่งของชุดบิตสูงสุด (หรือชุดบิตที่สำคัญที่สุดคือ MSB)"
Michael Burr

คำตอบ:


64

GCC มี :

 - ฟังก์ชันในตัว: int __builtin_clz (int x ไม่ได้ลงนาม)
     ส่งคืนจำนวน 0 บิตนำหน้าใน X โดยเริ่มจากมากที่สุด
     ตำแหน่งบิตที่สำคัญ ถ้า X เป็น 0 ผลลัพธ์จะไม่ถูกกำหนด

 - ฟังก์ชันในตัว: int __builtin_clzl (ยาวไม่ได้ลงนาม)
     คล้ายกับ `__builtin_clz 'ยกเว้นประเภทอาร์กิวเมนต์คือ` unsigned
     ยาว'.

 - ฟังก์ชันในตัว: int __builtin_clzll (ยาวไม่ได้ลงนามยาว)
     คล้ายกับ `__builtin_clz 'ยกเว้นประเภทอาร์กิวเมนต์คือ` unsigned
     ยาวนาน '.

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


เคล็ดลับที่มีประโยชน์ถ้าป้อนข้อมูลของคุณสามารถเป็นศูนย์คือ__builtin_clz(x | 1): ไม่มีเงื่อนไขการตั้งค่าบิตต่ำโดยไม่มีการแก้ไขใด ๆ คนอื่น ๆ ที่ทำให้การส่งออก31สำหรับx=0โดยไม่ต้องเปลี่ยนเอาท์พุทสำหรับการป้อนข้อมูลอื่น ๆ

เพื่อหลีกเลี่ยงไม่ให้ต้องทำเช่นนั้นตัวเลือกอื่นของคุณคืออินทรินเฉพาะของแพลตฟอร์มเช่น ARM GCC __clz(ไม่จำเป็นต้องใช้ส่วนหัว) หรือ x86 _lzcnt_u32บน CPU ที่รองรับlzcntคำสั่ง (ระวังว่าจะlzcntถอดรหัสเหมือนbsrบนซีพียูรุ่นเก่าแทนที่จะเป็น faulting ซึ่งให้ 31-lzcnt สำหรับอินพุตที่ไม่ใช่ศูนย์)

น่าเสียดายที่ไม่มีวิธีใดที่จะใช้ประโยชน์จากคำสั่ง CLZ แบบพกพาบนแพลตฟอร์มที่ไม่ใช่ x86 ซึ่งกำหนดผลลัพธ์สำหรับอินพุต = 0 เป็น 32 หรือ 64 (ตามความกว้างตัวถูกดำเนินการ) x86 ของlzcntไม่ว่าเกินไปในขณะที่การผลิตบิตดัชนีคอมไพเลอร์ที่มีการพลิกเว้นแต่คุณจะใช้bsr31-__builtin_clz(x)

("ผลลัพธ์ที่ไม่ได้กำหนด" ไม่ใช่ C Undefined Behavior เป็นเพียงค่าที่ไม่ได้กำหนดไว้จริงๆแล้วคืออะไรก็ตามที่อยู่ในรีจิสเตอร์ปลายทางเมื่อคำสั่งทำงาน AMD ทำเอกสารสิ่งนี้ Intel ไม่ทำ แต่ CPU ของ Intel จะใช้พฤติกรรมนั้น แต่ไม่ใช่สิ่งที่เคยอยู่ในตัวแปร C ที่คุณกำหนดให้ก่อนหน้านี้นั่นไม่ใช่วิธีการทำงานเมื่อ gcc เปลี่ยน C เป็น asm ดูเพิ่มเติมเหตุใดการทำลาย "การพึ่งพาเอาต์พุต" ของ LZCNT จึงมีความสำคัญ )


5
MSVC จะมี_BitScanReverse
ratchet freak

1
พฤติกรรมที่ไม่ได้กำหนดไว้เป็นศูนย์ช่วยให้พวกเขารวบรวมเป็นคำสั่ง BSR เดียวบน x86 แม้ว่า LZCNT จะไม่พร้อมใช้งาน นี้เป็นประโยชน์ที่ยิ่งใหญ่สำหรับ__builtin_ctzมากกว่าffsซึ่งรวบรวมไป BSF และ CMOV ที่จะจัดการกับการป้อนข้อมูลเป็นศูนย์กรณี บนสถาปัตยกรรมที่ไม่มีการใช้งานสั้นพอ (เช่น ARM เก่าที่ไม่มีclzคำสั่ง) gcc จะส่งเสียงเรียกไปยังฟังก์ชันตัวช่วย libgcc
Peter Cordes

41

สมมติว่าคุณใช้ x86 และเล่นเกมสำหรับแอสเซมเบลอร์แบบอินไลน์เล็กน้อย Intel มีBSRคำสั่ง ("bit scan reverse") มันเร็วในx86 บางตัว (microcoded กับคนอื่น ๆ ) จากคู่มือ:

ค้นหาตัวถูกดำเนินการต้นทางสำหรับบิตชุดที่สำคัญที่สุด (1 บิต) หากพบ 1 บิตที่สำคัญที่สุดดัชนีบิตจะถูกเก็บไว้ในตัวถูกดำเนินการปลายทาง ตัวถูกดำเนินการต้นทางอาจเป็นรีจิสเตอร์หรือตำแหน่งหน่วยความจำ ตัวถูกดำเนินการปลายทางคือรีจิสเตอร์ ดัชนีบิตเป็นค่าชดเชยที่ไม่ได้ลงนามจากบิต 0 ของตัวถูกดำเนินการต้นทาง หากตัวถูกดำเนินการแหล่งเนื้อหาเป็น 0 เนื้อหาของตัวถูกดำเนินการปลายทางจะไม่ได้กำหนดไว้

(หากคุณใช้ PowerPC จะมีcntlzคำสั่ง ("นับเลขศูนย์นำหน้า") ที่คล้ายกัน)

ตัวอย่างรหัสสำหรับ gcc:

#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" : "=r"(msb) : "r"(n));
    std::cout << n << " : " << msb << std::endl;
  }
  return 0;
}

ดูบทช่วยสอนเกี่ยวกับแอสเซมเบลอร์แบบอินไลน์ซึ่งแสดงให้เห็นว่า (ส่วน 9.4) เร็วกว่าการวนซ้ำโค้ดมาก


4
จริงๆแล้วคำสั่งนี้มักจะเข้ารหัสแบบไมโครเป็นลูปและค่อนข้างช้า
rlbond

2
อันไหน ? BSR หรือ CNTLZ? ขณะที่ฉันอ่าน x86-timing.pdf ที่อ้างถึงข้างต้น BSR นั้นช้าใน Netburst Pentiums เท่านั้น ฉันไม่รู้อะไรเกี่ยวกับ PowerPC เลย
timday

5
... ตกลงเมื่อตรวจสอบอย่างใกล้ชิดทำให้ "BSR เร็วบน P3 / Pentium-M / Core2 x86s เท่านั้น" ช้าบน Netburst และ AMD
09:29 น

1
โปรดทราบ: ลิงก์สองรายการสุดท้ายของคุณเสียแล้ว
Baum mit Augen

2
@rlbond: ฮะ BSR บน P4 Prescott คือ 2 uops ที่มีเวลาแฝง 16 รอบ (!) โดยมีปริมาณงานหนึ่งต่อ 4c แต่ใน Netburst ก่อนหน้านี้มีเวลาแฝงเพียง 4 รอบ (ยังคงเป็น 2 uops) และหนึ่งต่อปริมาณงาน 2c (ที่มา: agner.org/optimize ) ในซีพียูส่วนใหญ่มันยังมีการพึ่งพาเอาต์พุตซึ่ง gcc ไม่ได้คำนึงถึง (เมื่ออินพุตเป็นศูนย์พฤติกรรมที่แท้จริงคือการปล่อยให้ปลายทางไม่เปลี่ยนแปลง) นี้สามารถนำไปสู่ปัญหาเช่นstackoverflow.com/questions/25078285/... IDK ทำไม gcc ถึงพลาด BSR เมื่อทำการแก้ไข
Peter Cordes

38

เนื่องจาก 2 ^ N เป็นจำนวนเต็มที่มีเฉพาะชุดบิต N (1 << N) การค้นหาตำแหน่ง (N) ของบิตชุดสูงสุดจึงเป็นฐานบันทึกจำนวนเต็ม 2 ของจำนวนเต็มนั้น

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

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

หากคุณเลื่อนลงในหน้านั้นจะมีรูปแบบที่รวดเร็วและซับซ้อนมากขึ้น อย่างไรก็ตามหากคุณรู้ว่าคุณกำลังจัดการกับตัวเลขที่มีเลขศูนย์นำหน้าจำนวนมากวิธีการที่ไร้เดียงสาอาจให้ความเร็วที่ยอมรับได้เนื่องจากการขยับบิตค่อนข้างเร็วใน C และอัลกอริทึมง่ายๆไม่จำเป็นต้องสร้างดัชนีอาร์เรย์

หมายเหตุ:เมื่อใช้ค่า 64 บิตโปรดใช้ความระมัดระวังอย่างยิ่งเกี่ยวกับการใช้อัลกอริทึมที่ชาญฉลาดเป็นพิเศษ หลายตัวทำงานได้อย่างถูกต้องสำหรับค่า 32 บิตเท่านั้น


2
@ โจฮันก้าวผ่านตัวแก้ไขข้อบกพร่องสามารถช่วยอธิบายได้ว่าทำไมลูปจึงออก โดยทั่วไปแล้ว 'เนื่องจากนิพจน์ในเงื่อนไขประเมินเป็น 0 (ซึ่งถือว่าเป็นเท็จ) เมื่อ 1 บิตสุดท้ายถูกเลื่อนออกไปทางขวา
Quinn Taylor

2
เป็นความคิดที่ดีที่จะใช้ผลลัพธ์สุดท้ายเช่นนั้น :)
โยฮัน

6
หมายเหตุ: ต้องไม่ได้ลงนามสำหรับจำนวนเต็มที่ลงนามการเลื่อนด้านขวาจะล้มเหลวสำหรับจำนวนลบ
Xantix

2
Xantix: การเปลี่ยนแปลงใน C / C ++ เป็นการกะแบบตรรกะดังนั้นจึงทำงานได้ดี สำหรับ Java, JavaScript หรือ D, >>>คุณจำเป็นต้องใช้ผู้ประกอบการเปลี่ยนตรรกะ นอกจากนี้ยังอาจเป็นตัวเปรียบเทียบ!= 0และจำนวนวงเล็บที่ไม่ได้ระบุ
ไล่ล่า

8
@ Chase: ไม่มันไม่ใช่ มันเป็นกะตรรกะสำหรับการได้รับการรับรอง สำหรับการลงนามก็อาจหรือไม่อาจจะมีการเปลี่ยนแปลงเชิงตรรกะ (และก็มักจะเลขคณิตในความเป็นจริง)
Tim Čas

17

สิ่งนี้ควรจะเร็วฟ้าผ่า:

int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}

25
การเปลี่ยนแปลง 7 บิต 5 หรือคำแนะนำการทวีคูณและการพลาดแคชที่อาจเกิดขึ้น :) คุณทำการเปรียบเทียบหรือดูที่แอสเซมเบลอร์สร้างขึ้น? มันอาจจะจบลงค่อนข้างช้าขึ้นอยู่กับวิธีการมากของมันคอมไพเลอร์สามารถกำจัด
jalf

5
ฉันมาใหม่ที่นี่ ฉันไม่ได้รับคะแนนโหวตเชิงลบ ฉันได้ให้คำตอบเดียวกับซอร์สโค้ดที่ใช้งานได้จริง
Protagonist

9
"แคชที่เป็นไปได้ที่พลาด" อาจเป็นเพราะรหัสนี้ต้องการการเข้าถึงตารางการค้นหา หากตารางนั้นไม่ได้ถูกแคชเมื่อมีการเรียกนี้จะมีแผงลอยอยู่ในขณะที่เรียกข้อมูล สิ่งนี้อาจทำให้ประสิทธิภาพในกรณีที่แย่ที่สุดแย่กว่าโซลูชันที่ไม่ใช้ LUT
ผ่อนคลาย

13
ไม่ใช่ประเด็นจริงๆ มันใช้แคชข้อมูลมากเกินความจำเป็น (แคชมากกว่าหนึ่งบรรทัดแม้กระทั่ง) และแคชคำสั่งมากกว่าที่จำเป็น คุณอาจได้รับแคชพลาดซึ่งอาจหลีกเลี่ยงได้ในครั้งแรกที่คุณเรียกใช้ฟังก์ชันและจะทำให้แคชเกิดมลพิษเกินความจำเป็นดังนั้นหลังจากการโทรรหัสอื่น ๆ อาจพบว่าพลาดมากกว่าที่จำเป็น LUT มักจะไม่คุ้มกับปัญหาเพราะแคชพลาดมีราคาแพง แต่ฉันบอกเพียงว่าเป็นสิ่งที่ฉันต้องการเปรียบเทียบก่อนที่จะอ้างว่า "เร็วปานสายฟ้า" ไม่ใช่ว่าเป็นปัญหาแน่นอน
jalf

6
ตารางมี 32 รายการและทุกค่าคือ <255 (127) ดังนั้นกำหนดตารางเป็นประเภทถ่านที่ไม่ได้ลงชื่อและจะพอดีกับบรรทัดแคช L1 ขนาด 32 ไบต์เดียว และสิ่งทั้งหมดก็พอดีกับแคชสองบรรทัด
ChuckCottrill

16

ซึ่งเหมือนกับการค้นหาบันทึกจำนวนเต็ม มีลูกเล่นเล็กน้อย แต่ฉันได้สร้างเครื่องมือของตัวเองสำหรับสิ่งนี้ เป้าหมายของหลักสูตรคือความเร็ว

ความเข้าใจของฉันคือ CPU มีเครื่องตรวจจับบิตอัตโนมัติอยู่แล้วใช้สำหรับการแปลงจำนวนเต็มเพื่อลอย! ดังนั้นใช้สิ่งนั้น

double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness

เวอร์ชันนี้จะร่ายค่าเป็นสองเท่าจากนั้นอ่านเลขชี้กำลังซึ่งจะบอกคุณว่าบิตอยู่ที่ใด การเปลี่ยนและลบแฟนซีคือการดึงส่วนที่เหมาะสมออกจากค่า IEEE

การลอยตัวเร็วกว่าเล็กน้อย แต่การลอยสามารถให้ตำแหน่ง 24 บิตแรกแก่คุณได้เท่านั้นเนื่องจากมีความแม่นยำน้อยกว่า


ในการดำเนินการนี้อย่างปลอดภัยโดยไม่มีพฤติกรรมที่ไม่ได้กำหนดใน C ++ หรือ C ให้ใช้memcpyแทนการแคสต์ตัวชี้สำหรับการพิมพ์เจาะ คอมไพเลอร์รู้วิธีการอินไลน์อย่างมีประสิทธิภาพ

// static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

หรือใน C99 และใหม่กว่าให้ใช้ไฟล์union {double d; uint32_t u[2];};. แต่โปรดทราบว่าใน C ++ การกดประเภทยูเนี่ยนรองรับเฉพาะในคอมไพเลอร์บางตัวเป็นส่วนขยายไม่ใช่ใน ISO C ++


โดยปกติจะช้ากว่าอินทรินซิกเฉพาะแพลตฟอร์มสำหรับคำสั่งการนับเลขศูนย์นำหน้า แต่ ISO C แบบพกพาไม่มีฟังก์ชันดังกล่าว ซีพียูบางคนยังขาดศูนย์ชั้นนำการเรียนการสอนนับ doubleแต่บางส่วนของที่มีประสิทธิภาพสามารถแปลงจำนวนเต็มไป การพิมพ์รูปแบบบิต FP ให้กลับไปเป็นจำนวนเต็มอาจช้าได้ (เช่นใน PowerPC ต้องมีการจัดเก็บ / โหลดซ้ำและมักจะทำให้เกิดแผงขายของที่เก็บโหลดได้)

ขั้นตอนวิธีการนี้อาจจะเป็นประโยชน์สำหรับการใช้งาน SIMD เพราะซีพียูน้อยลงมี lzcntSIMD x86 มีคำสั่งดังกล่าวกับ AVX512CD เท่านั้น


2
ใช่. และ gcc จะทำสิ่งที่น่ารังเกียจด้วยรหัสเช่นนี้กับ -O2 เนื่องจากการเพิ่มประสิทธิภาพการใช้นามแฝง
MSN

4
การแคสต์ระหว่างจำนวนเต็มและทศนิยมอาจมีราคาแพงอย่างน่าประหลาดใจในซีพียู x86
jalf

1
ใช่ต้นทุน FPU สูง แต่การวัดเวลาจริงแสดงให้เห็นว่าสิ่งนี้เร็วกว่าออปบิตทั้งหมดหรือโดยเฉพาะลูปใด ๆ ลองใช้และใช้เวลาให้เร็วที่สุดคือคำแนะนำที่ดีที่สุดเสมอ ฉันไม่ได้มีปัญหากับ GCC และ -O2 กับสิ่งนี้
SPWorley

1
นี่ไม่ใช่พฤติกรรมที่ไม่ได้กำหนด (การอ่านค่าผ่านตัวชี้ของชนิดที่เข้ากันไม่ได้) ใช่หรือไม่
dreamlax

3
Hacker's Delight อธิบายถึงวิธีการแก้ไขข้อผิดพลาดในการลอยตัว 32 บิตใน 5-3 การนับเลขนำหน้า 0 นี่คือรหัสของพวกเขาซึ่งใช้สหภาพที่ไม่ระบุชื่อเพื่อซ้อนทับ asFloat และ asInt: k = k & ~ (k >> 1); asFloat = (ลอย) k + 0.5f; n = 158 - (asInt >> 23); (และใช่สิ่งนี้ขึ้นอยู่กับพฤติกรรมที่นำไปใช้งาน)
D Coetzee

11

Kaz Kylheku ที่นี่

ฉันเปรียบเทียบสองวิธีสำหรับตัวเลข 63 บิตนี้ (ประเภท long long บน gcc x86_64) โดยอยู่ห่างจากบิตเครื่องหมาย

(ฉันต้องการสิ่งนี้ "ค้นหาบิตสูงสุด" สำหรับบางสิ่งคุณจะเห็น)

ฉันใช้การค้นหาไบนารีที่ขับเคลื่อนด้วยข้อมูล (ตามคำตอบข้างต้นอย่างใกล้ชิด) ฉันยังใช้แผนผังการตัดสินใจที่ไม่มีการควบคุมด้วยมือซึ่งเป็นเพียงรหัสที่มีตัวถูกดำเนินการทันที ไม่มีลูปไม่มีตาราง

แผนภูมิการตัดสินใจ (maximum_bit_unrolled) ถูกเปรียบเทียบให้เร็วขึ้น 69% ยกเว้นกรณี n = 0 ที่การค้นหาไบนารีมีการทดสอบอย่างชัดเจน

การทดสอบพิเศษของ binary-search สำหรับ 0 case นั้นเร็วกว่าโครงสร้างการตัดสินใจเพียง 48% ซึ่งไม่มีการทดสอบพิเศษ

คอมไพเลอร์เครื่อง: (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5)

int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit(long long n)
{
  const long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

โปรแกรมทดสอบที่รวดเร็วและสกปรก:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d\n", n, n, b1, b2);

  printf("time1 = %d\n", (int) (mid - start));
  printf("time2 = %d\n", (int) (end - mid));
  return 0;
}

เมื่อใช้เพียง -O2 ความแตกต่างจะมากขึ้น ต้นไม้ตัดสินใจเร็วขึ้นเกือบสี่เท่า

ฉันยังเปรียบเทียบกับรหัสการเปลี่ยนบิตที่ไร้เดียงสา:

int highest_bit_shift(long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

สิ่งนี้เร็วสำหรับคนจำนวนน้อยเท่านั้นอย่างที่ใคร ๆ คาดไม่ถึง ในการพิจารณาว่าบิตสูงสุดคือ 1 สำหรับ n == 1 จะทำการเปรียบเทียบได้เร็วกว่า 80% อย่างไรก็ตามครึ่งหนึ่งของตัวเลขที่สุ่มเลือกในพื้นที่ 63 บิตมีชุดบิตที่ 63!

ในอินพุต 0x3FFFFFFFFFFFFFFF เวอร์ชันแผนผังการตัดสินใจค่อนข้างเร็วกว่าใน 1 เล็กน้อยและแสดงว่าเร็วกว่าตัวเปลี่ยนบิตถึง 1120% (12.2 เท่า)

ฉันจะเปรียบเทียบโครงสร้างการตัดสินใจเทียบกับบิวด์อินของ GCC และลองส่วนผสมของอินพุตแทนที่จะทำซ้ำกับหมายเลขเดียวกัน อาจมีการคาดคะเนสาขาที่ติดกันเกิดขึ้นและอาจมีสถานการณ์แคชที่ไม่สมจริงซึ่งทำให้การทำซ้ำเร็วขึ้นอย่างไม่น่าเชื่อ


9
ฉันไม่ได้บอกว่าสิ่งนี้ไม่ดี แต่โปรแกรมการทดสอบของคุณที่นี่จะทดสอบเฉพาะตัวเลขเดียวกันเท่านั้นซึ่งหลังจากการทำซ้ำ 2-3 ครั้งจะทำให้ตัวทำนายสาขาเป็นตำแหน่งสุดท้ายและหลังจากนั้นพวกเขาจะทำการทำนายสาขาที่สมบูรณ์แบบ สิ่งที่ดีคือด้วยการแจกแจงแบบสุ่มทั้งหมดครึ่งหนึ่งของตัวเลขจะใกล้เคียงกับการทำนายที่สมบูรณ์แบบนั่นคือ bit63
Surt


6
unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

1 ลงทะเบียน 13 คำแนะนำ เชื่อหรือไม่ว่าโดยปกติแล้วจะเร็วกว่าคำสั่ง BSR ที่กล่าวถึงข้างต้นซึ่งทำงานเป็นเส้นตรง นี่คือเวลาลอการิทึม

จากhttp://aggregate.org/MAGIC/#Most%20Significant%201%20Bit


7
รหัสด้านบนไม่ตอบคำถาม ส่งคืนจำนวนเต็มที่ไม่ได้ลงนามโดยที่บิตที่สำคัญที่สุดใน x ยังคงเปิดอยู่และบิตอื่น ๆ ทั้งหมดจะถูกปิด คำถามคือการคืนตำแหน่งที่สำคัญที่สุดในบิต
ตัวเอก

3
จากนั้นคุณสามารถใช้แนวทางลำดับ De Bruijn เพื่อค้นหาดัชนีของบิตที่ตั้งค่าไว้ :-)
R .. GitHub STOP HELPING ICE

5
@ Protagonist เขากล่าวในความคิดเห็นว่าพอเพียง
rlbond

อันนี้ (จากหน้าเดียวกัน) จะทำในสิ่งที่คุณต้องการ แต่ต้องมีฟังก์ชันเพิ่มเติม aggregate.org/MAGIC/#Log2%20of%20an%20Integer
Quinn Taylor

1
BSR เร็วใน CPU ของ Intel ตั้งแต่ Core2 เป็นอย่างน้อย LZCNT ทำงานได้อย่างรวดเร็วบนซีพียู AMD และ gcc ใช้สำหรับ__builtin_clzกรณีที่เปิดใช้งานด้วย-march=nativeหรือบางสิ่งบางอย่าง (เนื่องจากมันเร็วในทุก CPU ที่รองรับ) แม้ในซีพียูเช่น AMD Bulldozer-family ที่ BSR "ช้า" ก็ไม่ช้าขนาดนี้: 7 m-ops พร้อมเวลาแฝง 4 รอบและหนึ่งต่อ 4c ทรูพุต ในอะตอม, BSR เป็นจริงๆช้า: 16 รอบ ใน Silvermont คือ 10 uops พร้อมเวลาแฝง 10 รอบ ซึ่งอาจมีเวลาแฝงต่ำกว่า BSR บน Silvermont เล็กน้อย แต่เป็น IDK
Peter Cordes

6

ต่อไปนี้คือเกณฑ์มาตรฐาน (อย่างง่าย) บางส่วนของอัลกอริทึมที่ให้ไว้ในหน้านี้ ...

อัลกอริทึมยังไม่ได้รับการทดสอบกับอินพุตทั้งหมดของ int ที่ไม่ได้ลงชื่อ ดังนั้นตรวจสอบก่อนก่อนที่จะใช้บางอย่างสุ่มสี่สุ่มห้า;)

บนเครื่องของฉัน clz (__builtin_clz) และ asm ทำงานได้ดีที่สุด asm ดูเหมือนเร็วกว่าแล้ว clz ... แต่อาจเป็นเพราะเกณฑ์มาตรฐานง่ายๆ ...

//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  

#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)



/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)


/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)




/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)




/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



/***************** bitshift2 ********************/
int POS[32] = {0, 1, 28, 2, 29, 14, 24, 3,
             30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
             16, 7, 26, 12, 18, 6, 11, 5, 10, 9};

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])

#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)



#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math\n");
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));

  printf("\n\n");

  printf("clz\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  printf("\n\n");

  printf("i2f\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("\n\n");

  printf("asm\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("\n\n");

  printf("bitshift1\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("\n\n");

  printf("bitshift2\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("\n\nPlease wait...\n\n");


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");

  return EXIT_SUCCESS;
}

6

แม้ว่าฉันจะใช้วิธีนี้ก็ต่อเมื่อฉันต้องการประสิทธิภาพที่ดีที่สุดเท่าที่จะเป็นไปได้ (เช่นสำหรับการเขียน AI เกมกระดานบางประเภทที่เกี่ยวข้องกับบิตบอร์ด) วิธีแก้ปัญหาที่มีประสิทธิภาพที่สุดคือการใช้ ASM แบบอินไลน์ ดูส่วนการเพิ่มประสิทธิภาพของโพสต์บล็อกนี้สำหรับรหัสพร้อมคำอธิบาย

[... ] bsrlคำสั่งประกอบจะคำนวณตำแหน่งของบิตที่สำคัญที่สุด ดังนั้นเราสามารถใช้asmคำสั่งนี้:

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));

วิธีขยาย: โซลูชันลูปมาตรฐาน (เลื่อนไปทางซ้ายและตรวจสอบ MSB) น่าจะอ่านได้มากที่สุด เช่นเดียวกับในทุกกรณีที่เกี่ยวข้องกับการบิดเล็กน้อยความเร็วของ ASM จะไม่สามารถเอาชนะได้แม้ว่าจะไม่มีจุดที่ทำให้โค้ดของคุณยุ่งเหยิงเว้นแต่จำเป็น แฮ็คเป็นวิธีแก้ปัญหาระหว่างกันไปทางใดทางหนึ่ง
Noldorin

ฉันจะบอกว่าการใช้ลอการิทึมจะเป็นโซลูชันที่อ่านได้อย่างสมบูรณ์ (ตรวจสอบ asm ที่สร้างขึ้นเพื่อดูว่าคอมไพเลอร์สามารถปรับให้เหมาะสมเพื่อใช้คำสั่ง asm นี้ได้หรือไม่)
jalf

บางครั้งโซลูชัน ASM แบบอินไลน์จะช้าลงทั้งนี้ขึ้นอยู่กับการใช้งานในไมโครโค้ดของ CPU
rlbond

5
@rlbound: ฉันแทบไม่อยากจะเชื่อเลยแม้ว่าฉันอาจจะเข้าใจผิด สำหรับ CPU ยุคใหม่ใคร ๆ ก็คิดว่ามันจะแปลเป็นคำสั่งเดียว ....
Noldorin

3
@Noldorin มันสายไปหน่อย แต่ .. มันเป็นคำสั่งเดียว แต่ถ้ามันเป็น microcoded ตามที่ rlbond แนะนำคำสั่งเดียวก็สามารถถอดรหัสเป็นกลุ่ม µops ทั้งหมดได้ภายใน นั่นมีแนวโน้มที่จะเกิดขึ้นใน microarchitectures ของ AMD และ Intel Atom แต่ใน microarchitectures ปกติของ Intel จะเป็นการดำเนินการเพียงครั้งเดียว
harold

4

ฉันจำเป็นต้องมีกิจวัตรในการทำสิ่งนี้และก่อนที่จะค้นหาเว็บ (และพบหน้านี้) ฉันได้หาวิธีแก้ปัญหาของตัวเองโดยอาศัยการค้นหาแบบไบนารี แม้ว่าฉันจะแน่ใจว่ามีคนเคยทำสิ่งนี้มาก่อน! มันทำงานในเวลาคงที่และเร็วกว่าวิธีแก้ปัญหาที่ "ชัดเจน" ที่โพสต์ไว้แม้ว่าฉันจะไม่ได้อ้างสิทธิ์อะไรมากมาย แต่แค่โพสต์เพื่อความสนใจ

int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}

4

นั่นคือการค้นหาแบบไบนารีบางประเภทซึ่งใช้ได้กับประเภทจำนวนเต็ม (ไม่ได้ลงนาม!) ทุกชนิด

#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

เพื่อให้เสร็จสมบูรณ์:

#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}

4
โปรดพิจารณาอย่าใช้ ALL_CAPS สำหรับtypedefs หรืออะไรก็ตามยกเว้นมาโครตัวประมวลผลล่วงหน้า นี่เป็นอนุสัญญาที่ได้รับการยอมรับอย่างกว้างขวาง
underscore_d

4

คำตอบที่ซับซ้อนเกินไปที่นี่ ควรใช้เทคนิค Debruin เมื่ออินพุตมีกำลังสองอยู่แล้วมิฉะนั้นจะมีวิธีที่ดีกว่า ด้วยพลังของอินพุต 2 ตัว Debruin นั้นเร็วที่สุดและเร็วกว่า_BitScanReverseโปรเซสเซอร์ใด ๆ ที่ฉันเคยทดสอบ อย่างไรก็ตามในกรณีทั่วไป_BitScanReverse(หรือสิ่งที่เรียกว่าอินทรินซิคในคอมไพเลอร์ของคุณ) นั้นเร็วที่สุด (ในซีพียูบางตัวสามารถไมโครโค้ดได้)

หากฟังก์ชันภายในไม่ใช่ตัวเลือกนี่คือโซลูชันซอฟต์แวร์ที่ดีที่สุดสำหรับการประมวลผลอินพุตทั่วไป

u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

โปรดทราบว่าเวอร์ชันนี้ไม่จำเป็นต้องมีการค้นหา Debruin ในตอนท้ายซึ่งแตกต่างจากคำตอบอื่น ๆ ส่วนใหญ่ คำนวณตำแหน่งในสถานที่

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

u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

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


1
คำตอบบางคำตอบไม่มีสาขา แต่อาจรวบรวมด้วยกิ่งก้านที่มีเงื่อนไข คุณเปรียบเทียบเฉพาะค่าเดิมซ้ำ ๆ หรือรูปแบบง่ายๆหรืออะไร? การคาดเดาผิดสาขาเป็นตัวทำลายประสิทธิภาพ stackoverflow.com/questions/11227809/…
Peter Cordes

3

ดังที่คำตอบข้างต้นชี้ให้เห็นมีหลายวิธีในการกำหนดบิตที่สำคัญที่สุด อย่างไรก็ตามตามที่ได้ระบุไว้วิธีการดังกล่าวมีแนวโน้มที่จะไม่ซ้ำกันสำหรับการลงทะเบียน 32 บิตหรือ 64 บิต หน้า stanford.edu bithacksให้บริการโซลูชั่นที่ทำงานสำหรับทั้ง 32bit และ 64bit คอมพิวเตอร์ ด้วยการทำงานเพียงเล็กน้อยพวกเขาสามารถรวมกันเพื่อให้แนวทางข้ามสถาปัตยกรรมที่มั่นคงในการได้รับ MSB วิธีแก้ปัญหาที่ฉันมาถึงที่คอมไพล์ / ทำงานบนคอมพิวเตอร์ 64 และ 32 บิตคือ:

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/* 
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */
int
getmsb (uint32_t word)
{
    int r = 0;
    if (word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}

ไม่ได้เป็น int r; เดิมกำหนดไว้เหนือ#ifdef BUILD_64ธง? ซึ่งในกรณีนี้ไม่จำเป็นต้องมีการกำหนดนิยามใหม่ภายในเงื่อนไข
David C. Rankin

3

เวอร์ชันใน C โดยใช้การประมาณต่อเนื่อง:

unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

ข้อได้เปรียบ: เวลาทำงานจะคงที่โดยไม่คำนึงถึงจำนวนที่ระบุเนื่องจากจำนวนลูปจะเท่ากันเสมอ (4 ลูปเมื่อใช้ "int ที่ไม่ได้ลงชื่อ")


หากคุณเขียนด้วยตัวดำเนินการ ternary ( msb += (n>>msb) ? step : -step;) คอมไพเลอร์จำนวนมากมีแนวโน้มที่จะสร้าง asm แบบไม่มีสาขาหลีกเลี่ยงการคาดเดาสาขาผิดในทุกขั้นตอน ( stackoverflow.com/questions/11227809/… )
Peter Cordes

3

ฉันรู้ว่าคำถามนี้เก่ามาก แต่เพิ่งใช้ฟังก์ชันmsb () ด้วยตัวเองฉันพบว่าวิธีแก้ปัญหาส่วนใหญ่ที่นำเสนอที่นี่และในเว็บไซต์อื่น ๆ ไม่จำเป็นต้องมีประสิทธิภาพมากที่สุด - อย่างน้อยก็สำหรับคำจำกัดความส่วนตัวของฉันเกี่ยวกับประสิทธิภาพ (ดูอัปเดตด้านล่างด้วย ) นี่คือเหตุผล:

วิธีแก้ปัญหาส่วนใหญ่ (โดยเฉพาะผู้ที่ใช้รูปแบบการค้นหาไบนารีบางประเภทหรือวิธีไร้เดียงสาซึ่งทำการสแกนเชิงเส้นจากขวาไปซ้าย) ดูเหมือนจะละเลยข้อเท็จจริงที่ว่าสำหรับเลขฐานสองตามอำเภอใจมีไม่มากนักที่เริ่มต้นด้วยลำดับที่ยาวมากของ ศูนย์ ในความเป็นจริงสำหรับการใด ๆ บิตกว้างครึ่งหนึ่งของจำนวนเต็มทั้งหมดเริ่มต้นด้วย1และหนึ่งในสี่ของพวกเขาเริ่มต้นด้วย01 ดูว่าฉันไปถึงไหนแล้ว? ข้อโต้แย้งของฉันคือการสแกนเชิงเส้นที่เริ่มต้นจากตำแหน่งบิตที่สำคัญที่สุดไปยังตำแหน่งที่มีนัยสำคัญน้อยที่สุด (จากซ้ายไปขวา) นั้นไม่ได้เป็น "เชิงเส้น" อย่างที่มองในตอนแรก

แสดงได้ว่า1สำหรับความกว้างบิตใด ๆ จำนวนบิตเฉลี่ยที่ต้องทดสอบคือไม่เกิน 2 ซึ่งแปลเป็นความซับซ้อนของเวลาที่ตัดจำหน่ายของO (1)เทียบกับจำนวนบิต (!) .

แน่นอนว่ากรณีที่เลวร้ายที่สุดยังคงเป็นO (n)แย่กว่าO (log (n)) ที่คุณได้รับจากวิธีการค้นหาแบบไบนารี แต่เนื่องจากมีกรณีที่เลวร้ายที่สุดเพียงไม่กี่กรณีจึงมีความสำคัญน้อยมากสำหรับแอปพลิเคชันส่วนใหญ่ ( อัปเดต : ไม่มาก: อาจมีน้อย แต่อาจเกิดขึ้นได้โดยมีความเป็นไปได้สูง - ดูอัปเดตด้านล่าง)

นี่คือวิธีการ "ไร้เดียงสา" ที่ฉันคิดขึ้นมาซึ่งอย่างน้อยในเครื่องของฉันก็เอาชนะแนวทางอื่น ๆ ได้มากที่สุด (รูปแบบการค้นหาไบนารีสำหรับ ints 32 บิตจะต้องใช้บันทึก2 (32) = 5 ขั้นตอนเสมอในขณะที่อัลกอริทึมโง่ ๆ นี้ต้องการน้อยกว่า มากกว่า 2 โดยเฉลี่ย) - ขออภัยที่นี่เป็น C ++ และไม่ใช่ C บริสุทธิ์:

template <typename T>
auto msb(T n) -> int
{
    static_assert(std::is_integral<T>::value && !std::is_signed<T>::value,
        "msb<T>(): T must be an unsigned integral type.");

    for (T i = std::numeric_limits<T>::digits - 1, mask = 1 << i; i >= 0; --i, mask >>= 1)
    {
        if ((n & mask) != 0)
            return i;
    }

    return 0;
}

อัปเดต : ในขณะที่สิ่งที่ผมเขียนนี่เป็นความจริงที่ดีเลิศสำหรับพลเลขที่รวมกันของบิตทุกความเป็นไปได้อย่างเท่าเทียมกัน (ทดสอบความเร็วของฉันเพียงแค่วัดนานเท่าใดจึงจะกำหนด MSB สำหรับทุกจำนวนเต็ม 32 บิต), จำนวนเต็มชีวิตจริงสำหรับ ซึ่งฟังก์ชันดังกล่าวจะถูกเรียกโดยปกติจะเป็นไปตามรูปแบบที่แตกต่างกันตัวอย่างเช่นในรหัสของฉันฟังก์ชันนี้ใช้เพื่อกำหนดว่าขนาดของวัตถุเป็น 2 หรือเพื่อหากำลังถัดไปของ 2 ที่มากกว่าหรือเท่ากับขนาดของวัตถุ ฉันเดาว่าแอปพลิเคชันส่วนใหญ่ที่ใช้ MSB เกี่ยวข้องกับตัวเลขซึ่งมีขนาดเล็กกว่าจำนวนสูงสุดที่จำนวนเต็มสามารถแสดงได้ (ขนาดวัตถุไม่ค่อยใช้บิตทั้งหมดใน size_t) ในกรณีนี้วิธีแก้ปัญหาของฉันจะทำงานได้แย่กว่าวิธีการค้นหาแบบไบนารี - ดังนั้นจึงควรเลือกวิธีหลังแม้ว่าโซลูชันของฉันจะวนลูปผ่านจำนวนเต็มทั้งหมดได้เร็วกว่าก็ตาม
TL; DR:จำนวนเต็มในชีวิตจริงอาจมีอคติต่อกรณีที่เลวร้ายที่สุดของอัลกอริทึมธรรมดานี้ซึ่งจะทำให้ประสิทธิภาพแย่ลงในท้ายที่สุดแม้ว่าจะมีการตัดจำหน่าย O (1)สำหรับจำนวนเต็มตามอำเภอใจอย่างแท้จริงก็ตาม

1อาร์กิวเมนต์จะเป็นดังนี้ (ร่างคร่าวๆ): ให้nเป็นจำนวนบิต (ความกว้างบิต) มีจำนวนเต็ม2 nจำนวนเต็มซึ่งสามารถแทนค่าได้ด้วยnบิต มีจำนวนเต็ม2 n - 1 ที่เริ่มต้นด้วย1 ( 1 ตัวแรกได้รับการแก้ไขส่วนที่เหลือn - 1บิตอาจเป็นอะไรก็ได้) จำนวนเต็มเหล่านั้นต้องการการโต้ตอบเดียวของลูปเพื่อกำหนด MSB นอกจากนี้ยังมีจำนวนเต็ม2 n - 2จำนวนเริ่มต้นด้วย01โดยต้องใช้การวนซ้ำ 2 ครั้งจำนวนเต็ม2 n - 3เริ่มต้นด้วย001ต้องใช้การวนซ้ำ 3 ครั้งและอื่น ๆ

หากเราสรุปการทำซ้ำที่จำเป็นทั้งหมดสำหรับจำนวนเต็มที่เป็นไปได้ทั้งหมดและหารด้วย2 nจำนวนเต็มทั้งหมดเราจะได้จำนวนการวนซ้ำโดยเฉลี่ยที่จำเป็นสำหรับการกำหนด MSB สำหรับจำนวนเต็มn -bit:

(1 * 2 n - 1 + 2 * 2 n - 2 + 3 * 2 n - 3 + ... + n) / 2 n

การวนซ้ำเฉลี่ยชุดนี้เป็นแบบคอนเวอร์เจนท์และมีขีด จำกัด 2 สำหรับnต่ออินฟินิตี้

ดังนั้นอัลกอริทึมจากซ้ายไปขวาที่ไร้เดียงสาจึงมีความซับซ้อนของเวลาคงที่ที่ตัดจำหน่ายเป็นO (1)สำหรับจำนวนบิตใด ๆ


2
ฉันไม่คิดว่ามันจำเป็นต้องเป็นข้อสันนิษฐานที่ยุติธรรมที่อินพุตของฟังก์ชัน msb มักจะกระจายอย่างเท่าเทียมกัน ในทางปฏิบัติอินพุตเหล่านี้มักจะเป็นอินเทอร์รัปต์รีจิสเตอร์หรือบิตบอร์ดหรือโครงสร้างข้อมูลอื่น ๆ ที่มีค่ากระจายไม่สม่ำเสมอ สำหรับเกณฑ์มาตรฐานที่ยุติธรรมฉันคิดว่ามันปลอดภัยกว่าที่จะคิดว่าเอาต์พุต (ไม่ใช่อินพุต) จะกระจายเท่า ๆ กัน
johnwbyrd

3

log2ได้ให้เรา สิ่งนี้จะขจัดความจำเป็นในlog2การใช้ซอสพิเศษทั้งหมดที่คุณเห็นในหน้านี้ คุณสามารถใช้การใช้งานมาตรฐานได้log2ดังนี้:

const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

nของ0ULความต้องการที่จะได้รับการปกป้องต่อต้านเป็นอย่างดีเพราะ:

-∞ถูกส่งคืนและ FE_DIVBYZERO จะถูกยกขึ้น

ฉันได้เขียนตัวอย่างพร้อมกับการตรวจสอบที่ตั้งค่าโดยพลการIndexไว้ULONG_MAXที่นี่: https://ideone.com/u26vsi


ข้อพิสูจน์สำหรับgcc ของ ephemient คำตอบเดียวคือ:

const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

เอกสารสำหรับ_BitScanReverseรัฐที่Index:

เต็มไปด้วยตำแหน่งบิตของบิตชุดแรก (1) ที่พบ

ในทางปฏิบัติที่ฉันได้พบว่าถ้าnเป็น0ULที่Indexมีการตั้งค่า0ULเช่นเดียวกับที่มันจะหาของn 1ULแต่สิ่งเดียวที่รับประกันในเอกสารในกรณีnของ0ULคือผลตอบแทนคือ:

0 หากไม่พบบิตชุด

ดังนั้นในทำนองเดียวกันกับlog2การนำไปใช้ที่ต้องการเหนือผลตอบแทนควรตรวจสอบการตั้งค่าIndexเป็นค่าที่ตั้งค่าสถานะในกรณีนี้ ฉันได้เขียนตัวอย่างการใช้ULONG_MAXสำหรับค่าสถานะนี้อีกครั้งที่นี่: http://rextester.com/GCU61409


ไม่_BitScanReverseกลับ 0 เท่านั้น0ถ้าใส่เป็น นี่เหมือนกับคำสั่งของ x86BSRซึ่งตั้งค่า ZF ตามอินพุตเท่านั้นไม่ใช่เอาต์พุต น่าสนใจที่ MS ใช้คำว่าเอกสารทิ้งindexไว้เมื่อไม่1พบบิต ที่ตรงกับพฤติกรรม x86 asm ของbsrด้วย (AMD จัดทำเอกสารว่าออกจากรีจิสเตอร์ปลายทางโดยไม่ได้แก้ไขใน src = 0 แต่ Intel บอกว่าเอาต์พุตที่ไม่ได้กำหนดแม้ว่าซีพียูของพวกเขาจะใช้ลักษณะการทำงานที่ไม่ได้แก้ไข) ซึ่งแตกต่างจาก x86 lzcntซึ่งให้32สำหรับการไม่พบ
Peter Cordes

@PeterCordes _BitScanReverseใช้การจัดทำดัชนีแบบศูนย์ดังนั้นถ้าnเป็น 1 ดังนั้นดัชนีของบิตที่ตั้งไว้จะเป็น 0 น่าเสียดายที่คุณพูดว่าถ้าnเป็น 0 ผลลัพธ์จะเป็น 0 ด้วยเช่นกัน :( ซึ่งหมายความว่าไม่มีวิธีใดที่จะใช้การกลับไปที่ แยกแยะระหว่างn1 หรือ 0 นั่นคือสิ่งที่ฉันพยายามจะสื่อสารคุณคิดว่ามีวิธีที่ดีกว่าในการพูดสิ่งนี้หรือไม่
Jonathan Mee

Indexฉันคิดว่าคุณกำลังพูดคุยเกี่ยวกับวิธีการตั้งค่า นั่นไม่ใช่มูลค่าส่งคืน จะส่งคืนบูลีนที่เป็นเท็จหากอินพุตเป็นศูนย์ (และนี่คือสาเหตุที่ดัชนีถูกส่งผ่านโดยการอ้างอิงแทนที่จะส่งคืนตามปกติ) godbolt.org/g/gQKJdE และฉันตรวจสอบแล้ว: แม้จะมีการใช้ถ้อยคำในเอกสารของ MS แต่_BitScanReverseก็ไม่ได้ปล่อยให้ดัชนีไม่ถูกตั้งค่าn==0: คุณจะได้รับค่าใดก็ตามที่อยู่ในการลงทะเบียนที่เกิดขึ้นเพื่อใช้ (ซึ่งในกรณีของคุณอาจเป็นทะเบียนเดียวกับที่ใช้ในIndexภายหลังทำให้คุณเห็น a 0)
Peter Cordes

คำถามนี้ไม่ได้ติดแท็ก c ++
รัส

@technosaurus ขอบคุณฉันลืมไปเอง เนื่องจากคำถามคือ C ที่เราเคยมีlog2มาตั้งแต่ C99
Jonathan Mee

2

คิดว่าตัวดำเนินการระดับบิต

ฉันไม่เข้าใจคำถามในครั้งแรก คุณควรสร้าง int ด้วยชุดบิตทางซ้ายสุด (ค่าอื่น ๆ เป็นศูนย์) สมมติว่า cmp ถูกตั้งค่าเป็นค่านั้น:

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}

การแปลงเป็นสตริงหมายความว่าอย่างไร คำจำกัดความของ ffs ใช้ int และส่งกลับค่า int การแปลงจะอยู่ที่ไหน? และการแปลงจะทำหน้าที่อะไรหากเรากำลังมองหาบิตในคำ
dreamlax

ฉันไม่รู้จักฟังก์ชันนั้น
Vasil

ควรจะเป็น8 CHAR_BITวิธีนี้ไม่น่าจะเป็นวิธีที่เร็วที่สุดเนื่องจากการคาดเดาผิดสาขาจะเกิดขึ้นเมื่อออกจากลูปเว้นแต่จะใช้กับอินพุตเดิมซ้ำ ๆ นอกจากนี้สำหรับอินพุตขนาดเล็ก (ศูนย์จำนวนมาก) จะต้องวนซ้ำมาก นี่เป็นเหมือนวิธีสำรองที่คุณใช้เป็นเวอร์ชันที่ตรวจสอบได้ง่ายในการทดสอบหน่วยเพื่อเปรียบเทียบกับเวอร์ชันที่ปรับให้เหมาะสม
Peter Cordes

2

การขยายมาตรฐานของ Josh ... เราสามารถปรับปรุง clz ได้ดังนี้

/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

เกี่ยวกับ asm: โปรดทราบว่ามี bsr และ bsrl (นี่คือเวอร์ชัน "ยาว") ปกติอาจเร็วกว่าเล็กน้อย


1

โปรดทราบว่าสิ่งที่คุณพยายามทำคือคำนวณ log2 จำนวนเต็มของจำนวนเต็ม

#include <stdio.h>
#include <stdlib.h>

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

สังเกตว่าคุณพยายามค้นหาได้มากกว่า 1 บิตในแต่ละครั้ง

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

แนวทางนี้ใช้การค้นหาแบบไบนารี

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

วิธีการค้นหาแบบไบนารีอีกวิธีหนึ่งซึ่งอาจอ่านได้ง่ายกว่า

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

และเนื่องจากคุณต้องการทดสอบสิ่งเหล่านี้

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}

1

การใส่สิ่งนี้เนื่องจากเป็นวิธีการ 'ยังเป็นอีกวิธีหนึ่ง' ดูเหมือนว่าจะแตกต่างจากที่ให้ไว้

ส่งกลับ-1ถ้าx==0เป็นอย่างอื่นfloor( log2(x)) (ผลลัพธ์สูงสุด 31)

ลดปัญหาจาก 32 เป็น 4 บิตจากนั้นใช้ตาราง บางทีอาจจะไม่สง่างาม แต่ในทางปฏิบัติ

นี่คือสิ่งที่ฉันใช้เมื่อไม่ต้องการใช้__builtin_clzเนื่องจากปัญหาการพกพา

เพื่อให้กะทัดรัดยิ่งขึ้นเราสามารถใช้ลูปเพื่อลดแทนโดยเพิ่ม 4 ถึง r ในแต่ละครั้งโดยทำซ้ำได้สูงสุด 7 ครั้ง หรือไฮบริดบางตัวเช่น (สำหรับ 64 บิต): วนซ้ำเพื่อลดเป็น 8 ทดสอบเพื่อลดเป็น 4

int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}

1

ว้าวนั่นคือคำตอบมากมาย ฉันไม่เสียใจที่ตอบคำถามเก่า

int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

คำตอบนี้ค่อนข้างคล้ายกับคำตอบอื่น ...


การเขียนจำนวนกะเป็น1<<kสัมผัสที่ดี แล้วหน้ากากล่ะ? (1 << (1<<k-1)-1<< (1<<k-1)? ( most optimal? คุณเปรียบเทียบขั้นสุดยอด?)
greybeard

@greybeard หากคุณดูการแก้ไขของคำถามนี้คุณจะเห็นเมื่อฉันเพิ่มส่วนที่ "เหมาะสมที่สุด" ฉันลืมลบออกเนื่องจากเปลี่ยนคำตอบ นอกจากนี้ผมไม่แน่ใจว่าคุณจะมีการพูดคุยเกี่ยวกับหน้ากาก? (หน้ากากอะไรฉันไม่ได้ติดตามคุณ)
Harry Svensson

( บิต) หน้ากาก (เป็นค่าใช้ในการเลือก / บิตชัดเจนคัดเลือก / ใช้ใน&และ&~.) ((type)1<<(1<<k))-1<<(1<<k)คุณสามารถแทนที่ค่าคงที่ฐานสิบหกโดยชอบของ
greybeard

โอ้ใช่ฉันใช้มาสก์ฉันลืมไปเลย ฉันได้ตอบคำถามนี้เมื่อสองสามเดือนที่แล้ว ... - อืมดีเนื่องจากมีการประเมินในช่วงเวลารวบรวมฉันบอกว่ามันเทียบเท่ากับค่าฐานสิบหก อย่างไรก็ตามหนึ่งเป็นความลับและหนึ่งเป็นเลขฐานสิบหก
Harry Svensson

0

รหัส:

    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf( "The left-most non zero bit of %d is bit %d\n", x, p);
    }

หรือรับส่วนจำนวนเต็มของคำสั่ง FPU FYL2X (Y * Log2 X) โดยการตั้งค่า Y = 1


uhhhhh อะไร? ฟังก์ชันนี้เป็นอย่างไร พกพาไปในทางใดได้บ้าง?
underscore_d

รหัสในหน้าต่างเป็นแบบพกพา ฟังก์ชัน FYL2X () เป็นคำสั่ง fpu แต่อาจถูกพอร์ตและอาจพบได้ในไลบรารี FPU / math บางตัว
jemin

@underscore_d มันใช้งานได้เนื่องจากตัวเลขทศนิยมถูกทำให้เป็นมาตรฐาน ... การแปลงเป็นการเลื่อนบิตแมนทิสซาสองครั้งเพื่อกำจัดเลขศูนย์นำหน้าและรหัสนี้จะแยกเลขชี้กำลังและปรับเพื่อกำหนดจำนวนบิตที่เลื่อน แน่นอนว่ามันไม่ได้ขึ้นอยู่กับสถาปัตยกรรม แต่มันอาจจะใช้ได้กับทุกเครื่องที่คุณเจอ
Jim Balter

นี่เป็นเวอร์ชันอื่นของคำตอบนี้โปรดดูความคิดเห็นเกี่ยวกับประสิทธิภาพและความสามารถในการพกพาได้ที่นั่น (โดยเฉพาะการไม่สามารถเคลื่อนย้ายตัวชี้สำหรับการพิมพ์แบบเจาะได้) มันใช้การคำนวณที่อยู่เพื่อโหลดซ้ำเฉพาะ 32 บิตที่doubleสูงซึ่งน่าจะดีถ้ามันจัดเก็บ / โหลดซ้ำแทนการพิมพ์ปุนด้วยวิธีอื่นเช่น ด้วยmovqคำสั่งเช่นคุณจะได้รับที่นี่บน x86
Peter Cordes

นอกจากนี้ยังทราบ [คิดเห็นในการตอบว่า] ของฉันที่ฉันให้ตกระกำลำบากเตือนว่าวิธีการนี้จะช่วยให้คำตอบที่ถูกต้องสำหรับค่าใน (อย่างน้อย) [7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF]ช่วง
Glenn Slayden

0

โปสเตอร์อื่นให้ตารางการค้นหาโดยใช้การค้นหาแบบกว้างไบต์ ในกรณีที่คุณต้องการที่จะยืดออกบิตประสิทธิภาพมากขึ้น (ที่ค่าใช้จ่ายของ 32K ของหน่วยความจำแทนเพียง 256 รายการการค้นหา) นี่เป็นวิธีการแก้ปัญหาโดยใช้ตารางการค้นหา 15 บิตในC # 7สำหรับ.NET

ส่วนที่น่าสนใจคือการเริ่มต้นตาราง Marshal.AllocHGlobalเนื่องจากเป็นบล็อกขนาดค่อนข้างเล็กที่เราต้องการสำหรับชีวิตของกระบวนการที่ฉันจัดสรรหน่วยความจำที่ไม่มีการจัดการสำหรับการนี้โดยใช้ ดังที่คุณเห็นเพื่อประสิทธิภาพสูงสุดตัวอย่างทั้งหมดจะถูกเขียนเป็นเนทีฟ:

readonly static byte[] msb_tab_15;

// Initialize a table of 32768 bytes with the bit position (counting from LSB=0)
// of the highest 'set' (non-zero) bit of its corresponding 16-bit index value.
// The table is compressed by half, so use (value >> 1) for indexing.
static MyStaticInit()
{
    var p = new byte[0x8000];

    for (byte n = 0; n < 16; n++)
        for (int c = (1 << n) >> 1, i = 0; i < c; i++)
            p[c + i] = n;

    msb_tab_15 = p;
}

ตารางต้องการการเริ่มต้นเพียงครั้งเดียวผ่านรหัสด้านบน เป็นแบบอ่านอย่างเดียวดังนั้นจึงสามารถแชร์สำเนาส่วนกลางหนึ่งชุดสำหรับการเข้าถึงพร้อมกัน ด้วยตารางนี้คุณสามารถค้นหาบันทึกจำนวนเต็ม2ได้อย่างรวดเร็วซึ่งเป็นสิ่งที่เรากำลังมองหาที่นี่สำหรับความกว้างของจำนวนเต็มต่างๆ (8, 16, 32 และ 64 บิต)

ขอให้สังเกตว่ารายการตารางสำหรับ0การจำนวนเต็ม แต่เพียงผู้เดียวซึ่งความคิดของ 'ชุดบิตสูงสุด' -1จะไม่ได้กำหนดจะได้รับความคุ้มค่า ความแตกต่างนี้จำเป็นสำหรับการจัดการคำด้านบนที่มีค่า 0 อย่างเหมาะสมในโค้ดด้านล่าง โดยไม่ต้องกังวลใจอีกต่อไปนี่คือรหัสสำหรับอนุพันธ์จำนวนเต็มต่างๆ:

ulong (64 บิต) เวอร์ชัน

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(this ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 0x40) - 1;      // handles cases v==0 and MSB==63

    int j = /**/ (int)((0xFFFFFFFFU - v /****/) >> 58) & 0x20;
    j |= /*****/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

uint (32 บิต) เวอร์ชัน

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(uint v)
{
    if ((int)v <= 0)
        return (int)((v >> 26) & 0x20) - 1;     // handles cases v==0 and MSB==31

    int j = (int)((0x0000FFFFU - v) >> 27) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

เกินพิกัดต่างๆสำหรับข้างต้น

public static int HighestOne(long v) => HighestOne((ulong)v);
public static int HighestOne(int v) => HighestOne((uint)v);
public static int HighestOne(ushort v) => msb_tab_15[v >> 1];
public static int HighestOne(short v) => msb_tab_15[(ushort)v >> 1];
public static int HighestOne(char ch) => msb_tab_15[ch >> 1];
public static int HighestOne(sbyte v) => msb_tab_15[(byte)v >> 1];
public static int HighestOne(byte v) => msb_tab_15[v >> 1];

นี่เป็นโซลูชันการทำงานที่สมบูรณ์ซึ่งแสดงถึงประสิทธิภาพที่ดีที่สุดบน. NET 4.7.2 สำหรับทางเลือกมากมายที่ฉันเปรียบเทียบกับสายรัดทดสอบประสิทธิภาพเฉพาะ บางส่วนมีการระบุไว้ด้านล่าง พารามิเตอร์การทดสอบคือความหนาแน่นสม่ำเสมอของตำแหน่ง 65 บิตทั้งหมดนั่นคือ0 ... 31/63ค่าบวก0(ซึ่งให้ผลลัพธ์ -1) บิตด้านล่างตำแหน่งดัชนีเป้าหมายถูกเติมแบบสุ่ม การทดสอบเป็นแบบx64เท่านั้นโหมดรีลีสโดยเปิดใช้งานการปรับแต่ง JIT




นั่นคือจุดสิ้นสุดของคำตอบที่เป็นทางการของฉันที่นี่ สิ่งต่อไปนี้คือบันทึกย่อบางส่วนและลิงก์ไปยังซอร์สโค้ดสำหรับผู้สมัครทดสอบทางเลือกที่เกี่ยวข้องกับการทดสอบที่ฉันดำเนินการเพื่อตรวจสอบประสิทธิภาพและความถูกต้องของโค้ดด้านบน


เวอร์ชันที่ระบุไว้ข้างต้นซึ่งมีรหัสว่า Tab16A เป็นผู้ชนะที่สม่ำเสมอในการวิ่งหลายครั้ง ผู้สมัครต่าง ๆ เหล่านี้ในรูปแบบการทำงาน / รอยขีดข่วนใช้งานสามารถพบได้ที่นี่ , ที่นี่และที่นี่

 ผู้สมัคร 1 คน HighestOne_Tab16A 622,496
 ผู้สมัคร 2 คน HighestOne_Tab16C 628,234
 ผู้สมัคร 3 คน HighestOne_Tab8A 649,146
 ผู้สมัคร 4 คน HighestOne_Tab8B 656,847
 ผู้สมัคร 5 คน HighestOne_Tab16B 657,147
 ผู้สมัคร 6 คน HighestOne_Tab16D 659,650
 7 _highest_one_bit_UNMANAGED.HighestOne_U 702,900
 8 de_Bruijn.IndexOfMSB 709,672
 9 _old_2.HighestOne_Old2 715,810
10 _test_A.HighestOne8 757,188
11 _old_1.HighestOne_Old1 757,925
12 _test_A.HighestOne5 (ไม่ปลอดภัย) 760,387
13 _test_B.HighestOne8 (ไม่ปลอดภัย) 763,904
14 _test_A.HighestOne3 (ไม่ปลอดภัย) 766,433
15 _test_A.HighestOne1 (ไม่ปลอดภัย) 767,321
16 _test_A.HighestOne4 (ไม่ปลอดภัย) 771,702
17 _test_B.HighestOne2 (ไม่ปลอดภัย) 772,136
18 _test_B.HighestOne1 (ไม่ปลอดภัย) 772,527
19 _test_B.HighestOne3 (ไม่ปลอดภัย) 774,140
20 _test_A.HighestOne7 (ไม่ปลอดภัย) 774,581
21 _test_B.HighestOne7 (ไม่ปลอดภัย) 775,463
22 _test_A.HighestOne2 (ไม่ปลอดภัย) 776,865
ผู้สมัคร 23 คน HighestOne_NoTab 777,698
24 _test_B.HighestOne6 (ไม่ปลอดภัย) 779,481
25 _test_A.HighestOne6 (ไม่ปลอดภัย) 781,553
26 _test_B.HighestOne4 (ไม่ปลอดภัย) 785,504
27 _test_B.HighestOne5 (ไม่ปลอดภัย) 789,797
28 _test_A.HighestOne0 (ไม่ปลอดภัย) 809,566
29 _test_B.HighestOne0 (ไม่ปลอดภัย) 814,990
30 _highest_one_bit.HighestOne 824,345
30 _bitarray_ext.RtlFindMostSignificantBit 894,069
ผู้สมัคร 31 คน HighestOne_Naive 898,865

ที่น่าสังเกตคือประสิทธิภาพที่แย่มากของ ntdll.dll!RtlFindMostSignificantBitP / Invoke:

[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);

มันแย่มากเพราะนี่คือฟังก์ชันที่แท้จริงทั้งหมด:

    RtlFindMostSignificantBit:
        bsr rdx, rcx  
        mov eax,0FFFFFFFFh  
        movzx ecx, dl  
        cmovne      eax,ecx  
        ret

ฉันนึกภาพไม่ออกว่าประสิทธิภาพที่ไม่ดีนั้นเกิดจากห้าบรรทัดนี้ดังนั้นบทลงโทษการเปลี่ยนแปลงที่มีการจัดการ / เนทีฟจึงต้องถูกตำหนิ ฉันยังแปลกใจที่การทดสอบชอบshortตารางค้นหาโดยตรง32KB (และ 64KB) (16 บิต) ในตารางการค้นหาขนาด 128 ไบต์ (และ 256 ไบต์) byte(8 บิต) ฉันคิดว่าสิ่งต่อไปนี้จะสามารถแข่งขันได้มากขึ้นกับการค้นหาแบบ 16 บิต แต่อย่างหลังทำได้ดีกว่าสิ่งนี้อย่างต่อเนื่อง:

public static int HighestOne_Tab8A(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    int j;
    j =  /**/ (int)((0xFFFFFFFFU - v) >> 58) & 32;
    j += /**/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 16;
    j += /**/ (int)((0x000000FFU - (v >> j)) >> 60) & 8;
    return j + msb_tab_8[v >> j];
}

สิ่งสุดท้ายที่ฉันจะชี้ให้เห็นคือฉันค่อนข้างตกใจที่วิธีการ deBruijn ของฉันไม่ได้ดีไปกว่านี้ นี่เป็นวิธีที่ฉันเคยใช้อย่างแพร่หลายก่อนหน้านี้:

const ulong N_bsf64 = 0x07EDD5E59A4E28C2,
            N_bsr64 = 0x03F79D71B4CB0A89;

readonly public static sbyte[]
bsf64 =
{
    63,  0, 58,  1, 59, 47, 53,  2, 60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12, 44, 24, 15,  8, 23,  7,  6,  5,
},
bsr64 =
{
     0, 47,  1, 56, 48, 27,  2, 60, 57, 49, 41, 37, 28, 16,  3, 61,
    54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11,  4, 62,
    46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
    25, 39, 14, 33, 19, 30,  9, 24, 13, 18,  8, 12,  7,  6,  5, 63,
};

public static int IndexOfLSB(ulong v) =>
    v != 0 ? bsf64[((v & (ulong)-(long)v) * N_bsf64) >> 58] : -1;

public static int IndexOfMSB(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    v |= v >> 1; v |= v >> 2;  v |= v >> 4;   // does anybody know a better
    v |= v >> 8; v |= v >> 16; v |= v >> 32;  // way than these 12 ops?
    return bsr64[(v * N_bsr64) >> 58];
}

มีการอภิปรายเกี่ยวกับวิธีการ deBruijn ที่ยอดเยี่ยมและยอดเยี่ยมในคำถาม SO นี้และฉันก็มักจะเห็นด้วย การคาดเดาของฉันคือในขณะที่ทั้ง deBruijn และวิธีการค้นหาตารางโดยตรง (ที่ฉันพบว่าเร็วที่สุด) ทั้งคู่ต้องทำการค้นหาตารางและทั้งคู่มีการแยกย่อยน้อยมากมีเพียง deBruijn เท่านั้นที่มีการคูณ 64 บิต ฉันทดสอบIndexOfMSBฟังก์ชั่นที่นี่เท่านั้นไม่ใช่ deBruijn IndexOfLSB- แต่ฉันคาดว่ารุ่นหลังจะมีโอกาสที่ดีกว่ามากเนื่องจากมีการทำงานน้อยลงมาก (ดูด้านบน) และฉันน่าจะใช้มันต่อไปสำหรับ LSB


1
แคช L1D บนซีพียู x86 สมัยใหม่มีเพียง 32kiB LUT ขนาดใหญ่มีแนวโน้มที่จะแย่กว่า LUT ขนาดเล็กเว้นแต่คุณจะใช้ค่าเดิมซ้ำ ๆ หากไม่เป็นเช่นนั้นคุณจะพลาดแคชบ่อยครั้ง
Peter Cordes

0

วิธีการที่ต่ำต้อยของฉันง่ายมาก:

MSB (x) = INT [Log (x) / Log (2)]

การแปล: MSB ของ x คือค่าจำนวนเต็มของ (Log of Base x หารด้วย Log of Base 2)

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


สิ่งนี้ใช้ได้ผลหากสิ่งที่คุณสนใจคือประสิทธิภาพของนักพัฒนา หากคุณต้องการประสิทธิภาพรันไทม์คุณต้องมีอัลกอริทึมอื่น
Mikko Rantalainen

อาจล้มเหลวเนื่องจากข้อผิดพลาดในการปัดเศษ ตัวอย่างเช่นใน CPython 2 และ 3 int(math.log((1 << 48) - 1) / math.log(2))คือ 48
benrg

0

นี่คือวิธีการแก้ปัญหาได้อย่างรวดเร็วสำหรับCที่ทำงานในGCCและเสียงดังกราว ; พร้อมที่จะคัดลอกและวาง

#include <limits.h>

unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

unsigned long flsl(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

unsigned long long flsll(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

และน้อยรุ่นปรับปรุงสำหรับC ++

#include <climits>

constexpr unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

constexpr unsigned long fls(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

constexpr unsigned long long fls(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

รหัสจะถือว่าvalueไม่เป็น0เช่นนั้น หากคุณต้องการอนุญาต 0 คุณต้องแก้ไข


0

ฉันถือว่าคำถามของคุณเป็นจำนวนเต็ม (เรียกว่า v ด้านล่าง) ไม่ใช่จำนวนเต็มที่ไม่ได้ลงชื่อ

int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x80000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

หากคุณต้องการให้มันใช้งานได้โดยไม่ต้องคำนึงถึงเครื่องหมายคุณสามารถเพิ่ม 'v << = 1;' พิเศษ ก่อนลูป (และเปลี่ยนค่า r เป็น 30 ตามลำดับ) โปรดแจ้งให้เราทราบหากลืมอะไร ฉันยังไม่ได้ทดสอบ แต่ควรใช้งานได้ดี


v <<= 1เป็นพฤติกรรมที่ไม่ได้กำหนด (UB) v < 0เมื่อ
chux - คืนสถานะ Monica

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