Как сделать целое число log2 () в C ++?

В стандартных библиотеках C ++ я нашел только метод журнала с плавающей запятой. Теперь я использую журнал, чтобы найти уровень индекса в двоичном дереве (floor(2log(index))).

Код (C ++):

int targetlevel = int(log(index)/log(2));

Боюсь, что для некоторых краевых элементов (элементов со значением 2 ^ n) журнал вернет n-1.999999999999 вместо n.0. Правильный ли этот страх? Как я могу изменить свое утверждение, чтобы оно всегда возвращало правильный ответ?


person Peter Smit    schedule 15.06.2009    source источник
comment
Я не понимаю вопроса. Почему он вернет n - 1,9 (9)?   -  person sharptooth    schedule 15.06.2009
comment
Потому что не все целые числа могут быть сохранены точно как число с плавающей запятой. Если 7 не подходит, он будет сохранен, например, как 7.000001 или 6.999999.   -  person Peter Smit    schedule 15.06.2009
comment
Да, я это знаю. Но откуда взялось это 1,9 (9)? Возможно, вы могли бы переформатировать вопрос, используя ‹sup› ‹/sup› для верхних индексов и ‹sub› ‹/sub› для нижних индексов?   -  person sharptooth    schedule 15.06.2009
comment
Любое целое число может быть сохранено в точности в числе с плавающей запятой. Однако функция log () не обязательно является точной, и даже если это log (2), она нерациональна ни для натурального журнала, ни для базы 10, поэтому нет причин ожидать точного результата. Учитывая, что точные результаты не могут быть гарантированы, имеет смысл беспокоиться о точных граничных условиях.   -  person David Thornley    schedule 15.06.2009
comment
У вас должны быть довольно большие целые числа, вероятно, 2 ^ экспоненциального размера, прежде чем их нельзя будет точно представить. Если в этом случае у вас есть потеря точности, это потому, что log (2) не может быть точно представлен. Вы когда-нибудь будете вызывать этот метод только для 2 ^ n? Если это так, вы можете округлить до ближайшего целого числа (или просто использовать принятый ответ)   -  person erikkallen    schedule 15.06.2009


Ответы (17)


Вместо этого вы можете использовать этот метод:

int targetlevel = 0;
while (index >>= 1) ++targetlevel;

Примечание: это изменит index. Если он вам нужен без изменений, создайте еще один временный int.

Угловой случай - это когда index равен 0. Вам, вероятно, следует проверить его отдельно и выбросить исключение или вернуть ошибку, если index == 0.

person Igor Krivokon    schedule 15.06.2009
comment
Цикл while оценивает 0-целые числа как false? - person Peter Smit; 15.06.2009
comment
Если index = 0, targetlevel будет 0. В вашем коде это, вероятно, вызовет исключение. Какое значение вы хотите получить для index = 0? - person Igor Krivokon; 15.06.2009
comment
Я хочу сказать, что цикл должен останавливаться, когда index ›› = 1 оценивается как 0. Я не мог быстро найти где-нибудь, чтобы цикл while действительно остановился, когда выражение оценивается как целое число ноль. Это, конечно, логично, потому что биты совпадают с логическим значением false. - person Peter Smit; 15.06.2009
comment
... на самом деле, в вашем коде это не исключение - он будет оцениваться как минус бесконечность, а затем преобразован в int как максимальное отрицательное значение int. - person Igor Krivokon; 15.06.2009
comment
Ах, да, цикл while остановится на 0. В C не было логических значений, и C ++ следует тем же правилам. - person Igor Krivokon; 15.06.2009
comment
Обязательно укажите index как unsigned int, иначе у вас на руках будет очень опасная ошибка потенциально бесконечного цикла. - person bobobobo; 14.02.2013
comment
@bobobobo делает действительно хорошее замечание. Отрицательное число, сдвинутое вправо, никогда не достигает нуля. Более того, логарифм отрицательного целого числа не определен в первую очередь (как и логарифм нуля). Лично мне нравится возвращать –1, когда меня просят ввести целочисленный логарифм нуля. - person Todd Lehman; 21.07.2014
comment
Начиная с C++20 вы можете использовать std:.bit_width(index) - 1, чтобы применить ту же технику, но короче и компактнее. - person Zabuzard; 18.09.2020

