เทคนิค bit-wise ที่คุณชื่นชอบคืออะไร? [ปิด]


14

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

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

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

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

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

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

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

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

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


3
คำถามนี้และชื่อบัญชีของคุณ - โลกกลับมาสมเหตุสมผลอีกครั้ง ...
JK

+1 คำถามที่น่าสนใจตามมาด้วยการขุดและอื่น ๆ เช่นกัน;)
Anto

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

2
คุณรู้หรือไม่ว่ามีหนังสือทั้งเล่มเกี่ยวกับเทคนิคเหล่านี้ - แฮกเกอร์ Delight amazon.com/Hackers-Delight-Henry-S-Warren/dp/0201914654
nikie

ใช่มีเว็บไซต์ที่รองรับการทำงานของบิตเช่นกัน ฉันลืม URL แต่ google จะเปิดขึ้นเร็วพอ
quick_now

คำตอบ:


23

ฉันชอบแฮ็คของ Gosper (HAKMEM # 175) ซึ่งเป็นวิธีที่ฉลาดแกมโกงในการรับตัวเลขและรับหมายเลขถัดไปด้วยจำนวนบิตที่ตั้งไว้ มันมีประโยชน์เช่นในการสร้างชุดของkรายการจากnดังนี้:

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1 แต่จากนี้ไปฉันจะมีฝันร้ายเกี่ยวกับการหาสิ่งนี้ในช่วงเซสชั่นการแก้จุดบกพร่องโดยไม่ต้องแสดงความคิดเห็น
nikie

@nikie, muahahahaha! (ฉันมักจะใช้สิ่งนี้สำหรับปัญหาเช่น Project Euler - งานประจำวันของฉันไม่เกี่ยวข้องกับ combinatorics มาก)
Peter Taylor

7

วิธีสแควร์รูทแบบเร็ว inverseใช้เทคนิคระดับบิตที่แปลกประหลาดที่สุดในการคำนวณอินเวอร์สของสแควร์รูทที่ฉันเคยเห็น:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

sqrt ที่รวดเร็วก็น่าทึ่งเช่นกัน Carmack ดูเหมือนจะเป็นหนึ่งใน coder ที่ยิ่งใหญ่ที่สุด
BenjaminB

Wikipedia มีแหล่งข้อมูลที่เก่ากว่าเช่นBeyond3d.com/content/articles/15
MSalters

0

หารด้วย 3 - โดยไม่หันไปใช้การเรียกใช้ไลบรารีแบบรันไทม์

ปรากฎว่าการหารด้วย 3 (ขอบคุณคำแนะนำใน Stack Overflow) สามารถประมาณเป็น:

X / 3 = [(x / 4) + (x / 12)]

และ X / 12 คือ (x / 4) / 3 มีองค์ประกอบของการเรียกซ้ำเกิดขึ้นที่นี่ทันที

ปรากฎว่าหากคุณ จำกัด ช่วงของหมายเลขที่คุณกำลังเล่นคุณสามารถ จำกัด จำนวนการวนซ้ำที่จำเป็น

ดังนั้นสำหรับจำนวนเต็ม <2000 ที่ไม่ได้ลงชื่อต่อไปนี้เป็นอัลกอริทึมที่ง่ายและรวดเร็ว / 3 (สำหรับตัวเลขที่ใหญ่ขึ้นเพียงเพิ่มขั้นตอนเพิ่มเติม) คอมไพเลอร์ปรับแต่ง heck จากสิ่งนี้ดังนั้นมันจึงเร็วและเล็ก:

FastDivide3 แบบสั้นที่ไม่ได้ลงชื่อแบบคงที่
{
  RunningSum แบบสั้นที่ไม่ได้ลงชื่อ
  FractionalTwelth สั้นที่ไม่ได้ลงนาม;

  RunningSum = arg >> 2;

  FractionalTwelth = RunningSum >> 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  // ทำซ้ำมากกว่า 2 บรรทัดข้างต้นเพื่อความแม่นยำมากขึ้น

  คืน RunningSum;
}

1
แน่นอนว่าสิ่งนี้มีความเกี่ยวข้องกับไมโครคอนโทรลเลอร์ที่ไม่ชัดเจนมากเท่านั้น ซีพียูตัวจริงใด ๆ ที่เกิดขึ้นในช่วงสองทศวรรษที่ผ่านมาไม่จำเป็นต้องมีไลบรารีรันไทม์สำหรับการแบ่งจำนวนเต็ม
MSalters

1
โอ้แน่นอน แต่ micros ขนาดเล็กที่ไม่มีตัวคูณฮาร์ดแวร์นั้นเป็นเรื่องธรรมดามาก และถ้าคุณทำงานในที่ดินที่ฝังตัวและต้องการประหยัด $ 0.10 ในแต่ละผลิตภัณฑ์ที่ขายไปแล้วหนึ่งล้านคุณควรรู้เคล็ดลับสกปรก ๆ เงินนั้นประหยัด = กำไรพิเศษซึ่งทำให้หัวหน้าของคุณมีความสุขมาก
quick_now

สกปรก ... มันแค่คูณด้วย.0101010101(ประมาณ 1/3) เคล็ดลับสำหรับมืออาชีพ: คุณสามารถคูณด้วย.000100010001และ101(ซึ่งใช้เวลาเพียง 3 บิต แต่ยังมีการประมาณที่ดีกว่า.010101010101
MSalters

ฉันจะทำอย่างนั้นกับจำนวนเต็มเท่านั้นและไม่มีจุดลอยตัวได้อย่างไร
quick_now

1
Bitwise, x * 101 = x + x << 2. ในทำนองเดียวกัน, x * 0.000100010001 คือ x >> 4 + x >> 8 + x >> 12
MSalters

0

ถ้าคุณดูใน Erlang มี DSL ทั้งหมดสำหรับการดำเนินการบิต ดังนั้นคุณสามารถแยกโครงสร้างข้อมูลโดยบิตโดยการพูดแบบนี้:

<> = << 1,17,42: 16 >>

รายละเอียดทั้งหมดได้ที่นี่: http://www.erlang.org/doc/reference_manual/expressions.html#id75782

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