Если у меня есть целое число n, и я хочу знать позицию самого старшего бита (то есть, если младший бит находится справа, я хочу знать позицию самого дальнего левого бита, равного 1), какой способ узнать самый быстрый / эффективный?
Я знаю, что POSIX поддерживает ffs()
метод в strings.h для поиска первого установленного бита, но похоже, что нет соответствующего fls()
метода.
Есть ли какой-то действительно очевидный способ сделать это, что мне не хватает?
Как насчет случаев, когда вы не можете использовать функции POSIX для переносимости?
Изменить: как насчет решения, которое работает как на 32-разрядных, так и на 64-разрядных архитектурах (многие листинги кода кажутся такими, как будто они работают только на 32-разрядных int).
Ответы:
GCC имеет :
Я ожидал, что они будут переведены во что-то достаточно эффективное для вашей текущей платформы, будь то один из этих фантастических алгоритмов перестановки битов или отдельная инструкция.
Полезный трюк , если ваш вход может быть ноль
__builtin_clz(x | 1)
: безоговорочно устанавливая младший бит без изменения других делает вывод31
дляx=0
, без изменения выходного сигнала для любого другого входа.Чтобы избежать этого, вы можете использовать другой вариант - встроенные функции для конкретной платформы, такие как ARM GCC
__clz
(заголовок не требуется) или x86_lzcnt_u32
на процессорах, поддерживающихlzcnt
инструкцию. (Остерегайтесь этогоlzcnt
декодирования, какbsr
на старых процессорах, вместо сбоя, который дает 31-lzcnt для ненулевых входов.)К сожалению, нет возможности переносимо воспользоваться преимуществами различных инструкций CLZ на платформах, отличных от x86, которые определяют результат для input = 0 как 32 или 64 (в зависимости от ширины операнда). x86 тоже
lzcnt
делает то же самое, аbsr
выдает битовый индекс, который компилятор должен перевернуть, если вы его не используете31-__builtin_clz(x)
.(«Неопределенный результат» - это не неопределенное поведение C, а просто значение, которое не определено. Фактически, это то, что было в регистре назначения, когда выполнялась инструкция. AMD документирует это, Intel - нет, но процессоры Intel реализуют это поведение. . Но это не то, что раньше было в переменной C, которую вы назначаете, это обычно не то, как все работает, когда gcc превращает C в asm. См. Также Почему нарушение "выходной зависимости" LZCNT имеет значение? )
источник
__builtin_ctz
overffs
, который компилируется в BSF и CMOV для обработки случая, когда вход был нулевым. На архитектурах без достаточно короткой реализации (например, старый ARM безclz
инструкции), gcc выдает вызов вспомогательной функции libgcc.Предполагая, что вы работаете на x86 и играете немного на встроенном ассемблере, Intel предоставляет
BSR
инструкцию («битовое сканирование в обратном направлении»). Это быстро на некоторых x86 (микрокодировано на других). Из руководства:(Если вы используете PowerPC, есть аналогичная
cntlz
инструкция («считать ведущие нули»).)Пример кода для gcc:
См. Также это руководство по встроенному ассемблеру , в котором показано (раздел 9.4), что он значительно быстрее, чем код цикла.
источник
Поскольку 2 ^ N является целым числом с установленным только N-м битом (1 << N), поиск позиции (N) самого высокого установленного бита является целым числом с основанием 2 этого целого числа.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
Этот «очевидный» алгоритм может быть не прозрачен для всех, но когда вы понимаете, что код многократно сдвигается вправо на один бит, пока крайний левый бит не будет смещен (обратите внимание, что C обрабатывает любое ненулевое значение как истинное) и возвращает число смен, это имеет смысл. Это также означает, что он работает, даже когда установлено более одного бита - результат всегда для самого старшего бита.
Если вы прокрутите страницу вниз, то увидите более быстрые и сложные варианты. Однако, если вы знаете, что имеете дело с числами с большим количеством ведущих нулей, наивный подход может обеспечить приемлемую скорость, поскольку сдвиг битов в C выполняется довольно быстро, а простой алгоритм не требует индексации массива.
ПРИМЕЧАНИЕ. При использовании 64-битных значений будьте предельно осторожны при использовании экстра-умных алгоритмов; многие из них корректно работают только для 32-битных значений.
источник
>>>
. Плюс наверное компаратор!= 0
и некоторая неуказанная цифра скобок.Это должно быть молниеносно:
источник
Это что-то вроде поиска целочисленного журнала. Есть хитрости, но я сделал для этого свой собственный инструмент. Конечно, цель - скорость.
Я понимаю, что в ЦП уже есть автоматический бит-детектор, используемый для преобразования целых чисел в числа с плавающей запятой! Так что используйте это.
Эта версия приводит значение к двойному, а затем считывает показатель степени, который сообщает вам, где был бит. Причудливый сдвиг и вычитание предназначены для извлечения правильных частей из значения IEEE.
Немного быстрее использовать числа с плавающей запятой, но число с плавающей запятой может дать вам только первые 24 бита из-за своей меньшей точности.
Чтобы сделать это безопасно, без неопределенного поведения в C ++ или C, используйте
memcpy
вместо преобразования указателя для каламбура типов. Компиляторы знают, как эффективно встроить его.Или в C99 и более поздних версиях используйте файл
union {double d; uint32_t u[2];};
. Но обратите внимание, что в C ++ каламбур типа union поддерживается только некоторыми компиляторами в качестве расширения, но не в ISO C ++.Обычно это будет медленнее, чем встроенная функция для конкретной платформы для команды подсчета начальных нулей, но переносимый ISO C не имеет такой функции. В некоторых процессорах отсутствует команда подсчета с начальным нулем, но некоторые из них могут эффективно преобразовывать целые числа в
double
. Однако преобразование битового шаблона FP в целое число может быть медленным (например, на PowerPC это требует сохранения / перезагрузки и обычно вызывает остановку загрузки-попадания-сохранения).Этот алгоритм потенциально может быть полезен для реализаций SIMD, потому что меньшее количество процессоров имеют SIMD
lzcnt
. x86 получил такую инструкцию только с AVX512CDисточник
Каз Кылхеку здесь
Я протестировал два подхода для этого более 63-битных чисел (длинный длинный тип на gcc x86_64), избегая знакового бита.
(Видите ли, мне для чего-то нужен этот "самый высокий бит".)
Я реализовал двоичный поиск, управляемый данными (основанный на одном из приведенных выше ответов). Я также вручную реализовал полностью развернутое дерево решений, которое представляет собой просто код с непосредственными операндами. Ни петель, ни таблиц.
Дерево решений (high_bit_unrolled) оказалось на 69% быстрее, за исключением случая n = 0, для которого двоичный поиск имеет явный тест.
Специальный тест двоичного поиска для случая 0 всего на 48% быстрее, чем дерево решений, которое не имеет специального теста.
Компилятор, машина: (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5).
Программа быстрого и грязного тестирования:
При использовании только -O2 разница становится больше. Дерево решений почти в четыре раза быстрее.
Я также сравнил наивный код сдвига бит:
Как и следовало ожидать, это быстро только для небольших чисел. Определив, что самый высокий бит равен 1 для n == 1, тест был проведен более чем на 80% быстрее. Однако у половины случайно выбранных чисел в 63-битном пространстве установлен 63-й бит!
На входе 0x3FFFFFFFFFFFFFFF версия дерева решений немного быстрее, чем на 1, и показывает, что она на 1120% быстрее (в 12,2 раза), чем битовый сдвигатель.
Я также буду сравнивать дерево решений со встроенными функциями GCC, а также попробую сочетание входных данных, а не повторение с одним и тем же числом. Могут быть некоторые предсказания залипания ветвления и, возможно, некоторые нереалистичные сценарии кеширования, которые делают его искусственно быстрее при повторениях.
источник
Что о
?
источник
1 регистр, 13 инструкций. Вы не поверите, но это обычно быстрее, чем указанная выше инструкция BSR, которая работает в линейном времени. Это логарифмическое время.
Из http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit
источник
__builtin_clz
если он включен-march=native
или что-то в этом роде (поскольку он быстр на каждом процессоре, который его поддерживает). Даже на процессорах, подобных семейству AMD Bulldozer, где BSR «медленный», он не такой медленный: 7 операций в секунду с задержкой в 4 цикла и один на пропускную способность 4 цикла. На Атоме BSR действительно медленный: 16 циклов. В Silvermont это 10 мопов с задержкой 10 циклов. Это может быть немного меньшая задержка, чем BSR на Silvermont, но IDK.Вот несколько (простых) тестов алгоритмов, которые в настоящее время приведены на этой странице ...
Алгоритмы не тестировались на всех входах unsigned int; так что сначала проверьте это, прежде чем что-то слепо использовать;)
На моей машине clz (__builtin_clz) и asm работают лучше всего. asm кажется даже быстрее, чем clz ... но это может быть из-за простого теста ...
источник
Хотя я, вероятно, использовал бы этот метод только в том случае, если мне абсолютно необходима наилучшая возможная производительность (например, для написания какого-либо ИИ настольной игры с использованием битовых досок), наиболее эффективным решением является использование встроенного ASM. Код с пояснениями см. В разделе «Оптимизация» этого сообщения в блоге .
источник
Мне нужна была процедура для этого, и перед поиском в Интернете (и нахождением этой страницы) я придумал собственное решение, основанное на бинарном поиске. Хотя уверен, что кто-то делал это раньше! Он работает в постоянное время и может быть быстрее, чем опубликованное "очевидное" решение, хотя я не делаю никаких серьезных заявлений, просто публикую его для интереса.
источник
это своего рода бинарный поиск, он работает со всеми (беззнаковыми!) целыми типами
сделать завершенным:
источник
typedef
s или вообще чего-либо, кроме макросов препроцессора. Это общепринятое соглашение.Здесь есть слишком сложные ответы. Технику Debruin следует использовать только тогда, когда вход уже является степенью двойки, иначе есть способ лучше. При мощности двух входов Debruin является самым быстрым, даже быстрее, чем
_BitScanReverse
на любом процессоре, который я тестировал. Однако в общем случае_BitScanReverse
(или независимо от того, что внутреннее свойство вызывается в вашем компиляторе) является самым быстрым (хотя на некоторых процессорах он может быть микрокодирован).Если внутренняя функция не подходит, вот оптимальное программное решение для обработки общих входных данных.
Обратите внимание, что эта версия не требует поиска Debruin в конце, в отличие от большинства других ответов. Он вычисляет положение на месте.
Таблицы могут быть предпочтительнее, однако, если вы вызываете его несколько раз, риск промаха кеша будет затмевается ускорением таблицы.
Это должно обеспечить наивысшую пропускную способность из всех приведенных здесь программных ответов, но если вы вызываете его только изредка, предпочтите решение без таблиц, такое как мой первый фрагмент.
источник
Как указано в приведенных выше ответах, существует несколько способов определить наиболее значимый бит. Однако, как также указывалось, методы, вероятно, будут уникальными для 32-битных или 64-битных регистров. На странице битхаков stanford.edu представлены решения, которые работают как для 32-битных, так и для 64-битных вычислений. Немного поработав, их можно объединить, чтобы обеспечить надежный кросс-архитектурный подход к получению MSB. Решение, к которому я пришел, скомпилированное / работающее на 64- и 32-битных компьютерах, было:
источник
#ifdef BUILD_64
флага? В этом случае переопределение в условном выражении не требуется.Версия на C с использованием последовательного приближения:
Преимущество: время работы постоянно, независимо от предоставленного числа, поскольку количество петель всегда одинаково. (4 цикла при использовании "unsigned int")
источник
msb += (n>>msb) ? step : -step;
), больше компиляторов, вероятно, сделают asm без ветвей, избегая ошибочных прогнозов ветвлений на каждом шаге ( stackoverflow.com/questions/11227809/… ).Я знаю, что этот вопрос очень старый, но, просто реализовав функцию msb () , я обнаружил, что большинство решений, представленных здесь и на других веб-сайтах, не обязательно являются наиболее эффективными - по крайней мере, для моего личного определения эффективности (см. Также Обновление ниже ). Вот почему:
Большинство решений (особенно те, которые используют какую-то схему двоичного поиска или наивный подход, который выполняет линейное сканирование справа налево), похоже, игнорируют тот факт, что для произвольных двоичных чисел не так много, которые начинаются с очень длинной последовательности нули. Фактически, для любой разрядности половина всех целых чисел начинается с 1, а четверть из них - с 01 . Видишь, к чему я клоню? Мой аргумент состоит в том, что линейное сканирование, начиная с позиции наиболее значимого бита до наименее значимого (слева направо), не является таким «линейным», как могло бы показаться на первый взгляд.
Можно показать 1 , что для любой разрядности среднее количество битов, которые необходимо протестировать, не превышает 2. Это переводится в амортизированную временную сложность O (1) по отношению к количеству бит (!) ,
Конечно, наихудший случай по-прежнему O (n) , хуже, чем O (log (n)), который вы получаете с подходами, подобными двоичному поиску, но поскольку наихудших случаев так мало, они незначительны для большинства приложений ( Обновить : not very: Их может быть немного, но они могут возникать с большой вероятностью - см. Обновление ниже).
Вот "наивный" подход, который я придумал, который, по крайней мере, на моей машине превосходит большинство других подходов (схемы двоичного поиска для 32-битных целых чисел всегда требуют log 2 (32) = 5 шагов, тогда как этот глупый алгоритм требует меньше чем 2 в среднем) - извините, что это C ++, а не чистый C:
Обновление : в то время как то, что я написал здесь, совершенно верно для произвольных целых чисел, где каждая комбинация битов одинаково вероятна (мой тест скорости просто измерял, сколько времени потребовалось для определения MSB для всех 32-битных целых чисел), реальных целых чисел для Какая такая функция будет вызываться, обычно следуют другому шаблону: в моем коде, например, эта функция используется для определения того, является ли размер объекта степенью 2, или для нахождения следующей степени 2, большей или равной размер объекта . Я предполагаю, что большинство приложений, использующих MSB, используют числа, которые намного меньше максимального числа, которое может представлять целое число (размеры объектов редко используют все биты в size_t). В этом случае мое решение фактически будет работать хуже, чем подход бинарного поиска, поэтому последний, вероятно, следует предпочесть, хотя мое решение будет быстрее перебирать все целые числа.
TL; DR: Реальные целые числа, вероятно, будут иметь тенденцию к худшему случаю этого простого алгоритма, что в конечном итоге ухудшит его работу - несмотря на то, что он амортизирует O (1) для действительно произвольных целых чисел.
1 Аргумент выглядит следующим образом (черновик): пусть n будет количеством бит (разрядность). Всего имеется 2 n целых чисел, которые могут быть представлены n битами. Есть 2 n - 1 целых чисел, начинающихся с 1 (первая фиксированная 1 , оставшиеся n - 1 бит могут быть любыми). Эти целые числа требуют только одного взаимодействия цикла для определения MSB. Кроме того, есть 2 n - 2 целых числа, начинающихся с 01 , требующих 2 итераций, 2 n - 3 целых чисел, начинающихся с 001 , требующих 3 итераций, и так далее.
Если мы просуммируем все необходимые итерации для всех возможных целых чисел и разделим их на 2 n , общее количество целых чисел, мы получим среднее количество итераций, необходимых для определения MSB для n- битных целых чисел:
(1 * 2 п - 1 + 2 * 2 п - 2 + 3 * 2 п - 3 + ... + п) / 2 п
Эта серия средних итераций фактически сходится и имеет предел 2 для n в сторону бесконечности.
Таким образом, наивный алгоритм с написанием слева направо фактически имеет амортизированную постоянную временную сложность O (1) для любого количества битов.
источник
c99дал нам
log2
. Это устраняет необходимость во всех специальныхlog2
реализациях соуса, которые вы видите на этой странице. Вы можете использовать стандартнуюlog2
реализацию следующим образом:n
Из0UL
потребностей, остерегаться , а также, потому что:Я написал пример с этой проверкой , что произвольно устанавливает
Index
дляULONG_MAX
здесь: https://ideone.com/u26vsiзрительно-студияследствие единственного ответа ephemient gcc :
В документации
_BitScanReverse
указано, чтоIndex
это:На практике я обнаружил, что if
n
is0UL
thatIndex
имеет значение0UL
, как и дляn
of1UL
. Но единственное , что гарантировано в документации в случаеn
из0UL
что возвращение:Таким образом, аналогично предпочтительной
log2
реализации, описанной выше,Index
в этом случае должен быть проверен возврат, установленный на отмеченное значение. Я снова написал пример использованияULONG_MAX
этого значения флага здесь: http://rextester.com/GCU61409источник
_BitScanReverse
возвращает 0, только если вход был0
. Это похоже наBSR
инструкцию x86 , которая устанавливает ZF только на входе, а не на выходе. Интересно, что MS называет docsindex
незаданным, если1
бит не найден; это также соответствует поведению x86 asmbsr
. (AMD документирует это как оставление регистра назначения без изменений на src = 0, но Intel просто сообщает неопределенный вывод, хотя их процессоры действительно реализуют поведение без изменений.) Это не похоже на x86lzcnt
, который дает32
для не найденного._BitScanReverse
использует индексирование с нуля, таким образом, еслиn
1, то индекс установленного бита на самом деле равен 0. К сожалению, как вы говорите, еслиn
это 0, то вывод также равен 0 :( Это означает, что нет способа использовать возврат к различатьn
1 или 0. Это то, что я пытался передать. Как вы думаете, есть лучший способ сказать это?Index
. Это не возвращаемое значение. Он возвращает логическое значение false, если вход был нулевым (и поэтому Index передается по ссылке, а не возвращается в обычном режиме). godbolt.org/g/gQKJdE . И я проверил: несмотря на формулировку документов MS,_BitScanReverse
индекс не остается включеннымn==0
: вы просто получаете то значение, которое было в регистре, которое он использовал. (Что в вашем случае, вероятно, было тем же регистром, для которого он использовалсяIndex
позже, что привело к тому, что вы увидели a0
).log2
с C99.Подумайте о побитовых операторах.
Я неправильно понял вопрос в первый раз. Вы должны создать int с установленным крайним левым битом (остальные равны нулю). Предполагая, что для cmp установлено это значение:
источник
8
Должно бытьCHAR_BIT
. Маловероятно, что это будет самый быстрый способ, потому что при выходе из цикла произойдет неверное предсказание ветвления, если оно не используется повторно с одним и тем же входом. Кроме того, для небольших входных данных (много нулей) он должен много зацикливаться. Это похоже на резервный способ, который вы бы использовали в качестве простой для проверки версии в модульном тесте для сравнения с оптимизированными версиями.Расширяя тест Джоша ... можно улучшить clz следующим образом
По поводу asm: обратите внимание, что есть bsr и bsrl (это "длинная" версия). нормальный может быть немного быстрее.
источник
Обратите внимание, что вы пытаетесь вычислить целое число log2 целого числа,
Обратите внимание, что вы можете пытаться искать более 1 бита за раз.
Этот подход использует двоичный поиск
Другой метод двоичного поиска, возможно, более читаемый,
И поскольку вы захотите проверить это,
источник
Ввод этого, поскольку это «еще один» подход, кажется, отличается от других, уже приведенных.
возвращается,
-1
еслиx==0
, в противном случаеfloor( log2(x))
(максимальный результат 31)Уменьшите проблему с 32 до 4 бит, затем используйте таблицу. Возможно, неэлегантно, но прагматично.
Это то, что я использую, когда не хочу использовать
__builtin_clz
из-за проблем с переносимостью.Чтобы сделать его более компактным, вместо этого можно было бы использовать цикл для уменьшения, добавляя каждый раз 4 к r, максимум 7 итераций. Или какой-нибудь гибрид, например (для 64 бит): цикл уменьшить до 8, тест уменьшить до 4.
источник
Ого, это было много ответов. Я не сожалею, что отвечу на старый вопрос.
Этот ответ очень похож на другой ... да ладно.
источник
1<<k
приятный штрих. Что насчет масок?(1 << (1<<k-1)-1<< (1<<k-1)
? (most optimal
? Вы сравните SUPERLATIVE?)&
и&~
.) Вы можете заменить шестнадцатеричные константы на подобные((type)1<<(1<<k))-1<<(1<<k)
.Код:
Или получите целую часть инструкции FPU FYL2X (Y * Log2 X), установив Y = 1
источник
double
, что, вероятно, хорошо, если оно действительно сохраняет / перезагружает вместо каламбура типа каким-то другим способом, например с такойmovq
инструкцией, как на x86.[7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF]
.Другой плакат предоставил поисковую таблицу с использованием байтового поиска. Если вы хотите немного повысить производительность (за счет 32 КБ памяти вместо 256 записей поиска), вот решение, использующее 15-битную таблицу поиска на C # 7 для .NET. .
Самое интересное - это инициализация таблицы. Поскольку это относительно небольшой блок, который нам нужен на время жизни процесса, я выделяю для этого неуправляемую память, используя
Marshal.AllocHGlobal
. Как видите, для максимальной производительности весь пример написан как нативный:Таблица требует однократной инициализации с помощью приведенного выше кода. Он доступен только для чтения, поэтому одна глобальная копия может использоваться для одновременного доступа. С помощью этой таблицы вы можете быстро найти целочисленный журнал 2 , который мы здесь ищем, для всех различных целочисленных значений ширины (8, 16, 32 и 64 бит).
Обратите внимание, что записи таблицы для
0
единственного целого числа, для которого понятие «самый высокий установленный бит» не определено, присваивается значение-1
. Это различие необходимо для правильной обработки 0-значных верхних слов в приведенном ниже коде. Без лишних слов, вот код для каждого из различных целочисленных примитивов:ulong (64-битная) версия
uint (32-битная) версия
Различные перегрузки для вышеперечисленных
Это законченное рабочее решение, которое обеспечивает лучшую производительность в .NET 4.7.2 для множества альтернатив, которые я сравнивал со специализированной системой тестирования производительности. Некоторые из них упомянуты ниже. Параметры теста были равномерной плотностью всех 65-битных позиций, то есть 0 ... 31/63 плюс значение
0
(которое дает результат -1). Биты ниже позиции целевого индекса заполнялись случайным образом. Тесты проводились только для x64 , в режиме выпуска, с включенной JIT-оптимизацией.На этом мой официальный ответ окончен; Ниже приведены некоторые случайные заметки и ссылки на исходный код для альтернативных кандидатов на тестирование, связанных с тестированием, которое я провел для проверки производительности и правильности приведенного выше кода.
Представленная выше версия с кодом Tab16A неизменно выигрывала во многих тестах. Этих различных кандидатов в активной рабочей / временной форме можно найти здесь , здесь и здесь .
Примечательно то, что ужасная производительность
ntdll.dll!RtlFindMostSignificantBit
via P / Invoke:Это действительно очень плохо, потому что вот настоящая функция:
Я не могу себе представить, насколько низкая производительность связана с этими пятью строками, поэтому виноваты штрафы за управляемый / собственный переход. Я также был удивлен тем, что при тестировании действительно предпочтение отдавалось
short
таблицам прямого поиска размером 32 КБ (и 64 КБ) (16-бит), а не 128-байтовым (и 256-байтовым)byte
(8-битным) таблицам поиска. Я думал, что следующее будет более конкурентоспособным с 16-битным поиском, но последний последовательно превосходит это:Последнее, на что я хочу указать, это то, что я был весьма шокирован тем, что мой метод деБрюйна не сработал лучше. Это метод, который я раньше широко использовал:
Существует много дискуссий о том, насколько превосходны и велики методы деБрюйна в этом вопросе SO , и я был склонен согласиться. Я предполагаю, что, хотя оба метода deBruijn и прямой поисковой таблицы (которые я считаю самыми быстрыми) должны выполнять поиск в таблице, и оба имеют минимальное ветвление, только deBruijn имеет 64-битную операцию умножения. Я тестировал
IndexOfMSB
здесь только функции, а не deBruijn,IndexOfLSB
но я ожидаю, что у последнего будет гораздо больше шансов, поскольку у него намного меньше операций (см. Выше), и я, вероятно, продолжу использовать его для LSB.источник
Мой скромный метод очень прост:
MSB (x) = INT [журнал (x) / журнал (2)]
Перевод: MSB x - это целочисленное значение (Log of Base x, деленное на Log Base 2).
Его можно легко и быстро адаптировать к любому языку программирования. Попробуйте использовать его на своем калькуляторе, чтобы убедиться, что он работает.
источник
int(math.log((1 << 48) - 1) / math.log(2))
это 48.Вот быстрое решение для C, которое работает в GCC и Clang ; готов к копированию и вставке.
И немного улучшенная версия для C ++ .
Код предполагает, что
value
этого не будет0
. Если вы хотите разрешить 0, вам нужно изменить его.источник
Я предполагаю, что ваш вопрос касается целого числа (называемого v ниже), а не целого числа без знака.
Если вы хотите, чтобы он работал без учета знака, вы можете добавить лишнее 'v << = 1;' перед циклом (и соответственно измените значение r на 30). Пожалуйста, дайте мне знать, если я что-нибудь забыл. Я не тестировал его, но он должен работать нормально.
источник
v <<= 1
это неопределенное поведение (UB) , когдаv < 0
.0x8000000
, может ты имеешь ввиду лишний 0 там.