Как я могу разделить целое число со знаком, используя только двоичные операторы?
Я могу только использовать! ~ & ^ | + << >>
Я пишу это на C.
Я пытаюсь разделить число х на 2^n. Поэтому я подумал, что если бы я сдвинул x >> n, это сработало бы, но оно не работает с нечетными отрицательными целыми числами. Изначально это выглядело так:
int dl18(int x, int n) {
return (x >> n);
}
но если x = -9 и n = 1, вывод должен быть -4, но это -5. и если x = -9 и n = 0, вывод верен (-9).
Заранее спасибо.
Итак, я понял, что благодаря этому все работает, если только n = 0 и x не отрицательное число:
return (~(x >> 31) & (x >> n)) | ((x >> 31) & ((x >> n) + 1));
2 ответа
Предполагая два дополнения представления целых чисел со знаком и поведение арифметического сдвига >>
оператор, ответ может быть:
int dl18(int x, int n) {
if (x < 0) {
x += (1 << n) - 1;
}
return x >> n;
}
Дополнение необходимо, потому что >>
округляет отрицательные числа к отрицательной бесконечности. Добавляя 2^n - 1
результат всегда обрезается до нуля, как это происходит для /
оператор.
Из-за ваших требований, предполагая, что int
имеет 4
байты (и быть лишним педантичным CHAR_BIT = 8
), выражение может быть переписано (обфусцировано) как:
(x + ((x >> 31) & ((1 << n) + ~0))) >> n
Идея x >> 31
должен дублировать бит MSB, поэтому маска становится одной из всех (т.е. 0xFFFFFFFF
) или все нули, которые затем используются для сохранения или устранения ((1 << n) - 1)
от сложения. Круглые скобки &
необходимы, потому что сложение имеет более высокий приоритет, чем побитовое AND.
Этот алгоритм также используется компилятором GCC. Например:
int dl18_4(int x) { return x / 4; }
переводится с -O1
в:
dl18_4:
lea eax, [rdi+3] ; eax = rdi + 3
test edi, edi ; set sign flag if edi < 0
cmovns eax, edi ; eax = edi if SF = 0
sar eax, 2 ; eax = eax >> 2
ret
Обратите внимание, что смещение на отрицательное число вызывает неопределенное поведение, поэтому может быть безопаснее объявить второй параметр как unsigned int
,
Вот решение, которое позволяет избежать смещения битов отрицательных значений. Он предполагает представление с двойным дополнением, но не использует унарный отрицательный оператор.
Битовая маска используется для установки neg
к ненулевому значению, если x
отрицателен или равен нулю, если x
неотрицательный Здесь уловка, предложенная Grzegorz Szpetkowski, используется, чтобы избежать вычитания на 1: добавление ~0
вместо. Если x
отрицательно, значение x
изменяется на величину x
, Чтобы избежать использования унарного отрицательного здесь, используя трюк, предложенный @chux, мы пользуемся тем фактом, что для отрицательного значения в дополнении twos соответствующее положительное значение равно побитовому отрицанию отрицательного представления плюс 1.
Это величина x
может быть сдвинут бит, не сталкиваясь с зависимым от реализации поведением. После выполнения деления результат преобразуется обратно в отрицательное значение, если исходное значение было отрицательным, выполняя то же преобразование, что и раньше.
#include <stdio.h>
#include <limits.h>
int divide_2n(int x, unsigned n);
int main(void)
{
printf("-7 / 4 = %d\n", divide_2n(-7, 2));
printf("27 / 8 = %d\n", divide_2n(27, 3));
printf("-27 / 8 = %d\n", divide_2n(-27, 3));
printf("-9 / 2 = %d\n", divide_2n(-9, 1));
printf("-9 / 1 = %d\n", divide_2n(-9, 0));
return 0;
}
int divide_2n(int x, unsigned n)
{
unsigned n_bits = CHAR_BIT * sizeof(int);
unsigned neg = x & (1U << (n_bits + ~0));
if (neg) {
x = ~(unsigned)x + 1;
}
x = (unsigned)x >> n;
if (neg) {
x = ~x + 1;
}
return x;
}
-7 / 4 = -1
27 / 8 = 3
-27 / 8 = -3
-9 / 2 = -4
-9 / 1 = -9