Как я могу умножать и делить, используя только сдвиг и добавление битов?
Как я могу умножать и делить, используя только сдвига и добавления?
13 ответов:
чтобы умножить в терминах сложения и сдвига, вы хотите разложить одно из чисел по степеням двух, например:
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) в количестве битов. Реальные компьютерные системы (в отличие от теоретических компьютерных систем) имеют конечное число биты, поэтому умножение занимает постоянное кратное времени по сравнению с сложением и сдвигом. Если я правильно помню, современные процессоры, если они правильно конвейеризованы, могут делать умножение примерно так же быстро, как и сложение, возясь с использованием ALUs (арифметических единиц) в процессоре.
ответ Эндрю Тулуз может быть расширен до разделения.
деление на целочисленные константы подробно рассмотрено в книге Генри С. Уоррена "Hacker's Delight" (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:
если ОП означает умножение и деление произвольных чисел, а не деление на постоянное число, то этот поток может быть: https://stackoverflow.com/a/12699549/1182653
EDIT2:
один из самых быстрых способов деления на целочисленные константы-использовать модульную арифметику и сокращение Монтгомери: какой самый быстрый способ разделить целое число на 3?
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)
чтобы разделить число на немощность двух, я не знаю никакого простого способа, Если вы не хотите реализовать некоторую логику низкого уровня, использовать другие двоичные операции и использовать некоторую форму итерации.
- сдвиг влево на 1 позицию аналогичен умножению на 2. Сдвиг вправо аналогичен делению на 2.
- вы можете добавить в цикле умножать. Правильно выбрав переменную цикла и переменную сложения, можно привязать производительность. После того, как вы изучили это, вы должны использовать Мужик Умножения
Я перевел код 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; }
Возьмите два числа, скажем 9 и 10, запишите их как двоичные-1001 и 1010.
начните с результата, R, равным 0.
возьмите одно из чисел, 1010 в этом случае мы назовем его A, и сдвиньте его вправо на один бит, Если вы сдвинете один, добавьте первое число, мы назовем его B, к R.
теперь сдвиньте B влево на один бит и повторяйте, пока все биты не будут сдвинуты из A.
это легче увидеть, что происходит, если вы видите, что это написано, это пример:
0 0000 0 10010 1 000000 0 1001000 1 ------ 1011010
процедура деления целых чисел, которая использует сдвиги и добавляет Может быть получена простым способом из десятичного длинного деления, как учили в начальной школе. Выбор каждой цифры частного упрощается, так как цифра равна либо 0, либо 1: если текущий остаток больше или равен делителю, то наименее значимый бит частичного частного равен 1.
Так же, как и при десятичном длинном делении, цифры дивиденда считаются от большинства значимого до наименее значимого, по одной цифре за раз. Это легко достигается сдвигом влево в двоичном делении. Кроме того, биты частного собираются путем сдвига влево текущих бит частного на одну позицию, а затем добавления нового бита частного.
в классическом расположении эти два левых сдвига объединяются в левый сдвиг одной пары регистров. Верхняя половина держит текущий остаток, нижняя половина начальная держит дивиденд. По мере того как биты дивиденда перенесены к оставшийся регистр сдвигом влево, неиспользуемые наименее значимые биты нижней половины используются для накопления частных битов.
Ниже приведен язык ассемблера x86 и реализации C этого алгоритма. Этот конкретный вариант деления shift & add иногда называют вариантом "без выполнения", поскольку вычитание делителя из текущего остатка не выполняется, если остаток не больше или не равен делителю. В C нет понятия флаг переноса, используемый версией сборки в паре регистров сдвиг влево. Вместо этого он эмулируется, основываясь на наблюдении, что результат сложения по модулю 2n может быть меньше, что либо добавить, только если было выполнить.
#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
принято от здесь.
Это только для разделения:
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; }
Это должно работать для умножения:
.data .text .globl main main: # * = addi , , 0x9 addi , , 0x6 add , , # initialize product to zero Loop: beq , , Exit # if multiplier is 0,terminate loop andi , , 1 # mask out the 0th bit in multiplier beq , , Shift # if the bit is 0, skip add addu , , # add (shifted) multiplicand to product Shift: sll , , 1 # shift up the multiplicand 1 bit srl , , 1 # shift down the multiplier 1 bit j Loop # go for next Exit: # EXIT: li $v0,10 syscall
ниже метод является реализацией двоичного деления, учитывая, что оба числа являются положительными. Если вычитание является проблемой, мы можем реализовать это также с помощью двоичных операторов.
код
-(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; }
для всех, кто заинтересован в 16-битном решении x86, есть кусок кода от JasonKnight здесь1 (он также включает в себя подписанный умножить кусок, который я не проверял). Однако этот код имеет проблемы с большими входными данными,где часть "добавить bx, bx" будет переполнена.
исправленную версию:
softwareMultiply: ; INPUT CX,BX ; OUTPUT DX:AX - 32 bits ; CLOBBERS BX,CX,DI xor ax,ax ; cheap way to zero a reg mov dx,ax ; 1 clock faster than xor mov di,cx or di,bx ; cheap way to test for zero on both regs jz @done mov di,ax ; DI used for reg,reg adc @loop: shr cx,1 ; divide by two, bottom bit moved to carry flag jnc @skipAddToResult add ax,bx adc dx,di ; reg,reg is faster than reg,imm16 @skipAddToResult: add bx,bx ; faster than shift or mul adc di,di or cx,cx ; fast zero check jnz @loop @done: ret
или то же самое в сборке GCC inline:
asm("mov ,%%ax\n\t" "mov ,%%dx\n\t" "mov %%cx,%%di\n\t" "or %%bx,%%di\n\t" "jz done\n\t" "mov %%ax,%%di\n\t" "loop:\n\t" "shr ,%%cx\n\t" "jnc skipAddToResult\n\t" "add %%bx,%%ax\n\t" "adc %%di,%%dx\n\t" "skipAddToResult:\n\t" "add %%bx,%%bx\n\t" "adc %%di,%%di\n\t" "or %%cx,%%cx\n\t" "jnz loop\n\t" "done:\n\t" : "=d" (dx), "=a" (ax) : "b" (bx), "c" (cx) : "ecx", "edi" );
попробуйте это. https://gist.github.com/swguru/5219592
import sys # implement divide operation without using built-in divide operator def divAndMod_slow(y,x, debug=0): r = 0 while y >= x: r += 1 y -= x return r,y # implement divide operation without using built-in divide operator def divAndMod(y,x, debug=0): ## find the highest position of positive bit of the ratio pos = -1 while y >= x: pos += 1 x <<= 1 x >>= 1 if debug: print "y=%d, x=%d, pos=%d" % (y,x,pos) if pos == -1: return 0, y r = 0 while pos >= 0: if y >= x: r += (1 << pos) y -= x if debug: print "y=%d, x=%d, r=%d, pos=%d" % (y,x,r,pos) x >>= 1 pos -= 1 return r, y if __name__ =="__main__": if len(sys.argv) == 3: y = int(sys.argv[1]) x = int(sys.argv[2]) else: y = 313271356 x = 7 print "=== Slow Version ...." res = divAndMod_slow( y, x) print "%d = %d * %d + %d" % (y, x, res[0], res[1]) print "=== Fast Version ...." res = divAndMod( y, x, debug=1) print "%d = %d * %d + %d" % (y, x, res[0], res[1])