Как найти старший бит числа

Задача нахождения номера старшего единичного бита числа довольно часто встречается в олимпиадном программировании, например в задаче RMQ.
Рассмотрим четыре способа решения этой задачи. Условимся, что задачу будем решать для целого числа N ($latex 1leqslant{N}leqslant{2}^{32}-1$).

1. naive [O(logN)] Первый способ самый простой и очевидный: будем сдвигать N вправо на один бит, пока оно не станет равным 1 (а не 0, так мы сэкономим одну итерацию).

  1. inline int high_bit_line(UINT n) {
  2.   int res = 0;
  3.   while (n != 1) {
  4.     n >>= 1;
  5.     res++;
  6.   }
  7.   return res;
  8. }

* This source code was highlighted with Source Code Highlighter.

Сложность первого алгоритма – количество цифр в двоичном представлении N, то есть $latex log_2{N}$.


2. log2 [O(const)] Второй способ математический. Поскольку номер старшего бита – показатель старшей степени двойки, то его номер можно найти с помощью логарифма, округлив его вниз:

  1. #include <cmath>
  2.  
  3. const double EPS = 1e-11;
  4. inline double log2(double n) {
  5.   return log(n) / log(2.0);
  6. }
  7. inline int high_bit_log2(UINT n) {
  8.   return (int)(log2((double)n) + EPS);
  9. }

* This source code was highlighted with Source Code Highlighter.

Вроде бы все классно, но могут возникнуть проблемы с округлением. Поскольку математические операции в cmath могут возвращать неточные значения (например, sqrt(4) = 1.9999…) , то приходится добавлять к их результатам константу. Константа должна быть строго меньше числа, обратного максимальному значению N, иначе это может привести к неправильному результату (например, если к $latex log_2({2}^{32}-1)$ прибавить 10-9, то результат будет больше 31). Поэтому в нашем случае я взял 10-11, так как $latex frac 1 {{2}^{32}} approx {2}*{10}^{-10}$.
К сожалению, библиотека cmath в Visual Studio не поддерживает функцию log2, поэтому пришлось делать промежуточную функцию.
Сложность вычисления логарифма равна константе, но она достаточно велика.


3. Binary search [O(log(logN))] В основе этого способа лежит метод бинарного поиска. Будем брать правую часть числа (в двоичном представлении), пока она не равна нулю, а иначе берем левую часть, постепенно деля число пополам, пока не получим 1:

  1. inline int high_bit_bs(UINT n){
  2.   int size = sizeof(n) * 4;
  3.   int res = 0;
  4.   while (n != 1) {
  5.     int l = n >> size;
  6.     if (l) {
  7.       n = l;
  8.       res += size;
  9.  
  10.     } else {
  11.       n ^= l << size;
  12.     }
  13.     size >>= 1;
  14.   }
  15.   return res;
  16. }

* This source code was highlighted with Source Code Highlighter.

Рассмотрим применение этого алгоритма к числу 1234567890.

$latex 0 1 0 0 1 0 0 1 1 0 0 1 0 1 1 0 | 0 0 0 0 0 0 1 0 1 1 0 1 0 0 1 0$ res = 0; size = 16;

$latex 0 1 0 0 1 0 0 1|1 0 0 1 0 1 1 0$ res = 16; size = 8;

$latex 0 1 0 0|1 0 0 1$ res = 24; size = 4;

$latex 0 1|0 0$ res = 28; size = 2;

$latex 0|1$ res = 30; size = 1;
Сложность этого способа равна логарифму от числа битов N, то есть $latex log _{ 2 } (log _{ 2 }{ N } )$.


4. Binary search with mask [O(log(log(N)))]
Да, я не ошибся, сложность четвертого алгоритма почти равна сложности третьего, так как этот способ является всего лишь небольшой оптимизацией предыдущего. В третьем алгоритме мы находим правую часть числа через левую (строка 9). Здесь мы затрачиваем две операции: битового сдвига и исключающего ИЛИ (XOR). Эти операции можно заменить на сложение и И (AND), добавив константный массив масок:

const int MASK_R[6] = {0x0000FFFF, 0x000000FF, 0x0000000F, 0x00000003, 0x00000001};