Если вы используете новейшую платформу x86 или x86-64 (а вы, вероятно, так и есть), используйте инструкцию bsr, которая вернет позицию самого высокого установленного бита в целое число без знака. Оказывается, это то же самое, что и log2 (). Вот короткая функция C или C ++, которая вызывает bsr с помощью встроенного ASM:

#include <stdint.h>
static inline uint32_t log2(const uint32_t x) {
  uint32_t y;
  asm ( "\tbsr %1, %0\n"
      : "=r"(y)
      : "r" (x)
  );
  return y;
}
person Matt J    schedule 15.06.2009
comment
А на ARM вам понадобится clz, который возвращает 31 минус нужное вам значение. GCC имеет __builtin_clz, который предположительно использует bsr на x86. - person Steve Jessop; 15.06.2009
comment
Это действительно отличный ответ - person bobobobo; 14.02.2013
comment
Чтобы избежать вычитания, используйте вместо этого __builtin_ctz. int log2 (int x){return __builtin_ctz (x);} Также работает на x86. - person user2573802; 08.09.2013
comment
@ user2573802 Это неверно. __builtin_ctz(9) = 0, который не является log2(9). - person nixeagle; 11.10.2013
comment
О, теперь я вспомнил. Он находит установленный бит самого низкого порядка, поэтому он работает только для чисел, которые являются точными степенями двойки. Первый бит (бит 0) установлен на 9 (1001), поэтому он возвращает 0. - person user2573802; 25.11.2013
comment
Просто упомяну, если вы используете MSVS, эквивалентный внутренний эквивалент _ 1_ - person Stian Svedenborg; 05.08.2014
comment
static inline uint32_t log2(const uint32_t x){return (31 - __builtin_clz (x));} работает как на Intel, так и на ARM (но имеет неверный результат для 0 на ARM: log2 (0) = 4294967295). Так что полный аналог Intel bsr таков: static inline uint32_t log_2(const uint32_t x){if(x == 0) return 0;return (31 - __builtin_clz (x));} - person Eddy_Em; 09.02.2015
comment
@Eddy_Em не уверен, что вы думаете о log2 (0), поскольку математически log (0) не определен для всех баз. Возвращение INT_MAX не менее корректно, чем возврат 0. - person Snaipe; 07.04.2017
comment
недавний - значит 386 или новее :) - person 0xF; 27.11.2017

Если вам просто нужна операция быстрого целочисленного журнала 2, следующая функция mylog2() сделает это, не беспокоясь о точности с плавающей запятой:

#include <limits.h>

static unsigned int mylog2 (unsigned int val) {
    if (val == 0) return UINT_MAX;
    if (val == 1) return 0;
    unsigned int ret = 0;
    while (val > 1) {
        val >>= 1;
        ret++;
    }
    return ret;
}

#include <stdio.h>

int main (void) {
    for (unsigned int i = 0; i < 20; i++)
        printf ("%u -> %u\n", i, mylog2(i));
    putchar ('\n');
    for (unsigned int i = 0; i < 10; i++)
        printf ("%u -> %u\n", i+UINT_MAX-9, mylog2(i+UINT_MAX-9));
    return 0;
}

В приведенном выше коде также есть небольшая тестовая программа, чтобы вы могли проверить поведение:

0 -> 4294967295
1 -> 0
2 -> 1
3 -> 1
4 -> 2
5 -> 2
6 -> 2
7 -> 2
8 -> 3
9 -> 3
10 -> 3
11 -> 3
12 -> 3
13 -> 3
14 -> 3
15 -> 3
16 -> 4
17 -> 4
18 -> 4
19 -> 4

