Yalnızca bit kaydırma ve ekleme kullanarak nasıl çarpabilir ve bölebilirim?
Yalnızca bit kaydırma ve ekleme kullanarak nasıl çarpabilir ve bölebilirim?
Yanıtlar:
Toplama ve kaydırma açısından çarpmak için, sayılardan birini ikinin üsleriyle ayrıştırmak istersiniz, örneğin:
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. taban anlamına gelir)
Gördüğünüz gibi, çarpma, toplama, değiştirme ve tekrar geri alma olarak ayrıştırılabilir. Çarpma işleminin bit kaydırmalardan veya toplamadan daha uzun sürmesinin nedeni de budur - bit sayısında O (n) yerine O (n ^ 2) 'dir. Gerçek bilgisayar sistemleri (teorik bilgisayar sistemlerinin tersine) sınırlı sayıda bit içerir, bu nedenle çarpma, toplama ve kaydırmaya kıyasla sabit bir zaman katları alır. Doğru hatırlıyorsam, modern işlemciler, doğru bir şekilde boru hattına bağlanırsa, işlemcideki ALU'ların (aritmetik birimler) kullanımıyla uğraşarak çarpma işlemini toplama kadar hızlı yapabilirler.
Andrew Toulouse'un cevabı bölünmeye kadar genişletilebilir.
Tamsayı sabitlerine göre bölme, Henry S. Warren'ın "Hacker's Delight" kitabında (ISBN 9780201914658) ayrıntılı olarak ele alınmıştır.
Bölme uygulamak için ilk fikir, paydanın ters değerini ikinci tabana yazmaktır.
Örneğin,
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....
Yani,
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
32-bit aritmetik için.
Şartları açık bir şekilde birleştirerek işlemlerin sayısını azaltabiliriz:
b = (a >> 2) + (a >> 4)
b += (b >> 4)
b += (b >> 8)
b += (b >> 16)
Bölünmeyi ve artıkları hesaplamanın daha heyecan verici yolları var.
DÜZENLEME1:
OP, rastgele sayıların çarpılması ve bölünmesi anlamına geliyorsa, sabit bir sayı ile bölme anlamına gelmiyorsa, bu iş parçacığı yararlı olabilir: https://stackoverflow.com/a/12699549/1182653
DÜZENLEME2:
Tamsayı sabitlerine bölmenin en hızlı yollarından biri, modüler aritmetikten ve Montgomery indirgemesinden yararlanmaktır: Bir tamsayıyı 3'e bölmenin en hızlı yolu nedir?
b += r * 11 >> 5
ile r = a - q * 3
. Bağlantı: hackersdelight.org/divcMore.pdf page 2+.
X * 2 = 1 bit sola
kaydır X / 2 = 1 bit sağa kaydır
X * 3 = sola kaydır 1 bit ve sonra X ekle
add X
Sonuncusu için mi demek istiyorsun ?
x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k
Herhangi bir çarpma işlemi yapmak için bu kaydırmaları kullanabilirsiniz. Örneğin:
x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)
Bir sayıyı ikiye bölmek için, bazı düşük seviyeli mantık uygulamak, diğer ikili işlemleri kullanmak ve bir tür yineleme biçimi kullanmak istemiyorsanız, kolay bir yolun farkında değilim.
Python kodunu C'ye çevirdim. Verilen örnekte küçük bir kusur vardı. 32 bitin tamamını kaplayan temettü değeri, kayma başarısız olur. Sorunu çözmek için dahili olarak 64 bit değişkenler kullandım:
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
önce neden yaptığını bana söyleyebilir misin while
? Ayrıca, nPos >= 0
hile yapmayacak mısın?
Kaydırma ve toplamaları kullanan tam sayıları bölme prosedürü, ilkokulda öğretildiği gibi ondalık uzun el bölmesinden doğrudan bir şekilde türetilebilir. Her bölüm basamağının seçimi, basamak 0 ve 1 olduğu için basitleştirilmiştir: mevcut kalan bölüm bölenden büyük veya eşitse, kısmi bölümün en az anlamlı biti 1'dir.
Ondalık uzun el bölmesinde olduğu gibi, temettü rakamları her seferinde bir rakam olmak üzere en önemliden en az anlamlıya doğru olarak kabul edilir. Bu, ikili bölmede sola kayma ile kolayca başarılır. Ayrıca, bölüm bitleri, geçerli bölüm bitlerinin bir konum sola kaydırılması ve ardından yeni bölüm bitinin eklenmesiyle toplanır.
Klasik bir düzenlemede, bu iki sola kaydırma, bir kayıt çiftinin sola kaydırılmasıyla birleştirilir. Üst yarı mevcut kalanı tutar, alt yarı başlangıç temettü tutarını tutar. Temettü bitleri, kalan yazmacıya sola kaydırma ile aktarılırken, alt yarının kullanılmayan en önemsiz bitleri bölüm bitlerini biriktirmek için kullanılır.
Aşağıda bu algoritmanın x86 birleştirme dili ve C uygulamaları yer almaktadır. Bir kaydırma ve toplama bölümünün bu belirli varyantına bazen "performans göstermeyen" varyant denir, çünkü bölenin mevcut kalandan çıkarılması, kalan kısım bölenden büyük veya ona eşit olmadığı sürece gerçekleştirilmez. C'de, yazmaç çifti sola kaydırmasında montaj versiyonu tarafından kullanılan taşıma bayrağı kavramı yoktur. Bunun yerine, bir ilave etme sonucu 2 modülo gözlemine göre, taklit edilir , n bir taşıma ya da dışarı Katılan olmadığı taktirde daha küçük olabilir.
#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
İki sayı alın, 9 ve 10 diyelim, bunları ikili olarak yazın - 1001 ve 1010.
0 sonuç R ile başlayın.
Numaralardan birini alın, bu durumda 1010, ona A diyeceğiz ve bir bit sağa kaydıracağız, bir tane çıkarırsanız, ilk sayıyı ekleyeceğiz, ona B diyeceğiz, R'ye.
Şimdi B'yi bir bit sola kaydırın ve tüm bitler A'dan çıkana kadar tekrarlayın.
Yazılı olduğunu görürseniz, neler olduğunu görmek daha kolay, bu örnek:
0
0000 0
10010 1
000000 0
1001000 1
------
1011010
Alındığı burada .
Bu sadece bölme içindir:
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;
}
temelde temel güç 2 ile çarpma ve bölme işlemidir
sola kaydırma = x * 2 ^ y
sağa kaydırma = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
kısa, 3 = 2/2 ^ 3 = 1/4
eax
gibi kesirli bir değeri tutamaz 1/4
. (Tamsayı yerine sabit nokta kullanmıyorsanız, ancak bunu belirtmediyseniz)
Bu, çarpma için çalışmalıdır:
.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
Aşağıdaki yöntem, her iki sayının da pozitif olduğu düşünülerek ikili bölme uygulamasıdır. Çıkarma bir sorunsa, bunu ikili operatörleri kullanarak da uygulayabiliriz.
-(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);
}
Çarpma işlemi için:
-(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 bitlik bir x86 çözümüyle ilgilenen herkes için, burada JasonKnight tarafından hazırlanmış bir kod parçası var 1 (ayrıca test etmediğim imzalı bir çarpma parçası da içeriyor). Ancak, bu kodun "bx, bx ekle" kısmının taşacağı büyük girdilerle ilgili sorunları vardır.
Sabit versiyon:
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
Veya GCC satır içi montajda aynı:
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"
);
Bunu dene. 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])