Разделить на 10 с помощью битовых сдвигов?

можно ли разделить целое число без знака на 10, используя чистые битовые сдвиги, сложение, вычитание и может быть размножаться? Использование процессора с очень ограниченными ресурсами и медленным разделением.

6 ответов


вот что делает компилятор 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

даже на машине с медленной инструкцией умножения это будет быстрее, чем разделение программного обеспечения.


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

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.


конечно, вы можете, если вы можете жить с некоторой потерей точности. Если вы знаете диапазон значений входных значений, вы можете придумать битовый сдвиг и точное умножение. Некоторые примеры, как можно разделить на 10, 60, ... как это описано в этом блоге в формате время самый быстрый способ возможно.

temp = (ms * 205) >> 11;  // 205/2048 is nearly the same as /10

твой, Алоиз Краус!--4-->


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

допустим, мы должны найти x здесь x = v / 10.

мы будем использовать обратную операцию v = x * 10 потому что у него есть хорошее свойство, когда x = a + b, потом x * 10 = a * 10 + b * 10.

давайте использовать x как переменная, держащая лучшее приближение результата до сих пор. Когда поиск кончается,x будет держать результат. Мы установим каждый бит b of x от самого значительного к менее значительному, один за другим, end compare (x + b) * 10 С v. Если его меньше или равно v, тогда бит b находится в x. Чтобы проверить следующий бит, мы просто сдвигаем b на одну позицию вправо (делим на два).

мы можем избежать умножения на 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

Edit: чтобы получить алгоритм Kuba Ober, который позволяет избежать необходимости переменной x10 , мы можем вычесть b10 С v и . В этом случае 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 могут быть предварительно вычислены как константы.


ну деление вычитание, так что да. Сдвинуть вправо на 1 (разделить на 2). Теперь вычитайте 5 из результата, подсчитывая количество раз, когда вы делаете вычитание, пока значение не будет меньше 5. Результат-количество вычитаний, которые вы сделали. О, и разделение, вероятно, будет быстрее.

гибридная стратегия сдвига вправо, а затем деление на 5 с использованием обычного деления может улучшить производительность, если логика в делителе еще не делает этого для вас.


на архитектурах, которые могут сдвигать только одно место за раз, серия явных сравнений с уменьшающимися степенями двух, умноженными на 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;
}