4294967286 -> 31
4294967287 -> 31
4294967288 -> 31
4294967289 -> 31
4294967290 -> 31
4294967291 -> 31
4294967292 -> 31
4294967293 -> 31
4294967294 -> 31
4294967295 -> 31

Он вернет UINT_MAX для входного значения 0 в качестве указания на неопределенный результат, поэтому вы должны это проверить (ни одно допустимое целое число без знака не будет иметь такой высокий логарифм).

Кстати, есть несколько безумно быстрых хаков, чтобы сделать именно это (найти самый высокий бит, установленный в дополнительном числе до 2), доступных на здесь. Я бы не советовал использовать их, если скорость не важна (я сам предпочитаю удобочитаемость), но вы должны знать, что они существуют.

person paxdiablo    schedule 15.06.2009
comment
paxdiablo - мне нравится, что вы возвращаете –1 для входного значения 0. Обратите внимание, однако, что вы не на самом деле возвращаете -1, а фактически вместо этого ~0 (например, 0xFFFFFFFF, если у вас 32 -битовые целые числа), поскольку вы объявили функцию, возвращающую unsigned int, а не int. В этом смысле ~0 - это самое близкое к бесконечности значение, которое вы можете получить в целых числах. - person Todd Lehman; 21.07.2014
comment
@ToddLehman: На самом деле вы возвращаете -1. Затем к нему применяется интегральное продвижение, которое для отрицательных чисел устанавливает значение 2 ** 32 - n, а поскольку здесь n == -1, значение равно максимальному unsigned. В некоторых системах ~0 не даст вам того, что вы хотите. unsigned определяется в терминах значений, а не в терминах битового представления. - person David Stone; 11.10.2015
comment
@paxdiablo - Кстати, вы упомянули, что «правильное» значение для log₂ (0) - бесконечность, но разве это не будет отрицательной бесконечностью? То есть $ \ lim {x \ to 0} log x = - \ infty $. - person Todd Lehman; 13.10.2015
comment
@Todd, абсолютно правильно, предел приближается к отрицательной бесконечности. Однако, поскольку логарифмы на самом деле не определены для нуля (несмотря на ограничение), я переписал этот бит, чтобы удалить его. - person paxdiablo; 14.10.2015

Целочисленный логарифм по основанию 2

Вот что я делаю для 64-битных целых чисел без знака. Это вычисляет нижний предел логарифма с основанием 2, который эквивалентен индексу самого старшего бита. Этот метод невероятно быстр для больших чисел, потому что он использует развернутый цикл, который всегда выполняется за log₂64 = 6 шагов.

По сути, он вычитает все более мелкие квадраты в последовательности {0 ≤ k ≤ 5: 2 ^ (2 ^ k)} = {2³², 2¹⁶, 2⁸, 2⁴, 2², 2¹} = {4294967296, 65536, 256 , 16, 4, 2, 1} и суммирует показатели k вычитаемых значений.

int uint64_log2(uint64_t n)
{
  #define S(k) if (n >= (UINT64_C(1) << k)) { i += k; n >>= k; }

  int i = -(n == 0); S(32); S(16); S(8); S(4); S(2); S(1); return i;

  #undef S
}

Обратите внимание, что это возвращает –1, если задан недопустимый ввод 0 (это то, что проверяет начальный -(n == 0)). Если вы никогда не ожидаете вызвать его с помощью n == 0, вы можете заменить инициализатор int i = 0; и добавить assert(n != 0); при входе в функцию.

Целочисленный логарифм по основанию 10

Целочисленные логарифмы с основанием 10 можно вычислить аналогичным образом - с наибольшим квадратом для проверки, равным 10¹⁶, потому что log₁₀2⁶⁴ ≅ 19,2659 ...

