Побитовые операторы (кроме сдвигов) имеют какой-либо математический смысл в основе 10?

Согласно Wiki сдвиги могут использоваться для вычисления полномочий 2:

Левый арифметический сдвиг n эквивалентен умножению на 2^n (если значение не переполняется), в то время как правильный арифметический сдвиг n дополнительного значения two эквивалентен делению на 2^n и округление к отрицательной бесконечности.

Я всегда задавался вопросом если любые другие побитовые операторы (~,|,&,^) имейте какой-либо математический смысл при применении для базирования 10? Я понимаю, как они работают, но делают результаты таких операций могут использоваться для вычисления чего-либо полезного в десятичном мире?

31
задан Daniel Egeberg 23 July 2010 в 18:42
поделиться

6 ответов

"yep base-10 is what I mean"

В этом случае, да, они могут быть расширены до base-10 несколькими способами, хотя они и близко не так полезны, как в двоичном формате.

Одна из идей заключается в том, что & , | и т. Д. Аналогичны выполнению арифметики mod-2 для отдельных двоичных цифр. Если a и b являются одиночными двоичными цифрами, то

a & b = a * b (mod 2)
a ^ b = a + b (mod 2)
   ~a = 1-a   (mod 2)
a | b = ~(~a & ~b) = 1 - (1-a)*(1-b) (mod 2)

эквиваленты в базе 10 будут (обратите внимание, что они применяются к каждой цифре, а не к целое число)

a & b = a * b (mod 10)
a ^ b = a + b (mod 10)
   ~a = 9-a   (mod 10)
a | b = ~(~a & ~b) = 9 - (9-a)*(9-b) (mod 10)

Первые три полезны при разработке схем, которые используют BCD ( ~ a является дополнением к 9 ), например, калькуляторы без графического представления. , хотя при написании уравнений мы просто используем * и + , а не & и ^ . Первый также, по-видимому, используется в некоторых старых шифрах .

23
ответ дан 27 November 2019 в 22:18
поделиться

Забавный трюк для замены двух целых чисел без временной переменной - использование побитового XOR:

void swap(int &a, int &b) {
   a = a ^ b;
   b = b ^ a; //b now = a
   a = a ^ b; //knocks out the original a
}

Это работает, потому что XOR является коммутативным, поэтому a ^ b ^ b = a.

15
ответ дан 27 November 2019 в 22:18
поделиться

Да, есть и другие полезные операции, но они, как правило, ориентированы на операции с коэффициентами 2 (по очевидным причинам), например, проверка на четность/нечетность, проверка на коэффициент 2, округление вверх/вниз до ближайшего коэффициента 2 и т. д.

См. Hacker's Delight Генри С. Уоррена.

6
ответ дан 27 November 2019 в 22:18
поделиться

На всех языках, которые я использовал (по общему признанию, почти исключительно на C и C-производных), побитовые операторы являются исключительно целочисленными операциями (если, конечно, вы не переопределите операцию).

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

РЕДАКТИРОВАТЬ

Для целых чисел побитовые операции всегда имеют смысл. Поразрядные операции предназначены для целых чисел.

n << 1 == n * 2
n << 2 == n * 4
n << 3 == n * 8

n >> 1 == n / 2
n >> 2 == n / 4
n >> 3 == n / 8

n & 1 == {0, 1}       // Set containing 0 and 1
n & 2 == {0, 2}       // Set containing 0 and 2
n & 3 == {0, 1, 2, 3} // Set containing 0, 1, 2, and 3

n | 1 == {1, n, n+1}
n | 2 == {2, n, n+2}
n | 3 == {3, n, n+1, n+2, n+3}

И так далее.

6
ответ дан 27 November 2019 в 22:18
поделиться

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

if ((a < 0) && (b < 0)
{
  do something
{

В языке C это можно заменить на:

if ((a & b) < 0)
{
  do something
{

Это работает, потому что один бит в целых числах используется как знаковый бит (1 означает отрицательное значение). Операция и (a & b) будет бессмысленным числом, но его знак будет побитовым и из знаков чисел, и, следовательно, проверка знака результата будет работать.

Это может улучшить производительность, а может и не улучшить. Выполнение двух булевых тестов/разветвлений будет хуже на ряде архитектур и компиляторов. Современные компиляторы x86, вероятно, могут генерировать одно ответвление, используя некоторые из более новых инструкций даже с обычным синтаксисом.

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

Аналогично можно использовать ~ | и ^, если все условия (x<0). Для условий сравнения можно использовать вычитание:

if ((a < b) | (b < c))
{
}

становится:

if (((a-b) | (b-c)) < 0)
{
}

потому что a-b будет отрицательным, только если a меньше b. С этим могут возникнуть проблемы, если вы приблизитесь к max int в 2 раза - т.е. арифметическое переполнение, так что будьте осторожны.

В некоторых случаях эти оптимизации оправданы, но в остальном они бесполезны. А если уж совсем придираться, то у чисел с плавающей точкой есть еще и знаковые биты... ;-)

ПРИМЕР: В качестве примера, допустим, вы хотите предпринять действия в зависимости от порядка a,b,c. Вы можете сделать несколько вложенных конструкций if/else или сделать вот так:

x = ((a < b) << 2) | ((b < c) << 1) | (c < a);
switch (x):

Я использовал это в коде с 9 условиями, а также используя вычитания, упомянутые выше, с дополнительной логикой для выделения знаковых битов вместо less-than. Это быстрее, чем эквивалент с ветвлением. Однако вам больше не нужно делать вычитание и выделение знаковых битов, потому что стандарт был давно обновлен, чтобы определить true как 1, а с условными переходами и тому подобным, фактическое less-than может быть довольно эффективным в наши дни.

1
ответ дан 27 November 2019 в 22:18
поделиться

Вы можете вычислять логарифмы, используя только побитовые операторы...

Нахождение экспоненты n = 2**x с помощью побитовых операций [логарифм по основанию 2 от n]

3
ответ дан 27 November 2019 в 22:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: