มีวิธีที่สวยงามและรวดเร็วในการทดสอบให้ 1 บิตในจำนวนเต็มอยู่ในพื้นที่ที่ติดกันหรือไม่?


84

ฉันต้องการทดสอบว่าตำแหน่ง (จาก 0 ถึง 31 สำหรับจำนวนเต็ม 32 บิต) ที่มีค่าบิต 1 เป็นพื้นที่ที่ต่อเนื่องกันหรือไม่ ตัวอย่างเช่น:

00111111000000000000000000000000      is contiguous
00111111000000000000000011000000      is not contiguous

ฉันต้องการให้การทดสอบนี้คือฟังก์ชันบางอย่างhas_contiguous_one_bits(int)เป็นแบบพกพา

วิธีหนึ่งที่ชัดเจนคือการวนซ้ำตำแหน่งเพื่อค้นหาบิตเซ็ตแรกจากนั้นบิตแรกที่ไม่ได้ตั้งค่าและตรวจสอบบิตที่กำหนดเพิ่มเติม

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

bool has_contiguous_one_bits(int val)
{
    auto h = highest_set_bit(val);
    auto l = lowest_set_bit(val);
    return val == (((1 << (h-l+1))-1)<<l);
}

เพื่อความสนุกสนานนี่คือ 100 จำนวนเต็มแรกที่มีบิตต่อเนื่องกัน:

0 1 2 3 4 6 7 8 12 14 15 16 24 28 30 31 32 48 56 60 62 63 64 96 112 120 124 126 127 128 192 224 240 248 252 254 255 256 384 448 480 496 504 508 510 511 512 768 896 960 992 1008 1016 1020 1022 1023 1024 1536 1792 1920 1984 2016 2032 2040 2044 2046 2047 2048 3072 3584 3840 3968 4032 4064 4080 4088 4092 4094 4095 4096 6144 7168 7680 7936 8064 8128 8160 8176 8184 8188 8190 8191 8192 12288 14336 15360 15872 16128 16256 16320

พวกเขามี (แน่นอน) ของแบบฟอร์ม(1<<m)*(1<<n-1)ที่มีไม่ใช่เชิงลบและmn


4
@aafulei ใช่0x0มีขนาดกะทัดรัด ง่ายกว่าที่จะกำหนดสิ่งที่ตรงกันข้าม (ไม่ใช่แบบกะทัดรัด): หากมีบิตที่ตั้งค่าไว้เช่นมีบิตที่ไม่ได้ตั้งค่าอย่างน้อยหนึ่งบิตอยู่ระหว่างพวกเขา
Walter

1
@KamilCuk h>=lโดยฟังก์ชัน (โดยนัย) ของhighest_set_bit()และlowest_set_bit()
วอลเตอร์


6
ลิงก์ OEIS นั้นบอกว่าตัวเลขเหล่านี้มีตัวเลขที่ไม่เพิ่มขึ้นเมื่ออยู่ในไบนารี อีกวิธีหนึ่งในการอ้างถึงพวกเขาคือการบอกว่าสิ่งเหล่านี้อยู่ติดกัน (หรืออาจเชื่อมต่อกัน) สำหรับนักคณิตศาสตร์คนนี้ "กะทัดรัด" หมายถึงสิ่งที่แตกต่างกันมาก
Teepeemm

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

คำตอบ:


146
static _Bool IsCompact(unsigned x)
{
    return (x & x + (x & -x)) == 0;
}

สั้น ๆ :

x & -xให้บิตต่ำสุดที่ตั้งค่าเป็นx(หรือศูนย์ถ้าxเป็นศูนย์)

x + (x & -x) แปลงสตริงต่ำสุดของ 1s ที่ติดต่อกันเป็น 1 เดียว (หรือรวมเป็นศูนย์)

x & x + (x & -x) ล้าง 1 บิตเหล่านั้น

(x & x + (x & -x)) == 0 ทดสอบว่ามี 1 บิตเหลืออยู่หรือไม่

อีกต่อไป:

-xเท่ากับ~x+1โดยใช้สองส่วนเติมเต็มซึ่งเราถือว่า หลังจากพลิกบิต~xแล้วการเพิ่ม 1 การดำเนินการเพื่อให้กลับด้าน 1 บิตต่ำ~xและ 0 บิตแรก แต่จะหยุด ดังนั้นบิตที่ต่ำ-xถึงและรวมถึง 1 แรกจะเหมือนกับบิตต่ำของxแต่บิตที่สูงกว่าทั้งหมดจะถูกพลิก (ตัวอย่าง: ~10011100ให้01100011และการเพิ่ม 1 ให้01100100ดังนั้นค่าต่ำ100จึงเท่ากัน แต่ค่าสูง10011จะถูกปัดไป01100) จากนั้นx & -xให้บิตเดียวที่เป็น 1 ในทั้งสองซึ่งต่ำสุดคือ 1 บิต ( 00000100) (ถ้าxเป็นศูนย์แสดงว่าx & -xเป็นศูนย์)

การเพิ่มสิ่งนี้เพื่อxทำให้เกิดการพกพาตลอด 1 วินาทีที่ต่อเนื่องกันโดยเปลี่ยนเป็น 0 มันจะเหลือ 1 ไว้ที่ 0 บิตที่สูงกว่าถัดไป (หรือนำไปสู่ระดับไฮเอนด์โดยปล่อยให้รวมเป็นศูนย์) ( 10100000.)

เมื่อเป็น AND ด้วยxจะมี 0 ในตำแหน่งที่ 1s เปลี่ยนเป็น 0s (และตำแหน่งที่ carry เปลี่ยน 0 เป็น a 1) ดังนั้นผลลัพธ์จะไม่เป็นศูนย์หากมีอีก 1 บิตที่สูงขึ้น


23
อย่างน้อยก็มีคนรู้จักหนังสือ Hacker's Delight โปรดดูบทที่ 2-1 สำหรับคำตอบ แต่สิ่งนี้ยังได้รับคำตอบหลายครั้งที่นี่ใน SO อย่างไรก็ตาม: +1
Armin Montigny

33
ฉันหวังว่าถ้าคุณเคยเขียนโค้ดดังกล่าวในการผลิตคุณจะรวมคำอธิบายไว้ในความคิดเห็น)
Polygnome

14
สิ่งนี้ได้รับประโยชน์อย่างมากจาก x86 BMI1 เพื่อทำx & -xในblsiคำสั่งเดียวซึ่งก็คือ 1 uop บน Intel, 2 uops บน AMD Zen godbolt.org/z/5zBx-A แต่ถ้าไม่มี BMI1 เวอร์ชันของ @ KevinZจะมีประสิทธิภาพมากยิ่งขึ้น
Peter Cordes

3
@TommyAndersen: _Boolis a standard keyword, per C 2018 6.4.1 1.
Eric Postpischil

1
@ วอลเตอร์: หืม? unsignedนี้จะใช้รหัส หากคุณต้องการที่จะดำเนินการทดสอบสำหรับสองสมบูรณ์ลงนามในintวิธีที่ง่ายที่สุดก็คือการผ่านมันไปประจำในคำตอบนี้ปล่อยให้ถูกแปลงเป็นint unsignedที่จะให้ผลลัพธ์ที่ต้องการ การนำการดำเนินการไปใช้กับการลงนามintโดยตรงอาจเป็นปัญหาได้เนื่องจากปัญหาล้น / การดำเนินการ (หากคุณต้องการทดสอบส่วนประกอบหรือเครื่องหมายและขนาดintนั่นเป็นอีกเรื่องหนึ่งซึ่งส่วนใหญ่เป็นเพียงความสนใจทางทฤษฎีในปัจจุบัน)
Eric Postpischil

29

จริงๆแล้วไม่จำเป็นต้องใช้เนื้อแท้ใด ๆ

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

bool has_compact_bits( unsigned const x )
{
    // fill up the low order zeroes
    unsigned const y = x | ( x - 1 );
    // test if the 1's is one solid block
    return not ( y & ( y + 1 ) );
}

แน่นอนว่าหากคุณต้องการใช้อินทรินนิกส์นี่คือวิธีการป๊อปนับ:

bool has_compact_bits( unsigned const x )
{
    size_t const num_bits = CHAR_BIT * sizeof(unsigned);
    size_t const sum = __builtin_ctz(x) + __builtin_popcount(x) + __builtin_clz(z);
    return sum == num_bits;
}

2
เวอร์ชันแรกจะลดเหลือเพียง 4 คำสั่งหากคอมไพล์ด้วยการ-mtbmใช้ประโยชน์blsfill/ blcfillคำแนะนำ มันจะเป็นเวอร์ชันที่สั้นที่สุดที่เสนอมา น่าเสียดายที่แทบไม่มีโปรเซสเซอร์รองรับส่วนขยายชุดคำสั่งนั้น
Giovanni Cerretani

18

จริงๆแล้วคุณไม่จำเป็นต้องนับเลขศูนย์นำหน้า ตามที่ pmg แนะนำในความคิดเห็นการใช้ประโยชน์จากความจริงที่ว่าตัวเลขที่คุณกำลังมองหานั้นเป็นตัวเลขของลำดับOEIS A023758นั่นคือตัวเลขของรูปแบบ 2 ^ i - 2 ^ j กับ i> = jคุณสามารถนับเลขศูนย์ต่อท้ายได้ ( เช่นj - 1 ) สลับบิตเหล่านั้นในค่าดั้งเดิม (เทียบเท่ากับการเพิ่ม2 ^ j - 1 ) จากนั้นตรวจสอบว่าค่านั้นอยู่ในรูปแบบ2 ^ i - 1หรือไม่ ด้วย GCC / clang intrinsics

bool has_compact_bits(int val) {
    if (val == 0) return true; // __builtin_ctz undefined if argument is zero
    int j = __builtin_ctz(val) + 1;
    val |= (1 << j) - 1; // add 2^j - 1
    val &= (val + 1); // val set to zero if of the form (2^i - 1)
    return val == 0;
}

เวอร์ชันนี้เร็วกว่าเล็กน้อยสำหรับคุณและเวอร์ชันที่เสนอโดย KamilCuk และเวอร์ชันโดย Yuri Feldman พร้อมป๊อปเคานต์เท่านั้น

หากคุณใช้ C ++ 20 คุณอาจได้รับฟังก์ชันพกพาโดยแทนที่__builtin_ctzด้วยstd::countr_zero:

#include <bit>

bool has_compact_bits(int val) {
    int j = std::countr_zero(static_cast<unsigned>(val)) + 1; // ugly cast
    val |= (1 << j) - 1; // add 2^j - 1
    val &= (val + 1); // val set to zero if of the form (2^i - 1)
    return val == 0;
}

การแคสต์นั้นน่าเกลียด แต่ขอเตือนคุณว่าควรใช้งานประเภทที่ไม่ได้ลงชื่อเมื่อจัดการกับบิต Pre-C ++ 20 ทางเลือกคือboost::multiprecision::lsb.

แก้ไข:

เกณฑ์มาตรฐานของลิงก์ขีดฆ่าถูก จำกัด โดยข้อเท็จจริงที่ว่าไม่มีการแสดงคำสั่งป๊อปเคานต์สำหรับเวอร์ชัน Yuri Feldman พยายามรวบรวมไว้ในพีซีของฉันด้วย-march=westmereฉันได้วัดเวลาต่อไปนี้สำหรับการทำซ้ำ 1 พันล้านครั้งโดยมีลำดับที่เหมือนกันจากstd::mt19937:

  • เวอร์ชันของคุณ: 5.7 วินาที
  • เวอร์ชันที่สองของ KamilCuk: 4.7 วินาที
  • เวอร์ชันของฉัน: 4.7 วินาที
  • เวอร์ชันแรกของ Eric Postpischil: 4.3 วินาที
  • เวอร์ชันของ Yuri Feldman (โดยใช้อย่างชัดเจน__builtin_popcount): 4.1 วินาที

ดังนั้นอย่างน้อยที่สุดในสถาปัตยกรรมของฉันสิ่งที่เร็วที่สุดน่าจะเป็นสถาปัตยกรรมที่มีป๊อปเคานต์

แก้ไข 2:

ฉันได้อัปเดตเกณฑ์มาตรฐานของฉันด้วยเวอร์ชันใหม่ของ Eric Postpischil ตามที่ร้องขอในความคิดเห็นที่รหัสของการทดสอบของฉันสามารถพบได้ที่นี่ ฉันได้เพิ่มลูปแบบไม่ต้องใช้เพื่อประมาณเวลาที่ PRNG ต้องการ ฉันได้เพิ่มทั้งสองเวอร์ชันโดย KevinZ รหัสได้รับการรวบรวมในเสียงดัง-O3 -msse4 -mbmiเพื่อรับpopcntและblsiคำแนะนำ (ขอบคุณ Peter Cordes)

ผลลัพธ์: อย่างน้อยในสถาปัตยกรรมของฉันเวอร์ชันของ Eric Postpischil นั้นเร็วเท่ากับรุ่นของ Yuri Feldman และเร็วกว่ารุ่นอื่น ๆ อย่างน้อยสองเท่า


ฉันลบการดำเนินการ: return (x & x + (x & -x)) == 0;.
Eric Postpischil

3
นี่เป็นการเปรียบเทียบเวอร์ชันเก่ากว่าของ @ Eric เวอร์ชันเก่าใช่ไหม กับรุ่นปัจจุบัน, คอมไพล์ของเอริคจะเป็นคำแนะนำไม่กี่gcc -O3 -march=nehalem(เพื่อให้ popcnt มี) หรือน้อยกว่าถ้า BMI1 blsiสามารถใช้ได้สำหรับการx & -x: godbolt.org/z/zuyj_f และคำแนะนำนั้นเป็นแบบ single-uop ที่เรียบง่ายยกเว้นpopcntในเวอร์ชันของ Yuri ที่มีเวลาแฝง 3 รอบ (แต่ฉันคิดว่าคุณกำลังเร่งความเร็ว) ฉันยังถือว่าคุณต้องลบออกand valจากยูริไม่งั้นมันจะช้าลง
Peter Cordes

2
นอกจากนี้ฮาร์ดแวร์ใดที่คุณเปรียบเทียบกับฮาร์ดแวร์? การเชื่อมโยงโค้ดมาตรฐานทั้งหมดของคุณบน Godbolt หรืออะไรบางอย่างน่าจะเป็นความคิดที่ดีดังนั้นผู้อ่านในอนาคตสามารถทดสอบการใช้งาน C ++ ได้อย่างง่ายดาย
Peter Cordes

2
คุณควรทดสอบเวอร์ชันของ @ KevinZ ด้วย มันรวบรวมคำแนะนำแม้แต่น้อยโดยไม่ต้อง BMI1 (อย่างน้อยด้วยเสียงดังกราว; GCC ที่ไม่ inlined เสียรุ่นmovและล้มเหลวในการใช้ประโยชน์จากlea): godbolt.org/z/5jeQLQ ด้วย BMI1 เวอร์ชันของ Eric ยังดีกว่าบน x86-64 อย่างน้อยใน Intel ที่blsiเป็น uop เดียว แต่เป็น 2 uops บน AMD
Peter Cordes

15

ไม่แน่ใจว่าเร็ว แต่สามารถทำซับเดียวได้โดยตรวจสอบว่าval^(val>>1)มีไม่เกิน 2 บิต

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

#include <bitset>
bool has_compact_bits(unsigned val)
{
    return std::bitset<8*sizeof(val)>((val ^ (val>>1))).count() <= 2;
}

ในการปฏิเสธ0(เช่นยอมรับเฉพาะอินพุตที่มีกลุ่มบิตที่อยู่ติดกัน 1 กลุ่มเท่านั้น) ตรรกะ - และvalที่ไม่ใช่ศูนย์ คำตอบอื่น ๆ สำหรับคำถามนี้ยอมรับ0ว่ามีขนาดกะทัดรัด

bool has_compact_bits(unsigned val)
{
    return std::bitset<8*sizeof(val)>((val ^ (val>>1))).count() <= 2 and val;
}

