Задача нахождения номера старшего единичного бита числа довольно часто встречается в олимпиадном программировании, например в задаче RMQ.
Рассмотрим четыре способа решения этой задачи. Условимся, что задачу будем решать для целого числа N ($latex 1leqslant{N}leqslant{2}^{32}-1$).
1. naive [O(logN)] Первый способ самый простой и очевидный: будем сдвигать N вправо на один бит, пока оно не станет равным 1 (а не 0, так мы сэкономим одну итерацию).
- inline int high_bit_line(UINT n) {
- int res = 0;
- while (n != 1) {
- n >>= 1;
- res++;
- }
- return res;
- }
* This source code was highlighted with Source Code Highlighter.
Сложность первого алгоритма – количество цифр в двоичном представлении N, то есть $latex log_2{N}$.
2. log2 [O(const)] Второй способ математический. Поскольку номер старшего бита – показатель старшей степени двойки, то его номер можно найти с помощью логарифма, округлив его вниз:
- #include <cmath>
- const double EPS = 1e-11;
- inline double log2(double n) {
- return log(n) / log(2.0);
- }
- inline int high_bit_log2(UINT n) {
- return (int)(log2((double)n) + EPS);
- }
* 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:
- inline int high_bit_bs(UINT n){
- int size = sizeof(n) * 4;
- int res = 0;
- while (n != 1) {
- int l = n >> size;
- if (l) {
- n = l;
- res += size;
- } else {
- n ^= l << size;
- }
- size >>= 1;
- }
- return res;
- }
* 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};
Немного исправив код третьего способа, получаем:
- inline int high_bit_bsm(UINT n){
- int size = sizeof(n)*4;
- int res = 0;
- int m = 0;
- while (n != 1) {
- int l = n >> size;
- if (l) {
- n = l;
- res += size;
- } else {
- n &= MASK_R[m];
- }
- size >>= 1;
- m++;
- }
- return res;
- }
* 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
Есть массив
0 |
AnyKey 263 / 182 / 87 Регистрация: 03.05.2020 Сообщений: 790 |
||||
11.02.2021, 22:19 |
2 |
|||
так?
0 |
142 / 26 / 4 Регистрация: 06.05.2019 Сообщений: 1,714 Записей в блоге: 4 |
|
11.02.2021, 22:22 [ТС] |
3 |
так? А я откуда знаю. Я знаю что с++ официально с битами не работает, Ассемблер работает, а здесь это реализовывать геморойно. Вот число
0 |
6576 / 4561 / 1843 Регистрация: 07.05.2019 Сообщений: 13,726 |
|
11.02.2021, 22:22 |
4 |
? Если да покажите пример, как работает bitset я не знаю, ну и естественно использует ли он цикл для решения этой задачи. В любом случае хотелось бы свой алгоритм сделать. https://habr.com/ru/post/93172/
0 |
263 / 182 / 87 Регистрация: 03.05.2020 Сообщений: 790 |
|
11.02.2021, 22:24 |
5 |
А я откуда знаю а проверь
0 |
142 / 26 / 4 Регистрация: 06.05.2019 Сообщений: 1,714 Записей в блоге: 4 |
|
11.02.2021, 22:26 [ТС] |
6 |
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 знаковый, то можно просто сравнить с нулем и все.
1 |
6576 / 4561 / 1843 Регистрация: 07.05.2019 Сообщений: 13,726 |
|
11.02.2021, 22:34 |
8 |
Значит всё таки перебором через цикл, эту статью только что читал? Ну, а как иначе? Можно только вручную сузить диапазон поиска – первое слово, второе, первый полубайт и т.д.
0 |
142 / 26 / 4 Регистрация: 06.05.2019 Сообщений: 1,714 Записей в блоге: 4 |
|
11.02.2021, 22:41 [ТС] |
9 |
если char знаковый, то можно просто сравнить с нулем и все. В соседней теме Почему 2 байта дают -240 а на 8ке – 65296? я уже определил знаковый или нет и остался с носом.
Ну, а как иначе? Можно только вручную сузить диапазон поиска. Я думал происходит прыжок, типа как в адрес только быстрее т.к. это в пределах ряда регистров и обрабатываются биты а не байты а сколько байт или бит нужно посмотреть известно. Добавлено через 3 минуты
Ну, а как иначе? Можно только вручную сузить диапазон поиска Что быстрее чтение целого байта или перебор его битов, насколько последнее хуже первого?
0 |
142 / 26 / 4 Регистрация: 06.05.2019 Сообщений: 1,714 Записей в блоге: 4 |
|
11.02.2021, 22:51 [ТС] |
11 |
Можно использовать как обычные функции. Дорого. Всё таки чтение байта происходит быстрее чем поиск параметра в байте.
0 |
17395 / 9236 / 2254 Регистрация: 30.01.2014 Сообщений: 16,147 |
|
11.02.2021, 22:54 |
12 |
Дорого. Как вы это определили? Интринсик на то и интринсик, что разворачивается в одну или несколько простых машинных команд. А уж быстрее, чем это сделает процессор, у вас сделать точно не выйдет.
0 |
6576 / 4561 / 1843 Регистрация: 07.05.2019 Сообщений: 13,726 |
|
11.02.2021, 22:57 |
13 |
Можно использовать как обычные функции. Ну да. Просто заворачиваем поиск в отдельную функцию, а потом делаем морду топором и говорим – А где ты увидел цикл? Никого цикла нет!
0 |
17395 / 9236 / 2254 Регистрация: 30.01.2014 Сообщений: 16,147 |
|
11.02.2021, 23:00 |
14 |
Просто заворачиваем поиск в отдельную функцию, а потом делаем морду топором и говорим – А где ты увидел цикл? Никого цикла нет! Ну елы-палы! Ты-то хоть прочитай что я пишу внимательно.
0 |
6576 / 4561 / 1843 Регистрация: 07.05.2019 Сообщений: 13,726 |
|
11.02.2021, 23:05 |
15 |
Это не поиск с циклом завернут в функцию, а отдельная специальная машинная инструкция завернута в псевдофункцию. Ну так вопрос – есть ли алгоритм поиска старшего бита с константной сложностью, а не O(N)? А не инструкция ассемблера, в которую завёрнут тот же цикл.
0 |
17395 / 9236 / 2254 Регистрация: 30.01.2014 Сообщений: 16,147 |
|
11.02.2021, 23:10 |
16 |
А не инструкция ассемблера, в которую завёрнут тот же цикл. Это совершенно некорректно сравнивать тот цикл, который реализован аппаратно, с тем циклом про который изначально в этой теме шла речь. Никакой ручной цикл никогда не сравнится с этой командой по производительности.
0 |
SmallEvil 2339 / 1867 / 606 Регистрация: 29.06.2020 Сообщений: 7,056 |
||||
11.02.2021, 23:14 |
17 |
|||
а тут где подвох ?
0 |
17395 / 9236 / 2254 Регистрация: 30.01.2014 Сообщений: 16,147 |
|
11.02.2021, 23:20 |
18 |
а тут где подвох ? От задачи зависит. значащий (единичный) бит (это не обязательно знаковый), тогда ему нужно будет просмотреть биты начиная с самого старшего. В обычном виде это можно сделать циклом. Либо этот поиск атомарно и быстро может провести процессор, с использованием специальной инструкции. Более быстрого варианта в природе нет.
2 |
6576 / 4561 / 1843 Регистрация: 07.05.2019 Сообщений: 13,726 |
|
11.02.2021, 23:20 |
19 |
Это совершенно некорректно сравнивать тот цикл, который реализован аппаратно, с тем циклом про который изначально в этой теме шла речь. Никакой ручной цикл никогда не сравнится с этой командой по производительности. Вопрос вроде был об отсутствии цикла в принципе. Например, определить то, что число является степенью двойки, можно безо всяких циклов, хоть программно, хоть аппаратно. Есть ли подобный алгоритм для нахождения старшего бита?
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
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
15.6k6 gold badges51 silver badges98 bronze badges
answered Sep 28, 2013 at 4:22
David SchwartzDavid Schwartz
179k17 gold badges213 silver badges277 bronze badges
6