Вес Хэмминга строки — это количество символов , отличных от нулевого символа используемого алфавита . Таким образом, оно эквивалентно расстоянию Хэмминга от нулевой строки той же длины. Для наиболее типичного случая, строки битов , это количество единиц в строке или сумма цифр двоичного представления данного числа и нормы ℓ ₁ битового вектора. В этом двоичном случае его также называют подсчетом населения , [1] popcount , боковой суммой , [2] или суммированием битов . [3]
Гиря Хэмминга названа в честь Ричарда Хэмминга , хотя это понятие принадлежит не ему. [7] Вес Хэмминга двоичных чисел уже использовался в 1899 году Джеймсом В.Л. Глейшером для получения формулы для количества нечетных биномиальных коэффициентов в одной строке треугольника Паскаля . [8] Ирвинг С. Рид ввел понятие, эквивалентное весу Хэмминга в двоичном случае, в 1954 году. [9]
Вес Хэмминга используется в нескольких дисциплинах, включая теорию информации , теорию кодирования и криптографию . Примеры применения веса Хэмминга включают:
Подсчет численности битовой строки часто необходим в криптографии и других приложениях. Расстояние Хэмминга двух слов A и B можно рассчитать как вес Хэмминга A xor B . [1]
Проблема его эффективной реализации широко изучена. На некоторых процессорах доступна отдельная операция вычисления или параллельные операции над битовыми векторами. Для процессоров, лишенных этих функций, лучшие известные решения основаны на добавлении счетчиков в древовидную структуру. Например, чтобы подсчитать количество 1 бит в 16-битном двоичном числе a = 0110 1100 1011 1010, можно выполнить следующие операции:
Здесь операции такие же, как в языке программирования C , то X >> Y
есть сдвиг X вправо на бит Y, X и Y означает побитовое И для X и Y, а + — обычное сложение. Лучшие алгоритмы, известные для этой проблемы, основаны на концепции, проиллюстрированной выше, и приведены здесь: [1]
//типы и константы, используемые в функциях ниже //uint64_t — это 64-битный целочисленный тип переменной без знака (определенный в версии C99 языка C) const uint64_t m1 = 0x5555555555555555 ; //двоичный: 0101... const uint64_t m2 = 0x3333333333333333 ; //двоичный: 00110011.. const uint64_t m4 = 0x0f0f0f0f0f0f0f0f ; //двоичный: 4 нуля, 4 единицы ... const uint64_t m8 = 0x00ff00ff00ff00ff ; //двоичный: 8 нулей, 8 единиц ... const uint64_t m16 = 0x0000ffff0000ffff ; //двоичный: 16 нулей, 16 единиц ... const uint64_t m32 = 0x00000000ffffffff ; //двоичный: 32 нуля, 32 единицы const uint64_t h01 = 0x0101010101010101 ; //сумма 256 в степени 0,1,2,3... //Это простая реализация, показанная для сравнения, //и чтобы помочь понять лучшие функции. //Этот алгоритм использует 24 арифметических операции (сдвиг, сложение и). int popcount64a ( uint64_t x ) { x = ( x & m1 ) + (( x >> 1 ) & m1 ); //помещаем счетчик каждых 2 битов в эти 2 бита x = ( x & m2 ) + (( x >> 2 ) & m2 ); // помещаем счетчик каждых 4 битов в эти 4 бита x = ( x & m4 ) + (( x >> 4 ) & m4 ); // помещаем счетчик каждых 8 бит в эти 8 бит x = ( x & m8 ) + (( x >> 8 ) & m8 ); //помещаем счетчик каждых 16 бит в эти 16 бит x = ( x & m16 ) + (( x >> 16 ) & m16 ); //помещаем счетчик каждых 32 битов в эти 32 бита x = ( x & m32 ) + (( x >> 32 ) & m32 ); //помещаем счетчик каждых 64 бит в эти 64 бита return x ; } //Это использует меньше арифметических операций, чем любая другая известная //реализация на машинах с медленным умножением. //Этот алгоритм использует 17 арифметических операций. int popcount64b ( uint64_t x ) { x -= ( x >> 1 ) & m1 ; //помещаем счетчик каждых 2 битов в эти 2 бита x = ( x & m2 ) + (( x >> 2 ) & m2 ); //помещаем счетчик каждых 4 битов в эти 4 бита x = ( x + ( x >> 4 )) & m4 ; //помещаем счетчик каждых 8 бит в эти 8 бит x += x >> 8 ; //помещаем счетчик каждых 16 бит в младшие 8 бит x += x >> 16 ; //помещаем счетчик каждых 32 бит в младшие 8 бит x += x >> 32 ; //помещаем счетчик каждых 64 бит в младшие 8 бит return x & 0x7f ; } //При этом используется меньше арифметических операций, чем в любой другой известной //реализации на машинах с быстрым умножением. //Этот алгоритм использует 12 арифметических операций, одна из которых — умножение. int popcount64c ( uint64_t x ) { x -= ( x >> 1 ) & m1 ; //помещаем счетчик каждых 2 битов в эти 2 бита x = ( x & m2 ) + (( x >> 2 ) & m2 ); //помещаем счетчик каждых 4 битов в эти 4 бита x = ( x + ( x >> 4 )) & m4 ; //помещаем счетчик каждых 8 бит в эти 8 бит return ( x * h01 ) >> 56 ; //возвращает левые 8 бит x + (x<<8) + (x<<16) + (x<<24) + ... }
Вышеупомянутые реализации имеют лучшее поведение в наихудшем случае из всех известных алгоритмов. Однако, когда ожидается, что значение будет иметь несколько ненулевых битов, вместо этого может быть более эффективно использовать алгоритмы, которые считают эти биты по одному. Как описал Вегнер в 1960 году, [14] побитовое И для x с x - 1 отличается от x только обнулением младшего ненулевого бита: вычитание 1 изменяет самую правую строку из 0 на 1, а крайнюю правую 1 меняет на 0. Если изначально x имело n бит, равных 1, то после всего лишь n итераций этой операции x уменьшится до нуля. Следующая реализация основана на этом принципе.
//Лучше, когда большинство битов в x равны 0 //Этот алгоритм работает одинаково для всех размеров данных. //Этот алгоритм использует 3 арифметических операции и 1 сравнение/ветвь на каждый «1» бит в x. int popcount64d ( uint64_t x ) { int count ; for ( count = 0 ; x ; count ++ ) x &= x - 1 ; счетчик возврата ; }
Если разрешено большее использование памяти, мы можем вычислить вес Хэмминга быстрее, чем описанные выше методы. Имея неограниченную память, мы могли бы просто создать большую таблицу поиска веса Хэмминга для каждого 64-битного целого числа. Если мы можем сохранить таблицу поиска функции Хэмминга для каждого 16-битного целого числа, мы можем сделать следующее, чтобы вычислить вес Хэмминга каждого 32-битного целого числа.
static uint8_t wordbits [ 65536 ] = { /* количество бит целых чисел от 0 до 65535 включительно */ }; //Этот алгоритм использует 3 арифметических операции и 2 чтения из памяти. int popcount32e ( uint32_t x ) { возвращаем словесные биты [ x & 0xFFFF ] + словесные биты [ x >> 16 ]; }
//При желании таблица wordbits[] может быть заполнена с помощью этой функции int popcount32e_init ( void ) { uint32_t i ; uint16_t х ; число интервалов ; для ( я знак равно 0 ; я <= 0xFFFF ; я ++ ) { Икс знак равно я ; for ( count = 0 ; x ; count ++ ) // заимствовано из popcount64d() выше x &= x - 1 ; словесные биты [ я ] = количество ; } }
Мула и др. [15] показали, что векторизованная версия popcount64b может работать быстрее, чем специальные инструкции (например, popcnt на процессорах x64).
В кодировании с исправлением ошибок минимальный вес Хэмминга, обычно называемый минимальным весом w min кода, представляет собой вес ненулевого кодового слова с наименьшим весом. Вес w кодового слова — это количество единиц в слове. Например, слово 11001010 имеет вес 4.
В линейном блочном коде минимальный вес также является минимальным расстоянием Хэмминга ( d min ) и определяет способность кода исправлять ошибки. Если w min = n , то d min = n и код исправит до d min /2 ошибок. [16]
Некоторые компиляторы C предоставляют встроенные функции, обеспечивающие подсчет битов. Например, GCC (начиная с версии 3.4 в апреле 2004 г.) включает встроенную функцию __builtin_popcount
, которая будет использовать инструкцию процессора, если она доступна, или эффективную реализацию библиотеки в противном случае. [17] LLVM-GCC включает эту функцию начиная с версии 1.5 в июне 2005 года. [18]
В стандартной библиотеке C++ структура данных битового массива bitset
имеет count()
метод, который подсчитывает количество установленных битов. В C++20<bit>
был добавлен новый заголовок , содержащий функции std::popcount
и std::has_single_bit
принимающие аргументы целочисленных типов без знака.
В Java структура данных расширяемого битового массива BitSet
имеет BitSet.cardinality()
метод, который подсчитывает количество установленных битов. Кроме того, существуют Integer.bitCount(int)
функции Long.bitCount(long)
для подсчета битов в примитивных 32-битных и 64-битных целых числах соответственно. Кроме того, BigInteger
класс целых чисел произвольной точности также имеет BigInteger.bitCount()
метод, подсчитывающий биты.
В Python тип int
имеет bit_count()
метод подсчета количества установленных бит. Эта функциональность была представлена в Python 3.10, выпущенном в октябре 2021 года. [19]
В Common Lisp функция logcount
, учитывая неотрицательное целое число, возвращает количество 1 бит. (Для отрицательных целых чисел возвращается количество нулевых битов в записи дополнения до 2.) В любом случае целое число может быть БОЛЬШИМ ЧИСЛОМ.
Начиная с GHC 7.4, базовый пакет Haskell имеет popCount
функцию, доступную для всех типов, которые являются экземплярами класса Bits
(доступны из Data.Bits
модуля). [20]
MySQL- версия языка SQL предоставляет BIT_COUNT()
стандартную функцию. [21]
В Фортране 2008 есть стандартная встроенная элементарная функция, popcnt
возвращающая количество ненулевых битов в целом числе (или целочисленном массиве). [22]
Некоторые программируемые карманные научные калькуляторы имеют специальные команды для расчета количества установленных бит, например, #B
HP -16C [3] [23] и WP 43S , [24] [25] #BITS
[26] [27] или BITSUM
[28] [ 29] ] на эмуляторах HP-16C и nBITS
на WP 34S . [30] [31]
FreePascal реализует popcnt начиная с версии 3.0. [32]
CXi
.POPC
инструкцию, [12] [1], но большинство реализаций не реализуют ее, требуя ее эмуляции операционной системой. [33]SADD
инструкцию с 1999 года. SADD a,b,c
Подсчитывает все биты, равные 1 в b и 0 в c, и записывает результат в a.CIX
).ONES
инструкцию для выполнения 32-битного подсчета численности населения. [34]POPCNT
стала частью расширений SSE4a .POPCNT
инструкция с расширением набора команд SSE4.2 , впервые доступная в процессоре Core i7 на базе Nehalem , выпущенном в ноябре 2008 года.VCNT
как часть расширений Advanced SIMD ( NEON ).CPOP
как часть расширения Bit Manipulation (B). [35]Раздел 6.3: «В общем, количество пальцев, за которыми нам нужно следовать, будет количеством единиц в двоичном представлении расстояния от узла до запроса».