Як я можу множити і ділити, використовуючи лише зміщення та додавання бітів?
Як я можу множити і ділити, використовуючи лише зміщення та додавання бітів?
Відповіді:
Щоб помножити з точки зору додавання та зсуву, ви хочете розкласти одне з чисел на степені двох, наприклад:
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 (арифметичних одиниць) у процесорі.
Відповідь Ендрю Тулузи можна поширити на поділ.
Поділ на цілі константи детально розглянуто в книзі "Захоплення Хакера" Генрі С. Уоррена (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?
b += r * 11 >> 5
с r = a - q * 3
. Посилання: hackersdelight.org/divcMore.pdf сторінка 2+.
X * 2 = 1 біт зсув вліво
X / 2 = 1 біт зсув вправо
X * 3 = зсув вліво 1 біт, а потім додайте X
add X
той останній?
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)
Щоб розділити число на нерівневе з двох, я не знаю жодного простого способу, якщо ви не хочете реалізувати якусь логіку низького рівня, використовувати інші двійкові операції та використовувати якусь форму ітерації.
Я переклав код 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;
}
ullDivisor >>= 1
до while
циклу? Крім того, не nPos >= 0
зробить фокус?
Процедура ділення цілих чисел, яка використовує зсуви та додавання, може бути отримана прямо з десяткового довгострокового ділення, як її викладають у початковій школі. Вибір кожної цифрової частки спрощується, оскільки цифра дорівнює 0 або 1: якщо поточний залишок більший або дорівнює дільнику, найменш значущим бітом часткового часткового значення є 1.
Подібно до десяткового довготермінового поділу, цифри дивіденду вважаються від найбільш значущих до найменш значущих, по одній цифрі. Це легко досягається зсувом лівого бінарного ділення. Крім того, часткові біти збираються, зміщуючи поточні часткові біти вліво на одну позицію, потім додаючи новий частковий біт.
У класичному розташуванні ці два ліві зсуви поєднуються в ліві зсуви однієї пари регістрів. У верхній половині міститься поточний залишок, у нижній половині - початковий дивіденд. Оскільки біти дивідендів переносяться в регістр залишків за допомогою лівого зсуву, невикористані найменш значущі біти нижньої половини використовуються для накопичення часткових бітів.
Нижче наведено асемблерну мову x86 та реалізації цього алгоритму C. Цей конкретний варіант поділу зсуву та додавання іноді називають варіантом "без виконання", оскільки віднімання дільника від поточного залишку не виконується, якщо залишок не більший або дорівнює дільнику. У 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
Візьміть два числа, скажімо, 9 і 10, запишіть їх як двійкові - 1001 і 1010.
Почніть з результату, R, 0.
Візьміть одне з чисел, у цьому випадку 1010, ми будемо називати його A, і зміщуємо вправо на один біт, якщо ви зміщуєте одиницю, додайте перше число, ми будемо називати B, до R.
Тепер змістіть B ліворуч на один біт і повторюйте, поки всі біти не будуть зміщені з A.
Це легше зрозуміти, що відбувається, якщо ви бачите, як це виписано, ось приклад:
0
0000 0
10010 1
000000 0
1001000 1
------
1011010
Взято звідси .
Це лише для поділу:
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;
}
це в основному множення і ділення з базовою потужністю 2
зміщення вліво = x * 2 ^ y
зміщення вправо = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
shr eax, 3 = 2/2 ^ 3 = 1/4
eax
не може утримувати дробове значення типу 1/4
. (Якщо ви не використовуєте фіксовану точку замість цілого, але ви цього не вказали)
Це має працювати для множення:
.data
.text
.globl main
main:
# $4 * $5 = $2
addi $4, $0, 0x9
addi $5, $0, 0x6
add $2, $0, $0 # initialize product to zero
Loop:
beq $5, $0, Exit # if multiplier is 0,terminate loop
andi $3, $5, 1 # mask out the 0th bit in multiplier
beq $3, $0, Shift # if the bit is 0, skip add
addu $2, $2, $4 # add (shifted) multiplicand to product
Shift:
sll $4, $4, 1 # shift up the multiplicand 1 bit
srl $5, $5, 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;
}
-(int)multiplyNumber:(int)num1 withNumber:(int)num2
?
Для всіх, хто цікавиться 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:
asm("mov $0,%%ax\n\t"
"mov $0,%%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 $1,%%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])