c ++ portably exposes popcount ผ่านstd::bitset::count()หรือใน C ++ 20 std::popcountผ่าน C ยังไม่มีวิธีแบบพกพาที่สามารถรวบรวมคำสั่ง popcnt หรือคำสั่งที่คล้ายกันได้อย่างน่าเชื่อถือสำหรับเป้าหมายที่มีอยู่


2
ยังเร็วที่สุดจนถึงตอนนี้
Giovanni Cerretani

2
ฉันคิดว่าคุณต้องใช้ประเภทที่ไม่ได้ลงชื่อเพื่อให้แน่ใจว่าคุณเปลี่ยนเป็นเลขศูนย์ไม่ใช่สำเนาของบิตเครื่องหมาย พิจารณา11011111. ขวาเลขคณิตขยับมันจะกลายเป็นและเป็นแฮคเกอร์11101111 00110000ด้วยการเลื่อนไปทางขวาเชิงตรรกะ (การเลื่อน0ที่ด้านบน) คุณจะได้รับ10110000และตรวจจับกลุ่มบิตหลายกลุ่มได้อย่างถูกต้อง การแก้ไขเพื่อแก้ไขปัญหานั้น
Peter Cordes

3
นี่คือความฉลาดจริงๆ เท่าที่ฉันไม่ชอบสไตล์ (IMO แค่ใช้__builtin_popcount()คอมไพเลอร์ทุกตัวมีแบบดั้งเดิมเช่นนั้นในปัจจุบัน) นี่เป็นวิธีที่เร็วที่สุด (ในซีพียูสมัยใหม่) ในความเป็นจริงฉันจะเถียงว่าการนำเสนอนั้นมีความสำคัญอย่างยิ่งเนื่องจากบน cpu ที่ไม่มี POPCNT เป็นคำสั่งเดียวการใช้งานของฉันอาจเอาชนะสิ่งนี้ได้ ดังนั้นหากคุณจะใช้การใช้งานนี้คุณควรใช้อินทรินสิก std::bitsetมีอินเทอร์เฟซที่น่ากลัว
KevinZ

9

ซีพียูมีคำสั่งเฉพาะสำหรับสิ่งนั้นเร็วมาก บนพีซีเป็น BSR / BSF (เปิดตัวใน 80386 ในปี 1985) บน ARM คือ CLZ / CTZ

ใช้หนึ่งเพื่อค้นหาดัชนีของบิตชุดที่มีนัยสำคัญน้อยที่สุดเลื่อนจำนวนเต็มไปทางขวาตามจำนวนนั้น ใช้อีกอันหนึ่งเพื่อหาดัชนีของบิตชุดที่สำคัญที่สุดเปรียบเทียบจำนวนเต็มของคุณกับ (1u << (bsr + 1)) - 1

น่าเสียดายที่ 35 ปีไม่เพียงพอที่จะอัปเดตภาษา C ++ ให้เข้ากับฮาร์ดแวร์ ในการใช้คำแนะนำเหล่านี้จาก C ++ คุณจะต้องมีอินทรินส์ซึ่งไม่ใช่แบบพกพาและส่งคืนผลลัพธ์ในรูปแบบที่แตกต่างกันเล็กน้อย ใช้ตัวประมวลผลล่วงหน้า#ifdefฯลฯ เพื่อตรวจหาคอมไพเลอร์แล้วใช้อินทรินซิคที่เหมาะสม ใน MSVC พวกเขามี_BitScanForward, _BitScanForward64, ,_BitScanReverse _BitScanReverse64ใน GCC และส่งเสียงดังพวกเขา__builtin_clzและ__builtin_ctz.


2
@ e2-e4 Visual studio ไม่รองรับการประกอบแบบอินไลน์เมื่อคอมไพล์สำหรับ AMD64 นั่นเป็นเหตุผลที่ฉันแนะนำให้ใช้ภายใน
อาทิตย์

5
ตั้งแต่ C ++ 20 มีstd::countr_zeroและstd::countl_zero. ในกรณีที่คุณกำลังใช้ Boost ก็มีห่อแบบพกพาที่เรียกว่าและboost::multiprecision::lsb boost::multiprecision::msb
Giovanni Cerretani

