Как я могу умножить и разделить, используя только сдвиг и добавление битов? - PullRequest
77 голосов
/ 05 мая 2010

Как я могу умножить и разделить, используя только сдвиг и добавление битов?

Ответы [ 13 ]

68 голосов
/ 06 мая 2010

Чтобы умножить с точки зрения сложения и смещения, вы хотите разложить одно из чисел на степени двух, например:

21 * 5 = 10101_2 * 101_2             (Initial step)
       = 10101_2 * (1 * 2^2  +  0 * 2^1  +  1 * 2^0)
       = 10101_2 * 2^2 + 10101_2 * 2^0 
       = 10101_2 << 2 + 10101_2 << 0 (Decomposed)
       = 10101_2 * 4 + 10101_2 * 1
       = 10101_2 * 5
       = 21 * 5                      (Same as initial expression)

(_2 означает основание 2)

Как видите, умножение можно разложить на сложение, сдвиг и обратно. По этой же причине умножение занимает больше времени, чем сдвиги битов или сложение - это число O (n ^ 2), а не O (n) по количеству бит. Реальные компьютерные системы (в отличие от теоретических компьютерных систем) имеют конечное число битов, поэтому умножение занимает постоянное кратное время по сравнению с сложением и сдвигом. Если я правильно помню, современные процессоры, при правильной конвейеризации, могут умножать примерно так же быстро, как и сложение, путаясь с использованием ALU (арифметических единиц) в процессоре.

41 голосов
/ 26 апреля 2012

Ответ Эндрю Тулузе может быть расширен до деления.

Деление на целочисленные константы подробно рассмотрено в книге Генри Уоррена "Восхищение хакера" (ISBN 9780201914658).

Первая идея для реализации деления состоит в том, чтобы записать обратное значение знаменателя в базе два.

например., 1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....

Итак, a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30) для 32-битной арифметики.

Комбинируя термины очевидным образом, мы можем сократить количество операций:

b = (a >> 2) + (a >> 4)

b += (b >> 4)

b += (b >> 8)

b += (b >> 16)

Существуют более интересные способы вычисления деления и остатков.

EDIT1:

Если OP означает умножение и деление произвольных чисел, а не деление на постоянное число, тогда этот поток может быть полезен: https://stackoverflow.com/a/12699549/1182653

EDIT2:

Один из самых быстрых способов деления на целочисленные константы - использовать модульную арифметику и сокращение Монтгомери: Какой самый быстрый способ делить целое число на 3?

24 голосов
/ 05 мая 2010

X * 2 = 1 бит сдвиг влево
X / 2 = 1 бит сдвиг вправо
X * 3 = сдвинуть влево на 1 бит, а затем добавить X

21 голосов
/ 05 мая 2010

x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k

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

x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)

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

17 голосов
/ 05 мая 2010
  1. Сдвиг влево на 1 позицию аналогичен умножению на 2. Правое смещение аналогично делению на 2.
  2. Вы можете добавить в цикл для умножения. Правильно выбрав переменную цикла и переменную сложения, вы можете связать производительность. После того, как вы это изучите, вы должны использовать Крестьянское умножение
6 голосов
/ 05 ноября 2013

Я перевел код Python на C. В приведенном примере был небольшой недостаток. Если значение дивиденда, которое заняло все 32 бита, сдвиг завершится неудачей. Я просто использовал 64-битные переменные для решения проблемы:

int No_divide(int nDivisor, int nDividend, int *nRemainder)
{
    int nQuotient = 0;
    int nPos = -1;
    unsigned long long ullDivisor = nDivisor;
    unsigned long long ullDividend = nDividend;

    while (ullDivisor <  ullDividend)
    {
        ullDivisor <<= 1;
        nPos ++;
    }

    ullDivisor >>= 1;

    while (nPos > -1)
    {
        if (ullDividend >= ullDivisor)
        {
            nQuotient += (1 << nPos);
            ullDividend -= ullDivisor;
        }

        ullDivisor >>= 1;
        nPos -= 1;
    }

    *nRemainder = (int) ullDividend;

    return nQuotient;
}
4 голосов
/ 07 сентября 2015

Процедура деления целых чисел, в которой используются сдвиги и сложения, может быть получена прямым способом из десятичного деления на длинные, как преподаются в начальной школе. Выбор каждой факторной цифры упрощается, так как эта цифра равна 0 и 1: если текущий остаток больше или равен делителю, младший значащий бит частичного частного равен 1.

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

В классическом расположении эти два сдвига влево объединяются в сдвиг влево одной пары регистров. Верхняя половина содержит текущий остаток, нижняя половина - дивиденд. Поскольку дивидендные биты передаются в регистр остатка с помощью сдвига влево, неиспользуемые младшие значащие биты младшей половины используются для накопления частных битов.

Ниже представлен язык ассемблера x86 и реализации C этого алгоритма. Этот конкретный вариант деления сдвига и сложения иногда называют вариантом «бездействия», поскольку вычитание делителя из текущего остатка не выполняется, если остаток не больше или равен делителю. В Си нет понятия флага переноса, используемого версией сборки в сдвиге пары регистров слева. Вместо этого он эмулируется, основываясь на наблюдении, что результат сложения по модулю 2 n может быть меньше, что либо добавляется, только если был результат.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define USE_ASM 0

