Разделить на 10 с помощью сдвигов бит?
Можно ли разделить целое число без знака на 10, используя чистые сдвиги бит, сложение, вычитание и, возможно, умножение? Использование процессора с очень ограниченными ресурсами и медленным делением.
Ответы
Ответ 1
Вот что делает компилятор Microsoft при компиляции разделов малыми интегральными константами. Предположим, что 32-разрядная машина (код может быть соответствующим образом скорректирован):
int32_t div10(int32_t dividend)
{
int64_t invDivisor = 0x1999999A;
return (int32_t) ((invDivisor * dividend) >> 32);
}
Что здесь происходит, мы умножаемся на близкое приближение 1/10 * 2 ^ 32, а затем удаляем 2 ^ 32. Этот подход может быть адаптирован к разным делителям и разной ширине бит.
Это отлично работает для архитектуры ia32, так как его команда IMUL поместит 64-разрядный продукт в edx: eax, а значение edx будет желаемым. Viz (при условии, что дивиденд передается в eax, а фактор возвращается в eax)
div10 proc
mov edx,1999999Ah ; load 1/10 * 2^32
imul eax ; edx:eax = dividend / 10 * 2 ^32
mov eax,edx ; eax = dividend / 10
ret
endp
Даже на машине с инструкцией с медленным умножением это будет быстрее, чем разделение программного обеспечения.
Ответ 2
Хотя ответы, полученные до сих пор, соответствуют фактическому вопросу, они не соответствуют названию. Итак, вот решение, сильно вдохновленное Hacker Delight, которое действительно использует только бит-сдвиги.
unsigned divu10(unsigned n) {
unsigned q, r;
q = (n >> 1) + (n >> 2);
q = q + (q >> 4);
q = q + (q >> 8);
q = q + (q >> 16);
q = q >> 3;
r = n - (((q << 2) + q) << 1);
return q + (r > 9);
}
Я думаю, что это лучшее решение для архитектур, которым не хватает команды multiply.
Ответ 3
Конечно, вы можете, если вы можете жить с некоторой потерей точности. Если вы знаете диапазон значений ваших входных значений, вы можете получить битовое смещение и умножение, которое является точным. Некоторые примеры того, как вы можете разделить на 10, 60,... как описано в этом блоге, чтобы отформатировать время самым быстрым способом.
temp = (ms * 205) >> 11; // 205/2048 is nearly the same as /10
Ответ 4
Учитывая ответ Кубы Оберса, есть еще один в том же духе.
Он использует итеративную аппроксимацию результата, но я не ожидал бы каких-либо неожиданных результатов.
Скажем, нам нужно найти x
где x = v / 10
.
Хорошо используйте обратную операцию v = x * 10
, потому что она имеет свойство nice, когда x = a + b
, затем x * 10 = a * 10 + b * 10
.
Используйте x
как переменную, которая наилучшим образом приближается к результату. Когда поиск заканчивается, x
Будет удерживать результат. Ну, установите каждый бит b
из x
от самого значимого до менее значимого, один за другим, сравните (x + b) * 10
с v
. Если его меньше или равно v
, тогда бит b
устанавливается в x
. Чтобы проверить следующий бит, мы просто сдвигаем одну позицию вправо (разделим на две части).
Мы можем избежать умножения на 10, удерживая x * 10
и b * 10
в других переменных.
Это дает следующий алгоритм для деления v
на 10.
uin16_t x = 0, x10 = 0, b = 0x1000, b10 = 0xA000;
while (b != 0) {
uint16_t t = x10 + b10;
if (t <= v) {
x10 = t;
x |= b;
}
b10 >>= 1;
b >>= 1;
}
// x = v / 10
Изменить:, чтобы получить алгоритм Kuba Ober, который позволяет избежать необходимости переменной x10
, мы можем вычесть b10
из v
и v10
. В этом случае x10
больше не требуется. Алгоритм становится
uin16_t x = 0, b = 0x1000, b10 = 0xA000;
while (b != 0) {
if (b10 <= v) {
v -= b10;
x |= b;
}
b10 >>= 1;
b >>= 1;
}
// x = v / 10
Цикл может быть размотан, а различные значения b
и b10
могут быть предварительно вычислены как константы.
Ответ 5
Деление скважины является вычитанием, так что да. Сдвиг вправо на 1 (разделите на 2). Теперь вычитаем 5 из результата, подсчитывая количество вычетов, пока значение будет меньше 5. Результатом будет количество вычитаемых вычетов. О, и деление, вероятно, будет быстрее.
Гибридная стратегия сдвига справа, а затем деление на 5 с использованием нормального деления может привести к повышению производительности, если логика в делителе уже не делает этого для вас.
Ответ 6
В архитектуре, которая может сдвигать только одно место за раз, серия явных сравнений против уменьшающихся полномочий двух, умноженных на 10, может работать лучше, чем решение, получающее удовольствие от хакера. Предполагая 16-битный дивиденд:
uint16_t div10(uint16_t dividend) {
uint16_t quotient = 0;
#define div10_step(n) \
do { if (dividend >= (n*10)) { quotient += n; dividend -= n*10; } } while (0)
div10_step(0x1000);
div10_step(0x0800);
div10_step(0x0400);
div10_step(0x0200);
div10_step(0x0100);
div10_step(0x0080);
div10_step(0x0040);
div10_step(0x0020);
div10_step(0x0010);
div10_step(0x0008);
div10_step(0x0004);
div10_step(0x0002);
div10_step(0x0001);
#undef div10_step
if (dividend >= 5) ++quotient; // round the result (optional)
return quotient;
}
Ответ 7
чтобы немного расширить ответ Алоиса, мы можем расширить предложенный y = (x * 205) >> 11
на несколько кратных/сдвигов:
y = (ms * 1) >> 3 // first error 8
y = (ms * 2) >> 4 // 8
y = (ms * 4) >> 5 // 8
y = (ms * 7) >> 6 // 19
y = (ms * 13) >> 7 // 69
y = (ms * 26) >> 8 // 69
y = (ms * 52) >> 9 // 69
y = (ms * 103) >> 10 // 179
y = (ms * 205) >> 11 // 1029
y = (ms * 410) >> 12 // 1029
y = (ms * 820) >> 13 // 1029
y = (ms * 1639) >> 14 // 2739
y = (ms * 3277) >> 15 // 16389
y = (ms * 6554) >> 16 // 16389
y = (ms * 13108) >> 17 // 16389
y = (ms * 26215) >> 18 // 43699
y = (ms * 52429) >> 19 // 262149
y = (ms * 104858) >> 20 // 262149
y = (ms * 209716) >> 21 // 262149
y = (ms * 419431) >> 22 // 699059
y = (ms * 838861) >> 23 // 4194309
y = (ms * 1677722) >> 24 // 4194309
y = (ms * 3355444) >> 25 // 4194309
y = (ms * 6710887) >> 26 // 11184819
y = (ms * 13421773) >> 27 // 67108869
каждая строка представляет собой отдельный независимый расчет, и вы увидите свою первую "ошибку"/неверный результат со значением, указанным в комментарии. как правило, лучше брать наименьшее смещение для данного значения ошибки, так как это сведет к минимуму дополнительные биты, необходимые для сохранения промежуточного значения в вычислениях, например (x * 13) >> 7
"лучше", чем (x * 52) >> 9
как для него требуется на два бита меньше, в то время как оба начинают давать неправильные ответы выше 68.
если вы хотите рассчитать больше из них, можно использовать следующий (Python) код:
def mul_from_shift(shift):
mid = 2**shift + 5.
return int(round(mid / 10.))
и я сделал очевидную вещь для вычисления, когда это приближение начинает идти не так с:
def first_err(mul, shift):
i = 1
while True:
y = (i * mul) >> shift
if y != i // 10:
return i
i += 1
(обратите внимание, что //
используется для "целочисленного" деления, т.е. оно усекает/округляет до нуля)
причина шаблона "3/1" в ошибках (то есть 8 повторений 3 раза, а затем 9), по-видимому, связана с изменением баз, то есть log2(10)
составляет ~ 3,32. если мы отобразим ошибки, мы получим следующее:
где относительная погрешность определяется как: mul_from_shift(shift)/(1<<shift) - 0.1