int uint64_log10(uint64_t n)
{
  #define S(k, m) if (n >= UINT64_C(m)) { i += k; n /= UINT64_C(m); }

  int i = -(n == 0);
  S(16,10000000000000000); S(8,100000000); S(4,10000); S(2,100); S(1,10);
  return i;

  #undef S
}
person Todd Lehman    schedule 15.07.2014
comment
Очень хорошенькая. С приличным компилятором и правильным набором инструкций все условные действия могут быть реализованы как предикативные инструкции, поэтому нет никаких неверных предсказаний переходов; это все чистые вычисления в регистрах с (суперскалярной) скоростью, которую может достичь типичный современный процессор. - person Ira Baxter; 09.08.2014
comment
@IraBaxter - Спасибо ... И, что удивительно, в случае log2 этот метод сравнения со списком констант примерно на 60% быстрее (в моей системе), чем сдвиг и проверка на ноль. (Я полагаю, из-за современных кешей конвейера команд.) То есть выполнение if (n >> k) {...} для сдвига и сравнения с нулем на самом деле на 60% медленнее, чем выполнение if (n >= (UINT64_C(1) << k)) {...} для сравнения с 64-битной константой. - person Todd Lehman; 09.08.2014

Это было предложено в комментариях выше. Использование встроенных функций gcc:

static inline int log2i(int x) {
    assert(x > 0);

    return sizeof(int) * 8 - __builtin_clz(x) - 1;
}

static void test_log2i(void) {
    assert_se(log2i(1) == 0);
    assert_se(log2i(2) == 1);
    assert_se(log2i(3) == 1);
    assert_se(log2i(4) == 2);
    assert_se(log2i(32) == 5);
    assert_se(log2i(33) == 5);
    assert_se(log2i(63) == 5);
    assert_se(log2i(INT_MAX) == sizeof(int)*8-2);
}
person zbyszek    schedule 15.03.2014
comment
Не могу найти документы для assert_se - я предполагаю, что это может быть просто assert. - person Brent Bradburn; 21.05.2015
comment
Используйте unsigned x, и это соответствует floor(log2(x)) для всех 32-битных значений (кроме нуля). Я провел исчерпывающий тест с gcc 4.8.2 на x86 с sizeof (int) == 4. - person Brent Bradburn; 21.05.2015

Начиная с C ++ 20 вы можете использовать

std::bit_width(index) - 1

Очень короткий, компактный, быстрый и читаемый.

Он следует той же идее, что и ответ, предоставленный Игорем Кривоконем.

person Zabuzard    schedule 21.09.2020

Если вы используете C ++ 11, вы можете сделать это функцией constexpr:

constexpr std::uint32_t log2(std::uint32_t n)
{
    return (n > 1) ? 1 + log2(n >> 1) : 0;
}
person Kelmar    schedule 02.03.2016

У меня никогда не было проблем с точностью с плавающей запятой в формуле, которую вы используете (и быстрая проверка чисел от 1 до 2 31 - 1 не обнаружила ошибок), но если вы беспокоюсь, вместо этого вы можете использовать эту функцию, которая возвращает те же результаты и примерно на 66% быстрее в моих тестах:

int HighestBit(int i){
    if(i == 0)
        return -1;

    int bit = 31;
    if((i & 0xFFFFFF00) == 0){
        i <<= 24;
        bit = 7;
    }else if((i & 0xFFFF0000) == 0){
        i <<= 16;
        bit = 15;
    }else if((i & 0xFF000000) == 0){
        i <<= 8;
        bit = 23;
    }

    if((i & 0xF0000000) == 0){
        i <<= 4;
        bit -= 4;
    }

    while((i & 0x80000000) == 0){
        i <<= 1;
        bit--;
    }

    return bit; 
}
person P Daddy    schedule 15.06.2009
comment
В самом деле, опасность использования метода log (number) / log (base) не столько с основанием 2, сколько с другими числами. Например, log(1000) / log(10) дает 2,9999999999999996 (floor из которых 2 вместо 3) с семантикой двойной точности IEEE. - person Todd Lehman; 21.07.2014
comment
Но также обратите внимание, что, поскольку значения двойной точности IEEE имеют только 53 бита мантиссы (52 плюс понятный начальный 1-бит), метод log (число) / log (основание) полностью разваливается для чисел выше 2⁵³, что очень большое подмножество 64-битных целых чисел. Итак, хотя вы можете безопасно использовать log (number) / log (base) с 32-битными целыми числами, у вас возникнут проблемы с 64-битными целыми числами. - person Todd Lehman; 21.07.2014

Это не стандартно и не обязательно переносимо, но в целом будет работать. Не знаю, насколько это эффективно.

Преобразуйте целочисленный индекс в число с плавающей запятой достаточной точности. Представление будет точным, если точность достаточна.

Найдите представление чисел с плавающей запятой IEEE, извлеките показатель степени и сделайте необходимую настройку, чтобы найти журнал с основанием 2.

person David Thornley    schedule 15.06.2009
comment
Достаточная точность здесь равна двойной точности IEEE (64-битная double в C) для обработки 32-битных целых чисел и IEEE расширенной двойной точности (80-битная long double в C) для обработки 64-битных целых чисел. - person Todd Lehman; 21.07.2014

Выше есть похожие ответы. Этот ответ

  1. Работает с 64-битными числами
  2. Позволяет выбрать тип округления и
  3. Включает тестовый / образец кода

Функции:

    static int floorLog2(int64_t x)
    { 
      assert(x > 0);
      return 63 - __builtin_clzl(x);
    }

    static int ceilLog2(int64_t x)
    {
      if (x == 1)
        // On my system __builtin_clzl(0) returns 63.  64 would make more sense   
        // and would be more consistent.  According to stackoverflow this result  
        // can get even stranger and you should just avoid __builtin_clzl(0).     
        return 0;
      else
        return floorLog2(x-1) + 1;
    }

Код теста:

for (int i = 1; i < 35; i++)
  std::cout<<"floorLog2("<<i<<") = "<<floorLog2(i)
           <<", ceilLog2("<<i<<") = "<<ceilLog2(i)<<std::endl;
person Trade-Ideas Philip    schedule 22.04.2017

Эта функция определяет, сколько битов требуется для представления числового интервала: [0..maxvalue].

unsigned binary_depth( unsigned maxvalue )
   {
   int depth=0;
   while ( maxvalue ) maxvalue>>=1, depth++;
   return depth;
   }

Вычитая 1 из результата, вы получите floor(log2(x)), которое является точным представлением log2(x), когда x является степенью двойки.

x y y-1
0 0 -1
1 1 0
2 2 1 < / strong>
3 2 1
4 3 2
5 3 2
6 3 2
7 3 2
8 4 3

person Brent Bradburn    schedule 20.02.2013
comment
Это можно легко обобщить для поддержки любого «основания» (основание чисел) - просто используйте /=radix (деление на основание) вместо >>=1. - person Brent Bradburn; 26.08.2013

int log2(int x) {
    return sizeof(int)*8 - 1 - __builtin_clz(x);
}

предполагая, что ваш x> 0

person Ecto    schedule 01.12.2019
comment
__builtin_clz не является стандартной функцией C ++. - person Sneftel; 21.09.2020

Насколько глубоко вы проецируете свое дерево? Вы можете установить диапазон, скажем ... +/- 0,00000001 для числа, чтобы заставить его принимать целочисленное значение.

Я на самом деле не уверен, что вы попадете в число, подобное 1.99999999, потому что ваш log2 не должен терять точность при вычислении значений 2 ^ n (поскольку с плавающей запятой округляется до ближайшей степени 2).

person CookieOfFortune    schedule 15.06.2009

Эту функцию я написал здесь