#if USE_ASM
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
    uint32_t quot;
    __asm {
        mov  eax, [dividend];// quot = dividend
        mov  ecx, [divisor]; // divisor
        mov  edx, 32;        // bits_left
        mov  ebx, 0;         // rem
    $div_loop:
        add  eax, eax;       // (rem:quot) << 1
        adc  ebx, ebx;       //  ...
        cmp  ebx, ecx;       // rem >= divisor ?
        jb  $quot_bit_is_0;  // if (rem < divisor)
    $quot_bit_is_1:          // 
        sub  ebx, ecx;       // rem = rem - divisor
        add  eax, 1;         // quot++
    $quot_bit_is_0:
        dec  edx;            // bits_left--
        jnz  $div_loop;      // while (bits_left)
        mov  [quot], eax;    // quot
    }            
    return quot;
}
#else
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
    uint32_t quot, rem, t;
    int bits_left = CHAR_BIT * sizeof (uint32_t);

    quot = dividend;
    rem = 0;
    do {
            // (rem:quot) << 1
            t = quot;
            quot = quot + quot;
            rem = rem + rem + (quot < t);

            if (rem >= divisor) {
                rem = rem - divisor;
                quot = quot + 1;
            }
            bits_left--;
    } while (bits_left);
    return quot;
}
#endif
4 голосов
/ 06 мая 2010

Возьмите два числа, скажем, 9 и 10, запишите их в двоичном виде - 1001 и 1010.

Начните с результата, R, равного 0.

Возьмите одно из чисел, в данном случае 1010, мы назовем его A, и сдвинем его вправо на один бит, если вы сдвинете единицу, добавите первое число, мы назовем его B к R .

Теперь сдвиньте B влево на один бит и повторяйте, пока все биты не будут сдвинуты из A.

Проще увидеть, что происходит, если вы видите, что это написано, вот пример:

      0
   0000      0
  10010      1
 000000      0
1001000      1
 ------
1011010
2 голосов
/ 28 февраля 2016

Взято из здесь .

Это только для деления:

int add(int a, int b) {
        int partialSum, carry;
        do {
            partialSum = a ^ b;
            carry = (a & b) << 1;
            a = partialSum;
            b = carry;
        } while (carry != 0);
        return partialSum;
}

int subtract(int a, int b) {
    return add(a, add(~b, 1));
}

int division(int dividend, int divisor) {
        boolean negative = false;
        if ((dividend & (1 << 31)) == (1 << 31)) { // Check for signed bit
            negative = !negative;
            dividend = add(~dividend, 1);  // Negation
        }
        if ((divisor & (1 << 31)) == (1 << 31)) {
            negative = !negative;
            divisor = add(~divisor, 1);  // Negation
        }
        int quotient = 0;
        long r;
        for (int i = 30; i >= 0; i = subtract(i, 1)) {
            r = (divisor << i);
           // Left shift divisor until it's smaller than dividend
            if (r < Integer.MAX_VALUE && r >= 0) { // Avoid cases where comparison between long and int doesn't make sense
                if (r <= dividend) { 
                    quotient |= (1 << i);    
                    dividend = subtract(dividend, (int) r);
                }
            }
        }
        if (negative) {
            quotient = add(~quotient, 1);
        }
        return quotient;
}
1 голос
/ 13 февраля 2014

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

код

-(int)binaryDivide:(int)numerator with:(int)denominator
{
    if (numerator == 0 || denominator == 1) {
        return numerator;
    }

    if (denominator == 0) {

        #ifdef DEBUG
            NSAssert(denominator==0, @"denominator should be greater then 0");
        #endif
        return INFINITY;
    }

    // if (numerator <0) {
    //     numerator = abs(numerator);
    // }

    int maxBitDenom = [self getMaxBit:denominator];
    int maxBitNumerator = [self getMaxBit:numerator];
    int msbNumber = [self getMSB:maxBitDenom ofNumber:numerator];

    int qoutient = 0;

    int subResult = 0;

    int remainingBits = maxBitNumerator-maxBitDenom;

    if (msbNumber >= denominator) {
        qoutient |=1;
        subResult = msbNumber - denominator;
    }
    else {
        subResult = msbNumber;
    }

    while (remainingBits > 0) {
        int msbBit = (numerator & (1 << (remainingBits-1)))>0?1:0;
        subResult = (subResult << 1) | msbBit;
        if(subResult >= denominator) {
            subResult = subResult - denominator;
            qoutient= (qoutient << 1) | 1;
        }
        else{
            qoutient = qoutient << 1;
        }
        remainingBits--;

    }
    return qoutient;
}

-(int)getMaxBit:(int)inputNumber
{
    int maxBit = 0;
    BOOL isMaxBitSet = NO;
    for (int i=0; i<sizeof(inputNumber)*8; i++) {
        if (inputNumber & (1<<i)) {
            maxBit = i;
            isMaxBitSet=YES;
        }
    }
    if (isMaxBitSet) {
        maxBit+=1;
    }
    return maxBit;
}


-(int)getMSB:(int)bits ofNumber:(int)number
{
    int numbeMaxBit = [self getMaxBit:number];
    return number >> (numbeMaxBit - bits);
}

Для умножения:

-(int)multiplyNumber:(int)num1 withNumber:(int)num2
{
    int mulResult = 0;
    int ithBit;

    BOOL isNegativeSign = (num1<0 && num2>0) || (num1>0 && num2<0);
    num1 = abs(num1);
    num2 = abs(num2);


    for (int i=0; i<sizeof(num2)*8; i++)
    {
        ithBit =  num2 & (1<<i);
        if (ithBit>0) {
            mulResult += (num1 << i);
        }

    }

    if (isNegativeSign) {
        mulResult =  ((~mulResult)+1);
    }

    return mulResult;
}
...