Вес Хэмминга строки — это количество символов, отличных от нулевого символа используемого алфавита . Таким образом, он эквивалентен расстоянию Хэмминга от строки из всех нулей той же длины. Для наиболее типичного случая строки битов это количество единиц в строке или сумма цифр двоичного представления заданного числа и ℓ ₁ нормы битового вектора. В этом двоичном случае это также называется подсчетом популяции , [1] popcount , боковой суммой , [2] или суммированием битов . [3]
Вес Хэмминга назван в честь американского математика Ричарда Хэмминга , хотя он не был создателем этого понятия. [5] Вес Хэмминга двоичных чисел уже использовался в 1899 году Джеймсом У. Л. Глейшером для получения формулы для числа нечетных биномиальных коэффициентов в одной строке треугольника Паскаля . [6] Ирвинг С. Рид ввел концепцию, эквивалентную весу Хэмминга в двоичном случае, в 1954 году. [7]
Вес Хэмминга используется в нескольких дисциплинах, включая теорию информации , теорию кодирования и криптографию . Примеры применения веса Хэмминга включают:
Подсчет популяции битовой строки часто требуется в криптографии и других приложениях. Расстояние Хэмминга двух слов A и B может быть рассчитано как вес Хэмминга A xor B . [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 = 0x333333333333333 ; //двоичный: 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 году [12] , побитовое И x с x − 1 отличается от x только обнулением младшего ненулевого бита: вычитание 1 изменяет самую правую строку нулей на 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 ; return count ; }
Если разрешено большее использование памяти, мы можем вычислить вес Хэмминга быстрее, чем вышеперечисленные методы. С неограниченной памятью мы могли бы просто создать большую таблицу поиска веса Хэмминга для каждого 64-битного целого числа. Если мы можем сохранить таблицу поиска функции Хэмминга для каждого 16-битного целого числа, мы можем сделать следующее, чтобы вычислить вес Хэмминга для каждого 32-битного целого числа.
static uint8_t wordbits [ 65536 ] = { /* количество бит целых чисел от 0 до 65535 включительно */ }; //Этот алгоритм использует 3 арифметические операции и 2 чтения памяти. int popcount32e ( uint32_t x ) { return wordbits [ x & 0xFFFF ] + wordbits [ x >> 16 ]; }
// При желании таблицу wordbits[] можно заполнить с помощью этой функции int popcount32e_init ( void ) { uint32_t i ; uint16_t x ; int count ; for ( i = 0 ; i <= 0xFFFF ; i ++ ) { x = i ; for ( count = 0 ; x ; count ++ ) // заимствовано из popcount64d() выше x &= x - 1 ; wordbits [ i ] = count ; } }
Мула и др. [13] показали, что векторизованная версия popcount64b может работать быстрее, чем специализированные инструкции (например, popcnt на процессорах x64).
В кодировании с исправлением ошибок минимальный вес Хэмминга, обычно называемый минимальным весом w min кода, — это вес ненулевого кодового слова с наименьшим весом. Вес w кодового слова — это количество единиц в слове. Например, слово 11001010 имеет вес 4.
В линейном блочном коде минимальный вес также является минимальным расстоянием Хэмминга ( d min ) и определяет способность кода к исправлению ошибок. Если w min = n , то d min = n и код исправит до d min /2 ошибок. [14]
Некоторые компиляторы C предоставляют встроенные функции, которые обеспечивают возможности подсчета битов. Например, GCC (начиная с версии 3.4 в апреле 2004 года) включает встроенную функцию __builtin_popcount
, которая будет использовать инструкцию процессора, если она доступна, или эффективную реализацию библиотеки в противном случае. [15] LLVM-GCC включает эту функцию с версии 1.5 в июне 2005 года. [16]
В стандартной библиотеке 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 года. [17]
В Common Lisp функция logcount
возвращает количество единичных битов в неотрицательном целом числе. (Для отрицательных целых чисел она возвращает количество нулевых битов в дополнительном коде.) В любом случае целое число может быть BIGNUM.
Начиная с GHC 7.4, базовый пакет Haskell имеет popCount
функцию, доступную для всех типов, которые являются экземплярами класса Bits
(доступную из Data.Bits
модуля). [18]
Версия MySQL языка SQL предоставляет BIT_COUNT()
стандартную функцию. [19]
В Fortran 2008 есть стандартная, встроенная, элементарная функция, popcnt
возвращающая количество ненулевых битов в целом числе (или массиве целых чисел). [20]
Некоторые программируемые научные карманные калькуляторы имеют специальные команды для расчета количества установленных битов, например, #B
на HP-16C [3] [21] и WP 43S , [22] [23] #BITS
[24] [25] или BITSUM
[26] [27] на эмуляторах HP-16C, а также nBITS
на WP 34S . [28] [29]
FreePascal реализует popcnt начиная с версии 3.0. [30]
CXi
.POPC
инструкцию, [10] [1] , но большинство реализаций ее не реализуют, требуя ее эмуляции операционной системой. [31]SADD
инструкцию с 1999 года. SADD a,b,c
Она подсчитывает все биты, которые равны 1 в b и 0 в c, и записывает результат в a.CIX
).ONES
инструкцией для выполнения 32-битного подсчета населения. [32]POPCNT
инструкцию как часть расширений SSE4a в 2007 году.POPCNT
инструкция с расширением набора инструкций SSE4.2 , впервые появившаяся в процессоре Core i7 на базе Nehalem , выпущенном в ноябре 2008 года.VCNT
инструкцию как часть расширений Advanced SIMD ( NEON ).CPOP
инструкцию как часть расширения Bit Manipulation (B). [33]Раздел 6.3: «В общем случае количество пальцев, по которым нам нужно следовать, будет равно количеству единиц в двоичном представлении расстояния от узла до запроса».