Немного исправив код третьего способа, получаем:

  1. inline int high_bit_bsm(UINT n){
  2.   int size = sizeof(n)*4;
  3.   int res = 0;
  4.   int m = 0;
  5.   while (n != 1) {
  6.     int l = n >> size;
  7.     if (l) {
  8.       n = l;
  9.       res += size;
  10.     } else {
  11.       n &= MASK_R[m];
  12.     }
  13.     size >>= 1;
  14.     m++;
  15.   }
  16.   return res;
  17. }

* This source code was highlighted with Source Code Highlighter.

Правда, в некоторых случаях эта оптимизация будет работать дольше, чем оригинал, поскольку операция сложения выполняется при каждом проходе цикла while.

Выводы: Подход с бинарным поиском  дает наилучший результат.
Если нужно решать поставленную задачу на ограниченном диапазоне, который можно хранить в памяти, то лучше динамикой подсчитать позицию старшего единичного бита для каждого числа в диапазоне.
Эта идея хорошо описана в
вики конспектах ИТМО(Раздел “Применение к задаче RMQ”).

> Вообще-то гарантирует. Пункт 7.18.1.1

То есть если такие типы существуют, то typedef должен быть.

Там не сказано, что «если такие типы существуют» (где?), а «если реализация обеспечивает». Причём по c99 не гарантируется наличие ни одного из этих типов, а posix не гарантирует наличие только 64-битных чисел и предлагает вызывать

getconf -a | grep POSIX_V7

Но можно ли себе представить такую реализацию, которая обеспечивала бы 8-, 16-, 32-битные числа, а также int_least64_t (который теоретически может быть, например, 128-битным), но не обеспечивала бы 64-битного числа? По-моему, это невероятно.

А вообще, gcc сейчас обеспечивает и 128-битные числа (__int128_t), в том числе на 32-разрядных системах.

Sorcerer ★★★★★

(21.07.11 09:35:28 MSK)

  • Показать ответы
  • Ссылка

Обычно для поиска старшего единичного бита используют функцию, которая возвращает количество лидирующих нулей, поскольку очень часто в системе команд процессора существует такая инструкция (например, в X86 это LZCNT), а компилятор обеспечивает встроенную функцию (например, в GCC это int __builtin_clz (unsigned int x)).
(см. также Find first set)

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

#include <stdlib.h>


