Як написати журнал бази (2) в c / c ++


98

Чи є спосіб написати функцію журналу (основа 2)?

Мова С має 2 вбудовані функції - >>

1. logщо є основою e.

2. log10основа 10;

Але мені потрібна функція журналу бази 2. Як це розрахувати.


1
Для обчислень очного яблука логарифм основи 2 близький до рівня логарифму основи 10 плюс натуральний логарифм. Очевидно, що краще написати точнішу (і швидшу) версію в програмі.
Девід Торнлі,

Для цілих чисел ви можете циклічно рухатись по правому бітовому зсуву і зупинятися, коли досягається 0. Кількість циклів - це наближення журналу
Василь Старинкевич

Відповіді:


198

Проста математика:

    log 2 ( x ) = log y ( x ) / log y (2)

де y може бути будь-яким, що для стандартних функцій журналу дорівнює 10 або e .



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);
Джої

38
... абоwhile (i >>= 1) { ++l; }
Лі Даніель Крокер

2
@Joey Це б працювало, якщо ціле число має 32-бітну ширину, чи не так? За 64 біт він мав би додатковий i>>32. Але оскільки Java має лише 32-розрядні ints, це нормально. Для C / C ++ це потрібно враховувати.
Зосо

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()реалізації, він цього не зробить. Моє ліжко.
Джої,

3
@abelenky: Оскільки log10()функція визначена у стандарті С, компілятор може вільно обробляти її "спеціально", включаючи попередній обчислення результату, що, на мою думку, було пропозицією @Johannes?
кафе

1
@CarlNorum: Я щойно перевірив, і gcc 4.7 принаймні замінює log10(2)константою.
кафе

8

Якщо ви хочете зробити це швидко, ви можете скористатися пошуковою таблицею, як у розділі Bit Twiddling Hacks (лише цілочисельний журнал2 ).

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.
Сафаєт Ахмед

@SafayetAhmed Будь ласка, опишіть, як ви хочете знайти журнал2 числа за допомогою цього методу. Я не знаю простішого способу отримати це значення. Окрім використання арифметики вище з таблицею пошуку, для обчислення можна використовувати ітераційний / рекурсивний алгоритм або використання виділеного / вбудованого обладнання.
bkausbk

Скажімо, біти 32-бітової змінної v пронумеровані від 0 (LSB) до N (MSB). Скажімо, найбільш значущим бітом v є n. Чи правильно було б сказати, що n являє собою поверх (log2 (v))? Вам не цікаво просто знайти n, задане v?
Сафаєт Ахмед

Я зрозумів, що те, що я описав, просто дасть вам найближчу найнижчу потужність 2, а не фактичний логарифм. Множення та пошук таблиці призначені для переходу від степеня двох до логарифму. Ви перекладаєте число 0x07C4ACDD, залишене на якусь суму. Сума, яку ви змістите вліво, буде залежати від потужності двох. Число таке, що будь-яка послідовна послідовність з 5 бітів є унікальною. (0000 0111 1100 0100 0110 1100 1101 1101) надає вам послідовності (00000) (00001) ... (11101). Залежно від того, наскільки далеко ви зсунули ліворуч, ви отримуєте один із цих 5-бітових шаблонів. Потім пошук таблиці. Дуже хороша.
Сафаєт Ахмед

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

Проголосуйте за простоту, чіткість та надання коду на основі наданої інформації, наданої операційною програмою.
yoyo

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 .


1
З цим рішенням є кілька помилок, але загалом це приємно, якщо ви хочете уникнути плаваючих крапок. Ви покладаєтесь на переповнення, щоб це працювало, оскільки ви ініціалізуєте ціле число без знака -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 із цілочисельним результатом:

temp = (((uint32_t) N) * approx_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

Покращена версія того, що робив Устаман Сангат

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

    return val;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.