เปรียบเทียบบิตกับบูลีน


12

ว่าฉันมีชุดของค่าสถานะการเข้ารหัสใน flagsuint16_t ตัวอย่างเช่นAMAZING_FLAG = 0x02. ตอนนี้ฉันมีฟังก์ชั่น ฟังก์ชั่นนี้ต้องตรวจสอบว่าฉันต้องการเปลี่ยนธงหรือไม่เพราะถ้าฉันต้องการทำเช่นนั้นฉันต้องเขียนเป็นแฟลช และนั่นก็มีราคาแพง ดังนั้นฉันต้องการการตรวจสอบซึ่งจะบอกฉันถ้ามีค่าเท่ากับflags & AMAZING_FLAG doSetนี่เป็นความคิดแรก:

setAmazingFlag(bool doSet)
{
    if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0)) {
        // Really expensive thing
        // Update flags
    }
}

นี่ไม่ใช่คำสั่งที่ใช้งานง่าย ฉันรู้สึกว่าควรจะมีวิธีที่ดีกว่าบางอย่างเช่น:

if ((flags & AMAZING_FLAG) != doSet){

}

แต่ตอนนี้ไม่ได้ทำงานจริงน่าจะเท่ากับtrue0x01

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


2
ยังไม่ชัดเจนว่าตรรกะของคุณจำเป็นต้องเป็นอะไร มันเป็นแบบนี้: (flags & AMAZING_FLAG) && doSet?
kaylum

คำถามไม่ชัดเจน เราต้องการตรวจสอบว่า 'ค่าสถานะ' เป็น 0x01 หรือไม่ ที่คุณต้องการ? ถ้าใช่เราสามารถใช้ตัวดำเนินการ bitwise '&'
Vikas Vijayan

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

หากสิ่งที่คุณกำลังพยายามทำคือทำให้อ่านง่ายขึ้นเพียงสร้างฟังก์ชัน `ค่าสถานะ NotEqual ''
Jeroen3

คำตอบ:


18

ในการแปลงจำนวนที่ไม่เป็นศูนย์ให้เป็น 1 (จริง) มีเคล็ดลับเก่า: ใช้!โอเปอเรเตอร์ (ไม่) สองครั้ง

if (!!(flags & AMAZING_FLAG) != doSet){

4
หรือ(bool)(flags & AMAZING_FLAG) != doSet- ซึ่งฉันค้นหาโดยตรงมากขึ้น (แม้ว่าสิ่งนี้จะแนะนำว่า Mr Microsoft มีปัญหาในเรื่องนี้) นอกจากนี้ยัง((flags & AMAZING_FLAG) != 0)อาจเป็นสิ่งที่รวบรวมและชัดเจนอย่างแน่นอน
Chris Hall

"นอกจากนี้ ((ธง & AMAZING_FLAG)! = 0) น่าจะเป็นสิ่งที่รวบรวมและชัดเจนอย่างแน่นอน" มันจะ? เกิดอะไรขึ้นถ้า doSet เป็นจริง
Cheiron

@ChrisHall (bool) 2 ส่งผลให้ 1 หรือยังเป็น 2 ใน C หรือไม่
user253751

3
C11 Standard, 6.3.1.2 ประเภทบูลีน: "เมื่อค่าสเกลาร์ใด ๆ ถูกแปลงเป็น _Bool ผลลัพธ์จะเป็น 0 ถ้าค่าเปรียบเทียบเท่ากับ 0 มิฉะนั้นผลลัพธ์จะเป็น 1" 6.5.4 ตัวดำเนินการคาสต์: "นำหน้านิพจน์ด้วยชื่อประเภทวงเล็บจะแปลงค่าของนิพจน์เป็นประเภทที่มีชื่อ"
Chris Hall

@Cheiron จะชัดเจน: ((bool)(flags & AMAZING_FLAG) != doSet)มีผลเช่นเดียวกับ(((flags & AMAZING_FLAG) != 0) != doSet)และพวกเขาทั้งสองอาจจะรวบรวมเพื่อสิ่งเดียวกัน ข้อเสนอแนะ(!!(flags & AMAZING_FLAG) != doSet)นั้นเทียบเท่ากันและฉันจินตนาการว่าจะรวบรวมสิ่งเดียวกันด้วย เป็นเรื่องของรสนิยมที่คุณคิดว่าชัดเจนขึ้น(bool)นักแสดงต้องการให้คุณจำได้ว่าการปลดเปลื้องและการแปลงไปยัง _Bool นั้นทำงานอย่างไร !!ต้องมียิมนาสติกจิตอื่น ๆ หรือสำหรับคุณที่จะรู้ว่า "หลอกลวง"
Chris Hall

2

คุณต้องแปลง bit mask เป็นคำสั่ง boolean ซึ่งใน C เท่ากับค่า 01หรือ

  • (flags & AMAZING_FLAG) != 0. วิธีที่พบมากที่สุด

  • !!(flags & AMAZING_FLAG). ค่อนข้างธรรมดาก็โอเคที่จะใช้ แต่เป็นความลับเล็กน้อย

  • (bool)(flags & AMAZING_FLAG). Modern C ทางจาก C99 ขึ้นไปเท่านั้น

ดำเนินการใด ๆ ทางเลือกข้างต้นแล้วเปรียบเทียบกับบูลของคุณโดยใช้หรือ!===


1

จากมุมมองแบบลอจิคัลflags & AMAZING_FLAGจะเป็นการดำเนินการเพียงเล็กน้อยที่ปิดบังแฟล็กอื่นทั้งหมด ผลลัพธ์คือค่าตัวเลข

ในการรับค่าบูลีนคุณจะต้องใช้การเปรียบเทียบ

(flags & AMAZING_FLAG) == AMAZING_FLAG

doSetและตอนนี้สามารถเปรียบเทียบค่าตรรกะนี้

if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)

ใน C อาจมีตัวย่อเนื่องจากกฎการแปลงโดยนัยของตัวเลขเป็นค่าบูลีน ดังนั้นคุณสามารถเขียน

if (!(flags & AMAZING_FLAG) == doSet)

เพื่อเขียนสั้น ๆ ที่มากขึ้น แต่รุ่นก่อนดีกว่าในแง่ของการอ่าน


1

คุณสามารถสร้างมาสก์ตามdoSetค่า:

#define AMAZING_FLAG_IDX 1
#define AMAZING_FLAG (1u << AMAZING_FLAG_IDX)
...

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

ตอนนี้เช็คของคุณจะเป็นดังนี้:

setAmazingFlag(bool doSet)
{
    const uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

    if (flags & set_mask) {
        // Really expensive thing
        // Update flags
    }
}

ในบางสถาปัตยกรรม!!อาจถูกรวบรวมไปยังสาขาและด้วยสิ่งนี้คุณอาจมีสองสาขา:

  1. การทำให้เป็นมาตรฐานโดย !!(expr)
  2. เปรียบเทียบกับ doSet

ข้อได้เปรียบของข้อเสนอของฉันคือการรับประกันสาขาเดียว

หมายเหตุ:ตรวจสอบให้แน่ใจว่าคุณไม่ได้แนะนำพฤติกรรมที่ไม่ได้กำหนดโดยเลื่อนไปทางซ้ายมากกว่า 30 (สมมติว่าเป็นจำนวนเต็ม 32 บิต) สามารถทำได้โดยง่ายstatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");


1
การแสดงออกบูลีนทุกประเภทมีศักยภาพที่จะส่งผลให้เกิดสาขา ฉันจะแยกชิ้นส่วนก่อนก่อนที่จะใช้เทคนิคการเพิ่มประสิทธิภาพด้วยตนเองที่แปลก
Lundin

1
@Lundin แน่นอนว่าทำไมฉันถึงเขียนว่า "ในบางสถาปัตยกรรม" ...
Alex Lop

1
ส่วนใหญ่เป็นเรื่องของการเพิ่มประสิทธิภาพของคอมไพเลอร์และไม่มาก ISA เอง
Lundin

1
@Lundin ฉันไม่เห็นด้วย สถาปัตยกรรมบางอย่างมี ISA สำหรับการตั้งค่า / ตั้งค่าบิตแบบมีเงื่อนไขซึ่งไม่จำเป็นต้องใช้ brach godbolt.org/z/4FDzvw
Alex Lop

หมายเหตุ: หากคุณทำสิ่งนี้โปรดกำหนดAMAZING_FLAGในแง่ของAMAZING_FLAG_IDX(เช่น#define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))) ดังนั้นคุณจึงไม่มีข้อมูลเดียวกันที่กำหนดไว้ในสองสถานที่ซึ่งสามารถอัปเดตได้ (พูดจาก0x4ถึง0x8) ในขณะที่อีกอัน ( IDXจาก2) ไม่มีการเปลี่ยนแปลง .
ShadowRanger

-1

การทดสอบนี้มีหลายวิธี:

ผู้ประกอบการที่ประกอบไปด้วยสามอาจสร้างการกระโดดที่มีราคาแพง:

if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0))

คุณยังสามารถใช้การแปลงบูลีนซึ่งอาจจะมีประสิทธิภาพหรือไม่:

if (!!(flags & AMAZING_FLAG) != doSet)

หรือทางเลือกเทียบเท่า:

if (((flags & AMAZING_FLAG) != 0) != doSet)

หากการคูณมีราคาถูกคุณสามารถหลีกเลี่ยงการกระโดดด้วย:

if ((flags & AMAZING_FLAG) != doSet * AMAZING_FLAG)

หากflagsไม่ได้ลงนามและคอมไพเลอร์ฉลาดมากการแบ่งด้านล่างอาจรวบรวมเป็นการเปลี่ยนแปลงอย่างง่าย:

if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)

หากสถาปัตยกรรมใช้เลขคณิตประกอบสองตัวนี่เป็นอีกทางเลือกหนึ่ง:

if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))

อีกวิธีหนึ่งสามารถกำหนดflagsเป็นโครงสร้างด้วย bitfields และใช้ไวยากรณ์ที่อ่านง่ายขึ้นมาก:

if (flags.amazing_flag != doSet)

อนิจจาวิธีการนี้มักจะขมวดคิ้วเนื่องจากข้อมูลจำเพาะของ bitfields ไม่อนุญาตให้มีการควบคุมที่แม่นยำในการใช้งานระดับบิต


เป้าหมายหลักของรหัสใด ๆ ที่ควรอ่านได้ซึ่งตัวอย่างส่วนใหญ่ไม่ใช่ ปัญหาที่ใหญ่กว่านั้นเกิดขึ้นเมื่อคุณโหลดตัวอย่างของคุณลงในคอมไพเลอร์: การใช้งานตัวอย่างสองครั้งแรกมีประสิทธิภาพที่ดีที่สุด: จำนวนกระโดดและโหลดน้อยที่สุด (ARM 8.2, -Os) (เทียบเท่ากัน) การใช้งานอื่น ๆ ทั้งหมดนั้นแย่ลง โดยทั่วไปแล้วเราควรหลีกเลี่ยงการทำสิ่งที่แปลกใหม่ (การเพิ่มประสิทธิภาพแบบไมโคร) เพราะคุณจะทำให้คอมไพเลอร์รู้ได้ยากขึ้นว่าอะไรกำลังเกิดขึ้นซึ่งทำให้ประสิทธิภาพลดลง ดังนั้น -1
Cheiron
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.