วิธีการเขียนฐานบันทึก (2) ใน c / c ++


99

มีวิธีใดในการเขียนฟังก์ชันบันทึก (ฐาน 2) หรือไม่?

ภาษา C มี 2 ฟังก์ชันในตัว - >>

1. logซึ่งเป็นฐาน e.

2. log10ฐาน 10;

แต่ฉันต้องการฟังก์ชันบันทึกของฐาน 2 วิธีคำนวณสิ่งนี้


1
สำหรับการคำนวณลูกตาลอการิทึมฐาน 2 จะใกล้เคียงกับลอการิทึมฐาน 10 บวกกับลอการิทึมธรรมชาติ เห็นได้ชัดว่าการเขียนเวอร์ชันที่ถูกต้อง (และเร็วกว่า) ในโปรแกรมจะดีกว่า
David Thornley

สำหรับจำนวนเต็มคุณสามารถวนซ้ำบน bitshift ทางขวาและหยุดเมื่อถึง 0 การนับลูปเป็นการประมาณของบันทึก
Basile Starynkevitch

คำตอบ:


202

คณิตศาสตร์ง่ายๆ:

    บันทึก2 ( x ) = บันทึกy ( x ) / บันทึกy (2)

ที่ปีสามารถเป็นอะไรก็ได้ซึ่งสำหรับฟังก์ชั่นบันทึกมาตรฐานเป็นทั้ง 10 หรืออี



53

หากคุณกำลังมองหาผลลัพธ์ที่สำคัญคุณสามารถกำหนดบิตสูงสุดที่ตั้งไว้ในค่าและส่งคืนตำแหน่งได้


27
นอกจากนี้ยังมีวิธีที่น่าสนใจสำหรับสิ่งนี้ (นำมาจากInteger.highestOneBit(int)วิธีการของ Java ):i |= (i >> 1); i |= (i >> 2); i |= (i >> 4); i |= (i >> 8); i |= (i >> 16); return i - (i >>> 1);
Joey

39
... หรือwhile (i >>= 1) { ++l; }
Lee Daniel Crocker

2
@ Joey นั่นจะใช้ได้โดยสมมติว่าจำนวนเต็มกว้าง 32 บิตไม่ใช่หรือ สำหรับ 64 i>>32บิตมันจะมีเป็นพิเศษ แต่เนื่องจาก Java มี ints เพียง 32 บิตจึงใช้ได้ สำหรับ C / C ++ จำเป็นต้องพิจารณา
Zoso

43
#define M_LOG2E 1.44269504088896340736 // log2(e)

inline long double log2(const long double x){
    return log(x) * M_LOG2E;
}

(การคูณอาจเร็วกว่าการหาร)


1
แค่อยากจะชี้แจง - ใช้กฎการแปลงบันทึก + ข้อเท็จจริงที่ว่า log_2 (e) = 1 / log_e (2) -> เราได้ผลลัพธ์
Guy L


9

ตามที่ระบุไว้ในhttp://en.wikipedia.org/wiki/Logarithm :

logb(x) = logk(x) / logk(b)

ซึ่งหมายความว่า:

log2(x) = log10(x) / log10(2)

11
โปรดทราบว่าคุณสามารถคำนวณ log10 (2) ล่วงหน้าเพื่อเพิ่มประสิทธิภาพได้
corsiKa

@Johannes: ฉันสงสัยว่าคอมไพเลอร์จะคำนวณ log10 (2) ล่วงหน้า คอมไพเลอร์ไม่ทราบว่า log10 จะคืนค่าเหมือนเดิมทุกครั้ง สำหรับคอมไพเลอร์ทั้งหมดทราบ log10 (2) สามารถส่งคืนค่าที่แตกต่างกันในการเรียกต่อเนื่อง
abelenky

@abelenky: โอเคฉันเอาคืน เนื่องจากคอมไพเลอร์ไม่เคยเห็นแหล่งที่มาของlog()การนำไปใช้งานจึงจะไม่ทำ ความผิดฉันเอง.
Joey

3
@abelenky: เนื่องจากlog10()เป็นฟังก์ชันที่กำหนดไว้ในมาตรฐาน C คอมไพเลอร์จึงมีอิสระที่จะปฏิบัติต่อ "พิเศษ" รวมถึงการคำนวณผลลัพธ์ล่วงหน้าซึ่งฉันเชื่อว่าเป็นคำแนะนำของ @Johannes
คาเฟ่

1
@CarlNorum: ฉันเพิ่งตรวจสอบและอย่างน้อย gcc 4.7 ก็แทนที่log10(2)ด้วยค่าคงที่
caf

8

หากคุณต้องการทำให้เร็วคุณสามารถใช้ตารางค้นหาเช่นในBit Twiddling Hacks (log2 จำนวนเต็มเท่านั้น)

uint32_t v; // find the log base 2 of 32-bit v
int r;      // result goes here

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

v |= v >> 1; // first round down to one less than a power of 2 
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;

r = MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27];

นอกจากนี้คุณควรดูวิธีการในตัวของคอมไพเลอร์_BitScanReverseซึ่งอาจเร็วกว่าเนื่องจากอาจคำนวณทั้งหมดในฮาร์ดแวร์

ลองดูการทำซ้ำที่เป็นไปได้วิธีทำ log2 จำนวนเต็ม () ใน C ++ ได้อย่างไร?


ทำไมต้องมีการคูณและค้นหาตารางในตอนท้าย? คุณไม่สามารถทำ (v + 1) ซึ่งจะปัดเศษขึ้นเป็นสองกำลังถัดไป จากนั้นคุณสามารถเลื่อนไปทางขวาเพื่อปัดลงไปที่กำลังถัดไปของ 2
Safayet Ahmed