static inline int _clz4 (uint x) {
  static int n[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
  return n[x];
}

static inline int _clz8 (uint x) {
  return x > 0xf ? _clz4(x >> 4) : _clz4(x) + 4;
}
static inline int _clz16 (uint x) {
  return x > 0xff ? _clz8(x >> 8) : _clz8(x) + 8;
}

static inline int clz32 (uint x) {
  return x > 0xffff ? _clz16(x >> 16) : _clz16(x) + 16;
}


// http://embeddedgurus.com/state-space/tag/clz/
#include <stdint.h>

static inline uint32_t CLZ1(uint32_t x) {
    static uint8_t const clz_lkup[] = {
        32U, 31U, 30U, 30U, 29U, 29U, 29U, 29U,
        28U, 28U, 28U, 28U, 28U, 28U, 28U, 28U,
        27U, 27U, 27U, 27U, 27U, 27U, 27U, 27U,
        27U, 27U, 27U, 27U, 27U, 27U, 27U, 27U,
        26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
        26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
        26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
        26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
        24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U
    };
    uint32_t n;
    if (x >= (1U << 16)) {
        if (x >= (1U << 24)) {
            n = 24U;
        }
        else {
            n = 16U;
        }
    }
    else {
        if (x >= (1U << 8)) {
            n = 8U;
        }
        else {
            n = 0U;
        }
    }
    return (uint32_t)clz_lkup[x >> n] - n;
}

(функция CLZ1() немного быстрее, но занимает больше памяти, чем clz32())

142 / 26 / 4

Регистрация: 06.05.2019

Сообщений: 1,714

Записей в блоге: 4

1

Как быстро прочитать старший бит числа без применения цикла?

11.02.2021, 22:16. Показов 2313. Ответов 27


Студворк — интернет-сервис помощи студентам

Есть массив char qw[]={12,-13}. Можно взять любое число и найти старший бит, тип как вы видите известен, возможно ли это сделать без цикла? Если да покажите пример, как работает bitset я не знаю, ну и естественно использует ли он цикл для решения этой задачи. В любом случае хотелось бы свой алгоритм сделать.



0



AnyKey

263 / 182 / 87

Регистрация: 03.05.2020

Сообщений: 790

11.02.2021, 22:19

2

C++
1
if(qw[i] & 0x80)...

так?



0



142 / 26 / 4

Регистрация: 06.05.2019

Сообщений: 1,714

Записей в блоге: 4

11.02.2021, 22:22

 [ТС]

3

Цитата
Сообщение от AnyKey
Посмотреть сообщение

так?

А я откуда знаю. Я знаю что с++ официально с битами не работает, Ассемблер работает, а здесь это реализовывать геморойно. Вот число 1111111100010000 на 2ух байтах это -240, нужно прочитать 15ый бит слева а он здесь равен 1це.



0



6576 / 4561 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

11.02.2021, 22:22

4

Цитата
Сообщение от Nexi99
Посмотреть сообщение

? Если да покажите пример, как работает bitset я не знаю, ну и естественно использует ли он цикл для решения этой задачи. В любом случае хотелось бы свой алгоритм сделать.

https://habr.com/ru/post/93172/



0



263 / 182 / 87

Регистрация: 03.05.2020

Сообщений: 790

11.02.2021, 22:24

5

Цитата
Сообщение от Nexi99
Посмотреть сообщение

А я откуда знаю

а проверь



0



142 / 26 / 4

Регистрация: 06.05.2019

Сообщений: 1,714

Записей в блоге: 4

11.02.2021, 22:26

 [ТС]

6

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

https://habr.com/ru/post/93172/

Значит всё таки перебором через цикл, эту статью только что читал?



0



DrOffset

17395 / 9236 / 2254

Регистрация: 30.01.2014

Сообщений: 16,147

11.02.2021, 22:30

7

Лучший ответ Сообщение было отмечено politoto как решение

Решение

Nexi99, если char знаковый, то можно просто сравнить с нулем и все.

C++
1
2
bool sign0 = qw[0] < 0; // true(1) - если старший бит равен 1 и false(0), если старший бит - 0
bool sign1 = qw[1] < 0;



1



6576 / 4561 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

11.02.2021, 22:34

8

Цитата
Сообщение от Nexi99
Посмотреть сообщение

Значит всё таки перебором через цикл, эту статью только что читал?

Ну, а как иначе? Можно только вручную сузить диапазон поиска – первое слово, второе, первый полубайт и т.д.



0



142 / 26 / 4

Регистрация: 06.05.2019

Сообщений: 1,714

Записей в блоге: 4

11.02.2021, 22:41

 [ТС]

9

Цитата
Сообщение от DrOffset
Посмотреть сообщение

если char знаковый, то можно просто сравнить с нулем и все.

В соседней теме Почему 2 байта дают -240 а на 8ке – 65296? я уже определил знаковый или нет и остался с носом.

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Ну, а как иначе? Можно только вручную сузить диапазон поиска.

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

Добавлено через 3 минуты

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Ну, а как иначе? Можно только вручную сузить диапазон поиска

Что быстрее чтение целого байта или перебор его битов, насколько последнее хуже первого?



0



142 / 26 / 4

Регистрация: 06.05.2019

Сообщений: 1,714

Записей в блоге: 4

11.02.2021, 22:51

 [ТС]

11

Цитата
Сообщение от DrOffset
Посмотреть сообщение

Можно использовать как обычные функции.

Дорого. Всё таки чтение байта происходит быстрее чем поиск параметра в байте.



0



17395 / 9236 / 2254

Регистрация: 30.01.2014

Сообщений: 16,147

11.02.2021, 22:54

12

Цитата
Сообщение от Nexi99
Посмотреть сообщение

Дорого.

Как вы это определили? Интринсик на то и интринсик, что разворачивается в одну или несколько простых машинных команд. А уж быстрее, чем это сделает процессор, у вас сделать точно не выйдет.
Так что не пишите ерунды, а лучше изучите материал, который я выше предоставил.



0



6576 / 4561 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

11.02.2021, 22:57

13

Цитата
Сообщение от DrOffset
Посмотреть сообщение

Можно использовать как обычные функции.

Ну да. Просто заворачиваем поиск в отдельную функцию, а потом делаем морду топором и говорим – А где ты увидел цикл? Никого цикла нет!



0



17395 / 9236 / 2254

Регистрация: 30.01.2014

Сообщений: 16,147

11.02.2021, 23:00

14

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

Просто заворачиваем поиск в отдельную функцию, а потом делаем морду топором и говорим – А где ты увидел цикл? Никого цикла нет!

Ну елы-палы! Ты-то хоть прочитай что я пишу внимательно.
Это не поиск с циклом завернут в функцию, а отдельная специальная машинная инструкция завернута в псевдофункцию.



0



6576 / 4561 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

11.02.2021, 23:05

15

Цитата
Сообщение от DrOffset
Посмотреть сообщение

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

Ну так вопрос – есть ли алгоритм поиска старшего бита с константной сложностью, а не O(N)? А не инструкция ассемблера, в которую завёрнут тот же цикл.



0



17395 / 9236 / 2254

Регистрация: 30.01.2014

Сообщений: 16,147

11.02.2021, 23:10

16

Цитата
Сообщение от oleg-m1973
Посмотреть сообщение

А не инструкция ассемблера, в которую завёрнут тот же цикл.

Это совершенно некорректно сравнивать тот цикл, который реализован аппаратно, с тем циклом про который изначально в этой теме шла речь. Никакой ручной цикл никогда не сравнится с этой командой по производительности.



0



SmallEvil

2339 / 1867 / 606

Регистрация: 29.06.2020

Сообщений: 7,056

11.02.2021, 23:14

17

а тут где подвох ?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
enum bits{first=1, second = 2, third = 4}; // пока два
 
int main()
{
    int i = 3;
    
    short s = -240;
    short high_bit= 1<<(sizeof(s)*8-1);
 
    std::cout << "nbit one : "<< bool(i&bits::first);
    std::cout << "nbit second : "<< bool(i&bits::second);
    std::cout << "nbit third : "<< bool(i&bits::third);
 
    std::cout << "nHigh bit of short : "<< bool((s&high_bit));
}



0



17395 / 9236 / 2254

Регистрация: 30.01.2014

Сообщений: 16,147

11.02.2021, 23:20

18

Цитата
Сообщение от SmallEvil
Посмотреть сообщение

а тут где подвох ?

От задачи зависит.
Или ТС хочет посмотреть чему равен знаковый бит. Тогда в общем-то ничего проще, чем первый ответ этой темы, или простое сравнение с нулем нет.
Или ТС хочет получить наиболее старший

значащий

(единичный) бит (это не обязательно знаковый), тогда ему нужно будет просмотреть биты начиная с самого старшего. В обычном виде это можно сделать циклом. Либо этот поиск атомарно и быстро может провести процессор, с использованием специальной инструкции. Более быстрого варианта в природе нет.



2



6576 / 4561 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

11.02.2021, 23:20

19

Цитата
Сообщение от DrOffset
Посмотреть сообщение

Это совершенно некорректно сравнивать тот цикл, который реализован аппаратно, с тем циклом про который изначально в этой теме шла речь. Никакой ручной цикл никогда не сравнится с этой командой по производительности.

Вопрос вроде был об отсутствии цикла в принципе. Например, определить то, что число является степенью двойки, можно безо всяких циклов, хоть программно, хоть аппаратно. Есть ли подобный алгоритм для нахождения старшего бита?



0



17395 / 9236 / 2254

Регистрация: 30.01.2014

Сообщений: 16,147

11.02.2021, 23:23

20

SmallEvil, Т.к. ТС нифига не поясняет (не хочет или не может), что именно ему надо, попеременно выдает совершено разные, иногда противоречащие друг друга кусочки какой-то одному ему понятной идеи, то помочь ему качественно можно разве что только случайно. Так что единственное, что мы тут можем, – это просто накидать тут разных вариантов и пусть он сам выбирает. Только практика показывает, что тема просто будет заброшена и все. А наши усилия пропадут даром.



0



Can anyone tell me what is lower and higher bits?. How to identify a higher and lower bit?. Below is binary form. How does 0110 is higher bit in it?.

0110 0111 1100 1010 1100 0111 1001 1011

asked Sep 28, 2013 at 4:19

Shane's user avatar

Just like in decimal, higher places are generally written to the left in binary. So if you see 0111, the 0 is the high bit. So this would represent 7 in decimal. The same applies when spaces are used, just like when commas (or dots, depending on your locale) are used to separate groups of digits in decimal.

Innat's user avatar

Innat

15.6k6 gold badges51 silver badges98 bronze badges

answered Sep 28, 2013 at 4:22

David Schwartz's user avatar

David SchwartzDavid Schwartz

179k17 gold badges213 silver badges277 bronze badges

6

Добавить комментарий