8
สิ่งนี้ไม่ตอบคำถามของฉันเลย - ฉันสงสัยว่าทำไมจึงได้รับการโหวตเพิ่มขึ้น
Walter

3
@ วอลเตอร์คุณหมายถึงอะไร "ไม่ตอบ"? ฉันได้รับคำตอบอย่างแม่นยำแล้วว่าคุณควรทำอย่างไรใช้ตัวประมวลผลล่วงหน้าและจากนั้นจึงสรุป
อาทิตย์

2
เห็นได้ชัดว่า C ++ 20 ในที่สุดก็เพิ่ม#include <bit> en.cppreference.com/w/cpp/header/bitด้วย bit-scan, popcount และ rotation เป็นเรื่องน่าสงสารที่ต้องใช้เวลานานขนาดนี้ในการแสดงบิตสแกนแบบพกพา แต่ตอนนี้ดีกว่าไม่เคย (Portable popcnt มีให้ใช้งานstd::bitset::count()แล้ว) C ++ 20 ยังขาดบางสิ่งที่ Rust ให้ ( doc.rust-lang.org/std/primitive.i32.html ) เช่น bit-reverse และ endian ซึ่งCPU บางตัวให้อย่างมีประสิทธิภาพ แต่ไม่ใช่ทั้งหมด แบบพกพาในตัวสำหรับการทำงานที่ซีพียูใด ๆ มีเหตุผลบางอย่างแม้ว่าผู้ใช้จะต้องรู้ว่าอะไรเร็ว
Peter Cordes

7

การเปรียบเทียบกับศูนย์แทนที่จะเป็นศูนย์จะช่วยประหยัดการดำเนินการบางอย่าง:

bool has_compact_bits2(int val) {
    if (val == 0) return true;
    int h = __builtin_clz(val);
    // Clear bits to the left
    val = (unsigned)val << h;
    int l = __builtin_ctz(val);
    // Invert
    // >>l - Clear bits to the right
    return (~(unsigned)val)>>l == 0;
}

ผลลัพธ์ต่อไปนี้ในหนึ่งคำสั่งน้อยกว่าข้างต้นgcc10 -O3บน x86_64 และใช้กับส่วนขยายเครื่องหมาย:

bool has_compact_bits3(int val) {
    if (val == 0) return true;
    int h = __builtin_clz(val);
    val <<= h;
    int l = __builtin_ctz(val);
    return ~(val>>l) == 0;
}

การทดสอบบนgodbolt


น่าเสียดายที่นี่ไม่ใช่แบบพกพา ฉันมักจะกลัวว่าจะมีลำดับความสำคัญของตัวดำเนินการผิดกับตัวดำเนินการกะเหล่านั้นคุณแน่ใจหรือ~val<<h>>h>>l == 0ไม่ว่าคุณคิดอย่างไร
Walter

4
ใช่ฉันแน่ใจแก้ไขและเพิ่มเครื่องมือจัดฟันอยู่แล้ว Och คุณสนใจโซลูชันแบบพกพาหรือไม่? เพราะฉันมองthere exists a faster way?และคิดว่าอะไร ๆ ก็เป็นไป
KamilCuk

5

คุณสามารถเรียบเรียงข้อกำหนดใหม่ได้:

  • กำหนด N จำนวนบิตที่แตกต่างจากบิตก่อนหน้า (โดยการวนซ้ำผ่านบิต)
  • ถ้า N = 2 และบิตแรกหรือสุดท้ายเป็น 0 ให้ตอบว่าใช่
  • ถ้า N = 1 ให้ตอบว่าใช่ (เพราะ 1s ทั้งหมดอยู่ด้านเดียว)
  • ถ้า N = 0 แล้วบิตใด ๆ เป็น 0 แสดงว่าคุณไม่มี 1 ขึ้นอยู่กับคุณถ้าคุณคิดว่าคำตอบคือใช่หรือไม่ใช่
  • อย่างอื่น: คำตอบคือไม่

การผ่านบิตทั้งหมดอาจมีลักษณะดังนี้:

unsigned int count_bit_changes (uint32_t value) {
  unsigned int bit;
  unsigned int changes = 0;
  uint32_t last_bit = value & 1;
  for (bit = 1; bit < 32; bit++) {
    value = value >> 1;
    if (value & 1 != last_bit  {
      changes++;
      last_bit = value & 1;
    }
  }
  return changes;
}

แต่สิ่งนี้สามารถปรับให้เหมาะสมได้อย่างแน่นอน (เช่นโดยการยกเลิกforลูปเมื่อvalueถึง0ซึ่งหมายความว่าไม่มีบิตที่สำคัญที่มีค่า 1 อยู่แล้ว)


3

คุณสามารถคำนวณตามลำดับนี้ได้ (สมมติว่าvalเป็นอินพุต):

uint32_t x = val;
x |= x >>  1;
x |= x >>  2;
x |= x >>  4;
x |= x >>  8;
x |= x >> 16;

เพื่อให้ได้ตัวเลขที่มีศูนย์ทั้งหมดด้านล่างที่1เต็มไปด้วยเลขนัยสำคัญที่สุด

คุณยังสามารถคำนวณy = val & -valเพื่อดึงข้อมูลทั้งหมดยกเว้น 1 บิตที่มีนัยสำคัญน้อยที่สุดในval(ตัวอย่างเช่น7 & -7 == 1และ12 & -12 == 4)
คำเตือน: สิ่งนี้จะล้มเหลวval == INT_MINดังนั้นคุณจะต้องจัดการกรณีนี้แยกกัน แต่จะเกิดขึ้นทันที

จากนั้นเลื่อนไปทางขวาyทีละตำแหน่งเพื่อให้ต่ำกว่า LSB จริงเล็กน้อยvalและทำกิจวัตรเดียวกันกับx:

uint32_t y = (val & -val) >> 1;
y |= y >>  1;
y |= y >>  2;
y |= y >>  4;
y |= y >>  8;
y |= y >> 16;

จากนั้นx - yหรือx & ~yหรือx ^ yสร้างมาสก์บิต 'กะทัดรัด' ที่ครอบคลุมความยาวทั้งหมดของval. เพียงเปรียบเทียบvalเพื่อดูว่าval"กะทัดรัด" หรือไม่


2

เราสามารถใช้คำแนะนำในตัว gccเพื่อตรวจสอบว่า:

จำนวนชุดบิต

int __builtin_popcount (ไม่ได้ลงชื่อ int x)
ส่งกลับจำนวน 1 บิตใน x

เท่ากับ (a - b):

: ดัชนีของบิตชุดสูงสุด (32 - CTZ) (32 เพราะ 32 บิตในจำนวนเต็มไม่ได้ลงชื่อ)

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

b : ดัชนีของบิตชุดต่ำสุด (CLZ):

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

ตัวอย่างเช่นถ้า n = 0b0001100110; เราจะได้รับ 4 พร้อมป๊อปนับ แต่ความแตกต่างของดัชนี (a - b) จะส่งกลับ 6

ซึ่งสามารถเขียนเป็น:

ฉันไม่คิดว่ามันจะหรูหราหรือมีประสิทธิภาพมากกว่าคำตอบที่โหวตมากที่สุดในปัจจุบัน:

ด้วยการประกอบดังต่อไปนี้:

mov     eax, edi
neg     eax
and     eax, edi
add     eax, edi
test    eax, edi
sete    al

แต่มันอาจจะง่ายกว่าที่จะเข้าใจ


1

โอเคนี่คือเวอร์ชันที่วนซ้ำบิต

template<typename Integer>
inline constexpr bool has_compact_bits(Integer val) noexcept
{
    Integer test = 1;
    while(!(test & val) && test) test<<=1; // skip unset bits to find first set bit
    while( (test & val) && test) test<<=1; // skip set bits to find next unset bit
    while(!(test & val) && test) test<<=1; // skip unset bits to find an offending set bit
    return !test;
}

สองลูปแรกพบพื้นที่กะทัดรัดแรก ลูปสุดท้ายตรวจสอบว่ามีบิตเซ็ตอื่นนอกเหนือจากพื้นที่นั้นหรือไม่

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