@SafayetAhmed โปรดอธิบายวิธีที่คุณต้องการค้นหา log2 ของตัวเลขด้วยวิธีการนั้น ฉันไม่รู้วิธีที่ง่ายกว่าในการรับค่านั้น นอกเหนือจากการใช้เลขคณิตด้านบนกับตารางการค้นหาแล้วเราสามารถใช้อัลกอริธึมซ้ำ / เรียกซ้ำหรือใช้ฮาร์ดแวร์เฉพาะ / ในตัวเพื่อทำการคำนวณ
bkausbk

สมมติว่าบิตของตัวแปร 32 บิต v มีเลข 0 (LSB) ถึง N (MSB) บอกว่าบิตชุดที่สำคัญที่สุดของ v คือ n จะถูกต้องหรือไม่หากจะบอกว่า n แทนพื้น (log2 (v)) คุณไม่สนใจที่จะหา n ให้ v หรือ?
Safayet Ahmed

ฉันรู้ว่าสิ่งที่ฉันอธิบายจะให้กำลังต่ำสุดที่ใกล้ที่สุดของ 2 ไม่ใช่ลอการิทึมจริง การคูณและการค้นหาตารางมีไว้สำหรับเปลี่ยนจากกำลังสองไปเป็นลอการิทึม คุณกำลังขยับหมายเลข 0x07C4ACDD ที่เหลืออยู่จำนวนหนึ่ง จำนวนที่คุณเลื่อนไปทางซ้ายจะขึ้นอยู่กับพลังของสอง จำนวนเป็นเช่นนั้นลำดับ 5 บิตที่ต่อเนื่องกันจะไม่ซ้ำกัน (0000 0111 1100 0100 0110 1100 1101 1101) ให้ลำดับ (00000) (00001) ... (11101) ขึ้นอยู่กับว่าคุณเลื่อนไปทางซ้ายมากแค่ไหนคุณจะได้รับหนึ่งในรูปแบบ 5 บิตเหล่านี้ จากนั้นค้นหาตาราง ดีมาก.
Safayet Ahmed


3
uint16_t log2(uint32_t n) {//but truncated
     if (n==0) throw ...
     uint16_t logValue = -1;
     while (n) {//
         logValue++;
         n >>= 1;
     }
     return logValue;
 }

โดยทั่วไปเช่นเดียวกับtomlogic 's


1
มีบางสิ่งผิดปกติกับวิธีแก้ปัญหานี้ แต่โดยทั่วไปนี่เป็นสิ่งที่ดีหากคุณต้องการหลีกเลี่ยงจุดลอยตัว คุณกำลังใช้ overflow เพื่อให้สิ่งนี้ทำงานได้เนื่องจากคุณกำลังเริ่มต้นจำนวนเต็มที่ไม่ได้ลงชื่อด้วย -1 สิ่งนี้สามารถแก้ไขได้โดยกำหนดค่าเริ่มต้นเป็น 0 แล้วคืนค่า - 1 สมมติว่าคุณตรวจสอบกรณี 0 ซึ่งคุณทำ ปัญหาอื่น ๆ คือคุณอาศัยการวนซ้ำหยุดเมื่อ n == 0 ซึ่งคุณควรระบุอย่างชัดเจน นอกเหนือจากนั้นวิธีนี้ดีมากหากคุณต้องการหลีกเลี่ยงจุดลอยตัว
Rian Quinn

2

คุณต้องรวม math.h (C) หรือ cmath (C ++) แน่นอนว่าคุณต้องทำตามคณิตศาสตร์ที่เรารู้ ... เฉพาะตัวเลข> 0

ตัวอย่าง:

#include <iostream>
#include <cmath>
using namespace std;

int main(){
    cout<<log2(number);
}

2

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

uint16_t approx_log_base_2_N_times_256(uint16_t n)
{
    uint16_t msb_only = 0x8000;
    uint16_t exp = 15;

    if (n == 0)
        return (-1);
    while ((n & msb_only) == 0) {
        msb_only >>= 1;
        exp--;
    }

    return (((uint16_t)((((uint32_t) (n ^ msb_only)) << 8) / msb_only)) | (exp << 8));
}

ในโปรแกรมหลักของฉันฉันต้องคำนวณ N * log2 (N) / 2 ด้วยผลลัพธ์จำนวนเต็ม:

อุณหภูมิ = (((uint32_t) N) * ประมาณ _log_base_2_N_times_256) / 512;

และค่า 16 บิตทั้งหมดไม่เคยปิดเกิน 2%


1

คำตอบทั้งหมดข้างต้นถูกต้อง คำตอบของฉันด้านล่างนี้จะเป็นประโยชน์หากมีคนต้องการ ฉันเห็นข้อกำหนดนี้ในคำถามมากมายที่เรากำลังแก้โดยใช้ C

log2 (x) = logy (x) / logy (2)

อย่างไรก็ตามหากคุณใช้ภาษา C และต้องการให้ผลลัพธ์เป็นจำนวนเต็มคุณสามารถใช้สิ่งต่อไปนี้:

int result = (int)(floor(log(x) / log(2))) + 1;

หวังว่านี่จะช่วยได้


0

log n / log 2ปรึกษาพื้นฐานทางคณิตศาสตร์ของคุณแน่นอน ไม่สำคัญว่าคุณจะเลือกlogหรือlog10ในกรณีนี้การหารด้วยlogฐานใหม่จะเป็นเคล็ดลับ


0

เวอร์ชันปรับปรุงของสิ่งที่ Ustaman Sangat ทำ

static inline uint64_t
log2(uint64_t n)
{
    uint64_t val;
    for (val = 0; n > 1; val++, n >>= 1);

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