// The 'i' is for int, there is a log2 for double in stdclib
inline unsigned int log2i( unsigned int x )
{
  unsigned int log2Val = 0 ;
  // Count push off bits to right until 0
  // 101 => 10 => 1 => 0
  // which means hibit was 3rd bit, its value is 2^3
  while( x>>=1 ) log2Val++;  // div by 2 until find log2.  log_2(63)=5.97, so
  // take that as 5, (this is a traditional integer function!)
  // eg x=63 (111111), log2Val=5 (last one isn't counted by the while loop)
  return log2Val ;
}
person bobobobo    schedule 14.02.2013

Перефразируя ответ Тодда Лемана на более общий:

#include <climits>

template<typename N>
constexpr N ilog2(N n) {
    N i = 0;
    for (N k = sizeof(N) * CHAR_BIT; 0 < (k /= 2);) {
        if (n >= static_cast<N>(1) << k) { i += k; n >>= k; }
    }
    return i;
}

Clang с -O3 разворачивает цикл:

0000000100000f50    pushq   %rbp
0000000100000f51    movq    %rsp, %rbp
0000000100000f54    xorl    %eax, %eax
0000000100000f56    cmpl    $0xffff, %edi
0000000100000f5c    setg    %al
0000000100000f5f    shll    $0x4, %eax
0000000100000f62    movl    %eax, %ecx
0000000100000f64    sarl    %cl, %edi
0000000100000f66    xorl    %edx, %edx
0000000100000f68    cmpl    $0xff, %edi
0000000100000f6e    setg    %dl
0000000100000f71    leal    (,%rdx,8), %ecx
0000000100000f78    sarl    %cl, %edi
0000000100000f7a    leal    (%rax,%rdx,8), %eax
0000000100000f7d    xorl    %edx, %edx
0000000100000f7f    cmpl    $0xf, %edi
0000000100000f82    setg    %dl
0000000100000f85    leal    (,%rdx,4), %ecx
0000000100000f8c    sarl    %cl, %edi
0000000100000f8e    leal    (%rax,%rdx,4), %eax
0000000100000f91    xorl    %edx, %edx
0000000100000f93    cmpl    $0x3, %edi
0000000100000f96    setg    %dl
0000000100000f99    leal    (%rdx,%rdx), %ecx
0000000100000f9c    sarl    %cl, %edi
0000000100000f9e    leal    (%rax,%rdx,2), %ecx
0000000100000fa1    xorl    %eax, %eax
0000000100000fa3    cmpl    $0x1, %edi
0000000100000fa6    setg    %al
0000000100000fa9    orl %ecx, %eax
0000000100000fab    popq    %rbp

Когда n является постоянным, результат вычисляется во время компиляции.

person wonder.mice    schedule 16.06.2018

Учитывая способ работы чисел с плавающей запятой (грубо говоря, мантисса * 2 ^ экспонента), то любое число до 2 ^ 127, которое является степенью 2, будет точно представлено без ошибок.

Это дает тривиальное, но довольно хакерское решение - интерпретировать битовый шаблон числа с плавающей запятой как целое число и просто смотреть на показатель степени. Это решение Дэвида Торнли, приведенное выше.

float f = 1;
for (int i = 0; i < 128; i++)
{
    int x = (*(int*)(&f)>>23) - 127;
    int l = int(log(f) / log(2));

    printf("i = %d, log = %d, f = %f quick = %d\n",
        i, l, f, x);
    f *= 2;
}

Неверно, что любое целое число может быть представлено как число с плавающей запятой - только те, у которых меньше битов, чем может представить мантисса. В 32-битных числах с плавающей запятой это 23 бита.

person GizmoGremlin    schedule 11.06.2019

person    schedule
comment
Это хорошо определено для самого сложного случая (2^N-1), по крайней мере, до N=32, но сталкивается с проблемами около N=(52-log(52)) или около того, когда результат двойной точности log начинает возвращать идентичные результаты для соседних значений. - person mwfearnley; 11.02.2017