Yalnızca bit kaydırma ve ekleme kullanarak nasıl çarpabilir ve bölebilirim?


86

Yalnızca bit kaydırma ve ekleme kullanarak nasıl çarpabilir ve bölebilirim?


20
Tıpkı ortaokulda kağıt üzerinde yapacağınız gibi, ondalık yerine sadece ikili kullanarak.
Pascal Cuoq

1
@mtk: Bu cevapta eksik olan nedir ? C veya montaj uygulaması, belirli işlenen genişlikleri, belirli bir bölme yöntemi mi arıyorsunuz (örn. Geri yükleme veya geri yüklemesiz)?
njuffa

Çıkarma tamam mı? Her şey kapanmış görünüyor
mksteve

Bu sorunun arkasındaki ihtiyaç nedir? CPU, çarpma ve bölme işlemlerini bit kaydırma ve toplama veya çıkarma işlemlerine çevirir ve eğer bu, derleyici zaten yapmadıysa.
Kelly S. Fransızca

1
@ KellyS.French Sadece merak, daha çok bir derleyicinin kısıtlı bir komut setiyle nasıl çalışabileceğini hayal etmenin bir yolu.
Spidfire

Yanıtlar:


77

Toplama ve kaydırma açısından çarpmak için, sayılardan birini ikinin üsleriyle ayrıştırmak istersiniz, örneğin:

( _22. 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.


6
Bir süre önce olduğunu biliyorum ama bölünme ile bir örnek verebilir misiniz? Teşekkürler
GniruT

42

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?


Hacker's Delight referansı için çok teşekkürler!
alecxe

2
Ehm evet, bu cevap (sabite bölme) sadece kısmen doğrudur. '3/3' yapmaya çalışırsanız 0 ile sonuçlanırsınız. Hacker's Delight'ta aslında telafi etmeniz gereken bir hata olduğunu açıklarlar. Bu durumda: b += r * 11 >> 5ile r = a - q * 3. Bağlantı: hackersdelight.org/divcMore.pdf page 2+.
2016

31

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


4
add XSonuncusu için mi demek istiyorsun ?
Mark Byers

1
Hala yanlış - son satır şöyle olmalıdır: "X * 3 = 1 bit sola kaydır ve sonra X ekle"
Paul R

1
"X / 2 = 1 bit sağa kayar", tamamen değil, 0'a (negatif sayılar için) değil, sonsuza yuvarlar, bu bölmenin olağan uygulamasıdır (en azından gördüğüm kadarıyla).
Leif Andersen

25

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.


@IVlad: Diyelim ki 3'e bölmek için yukarıdaki işlemleri nasıl birleştirirsiniz?
Paul R

@Paul R - doğru, bu daha zor. Cevabımı açıkladım.
IVlad

bir sabite göre bölme çok zor değildir (sihirli sabiti ile çarpın ve sonra 2'nin gücüne bölün), ancak bir değişkenle bölme biraz daha yanıltıcıdır.
Paul R

1
x * 14 == x * 16 - x * 2 == (x << 4) - (x << 2) gerçekten (x << 4) - (x << 1) olarak bitmemelidir, çünkü x < <1, x ile 2 ile çarpılıyor mu?
Alex Spencer

18
  1. 1 sıra sola kaydırma, 2 ile çarpmaya benzer. Sağa kaydırma, 2'ye bölmeye benzer.
  2. Çarpmak için bir döngü ekleyebilirsiniz. Döngü değişkenini ve toplama değişkenini doğru bir şekilde seçerek performansı sınırlayabilirsiniz. Bunu keşfettikten sonra, Peasant Multiplication kullanmalısınız.

9
+1: Ama sola kayma sadece 2 ile çarpmaya benzemiyor. 2 ile çarpıyor En azından taşana kadar ...
Don Roby

Kaydırma bölümü, negatif sayılar için yanlış sonuçlar verir.
David

6

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:


Negatif sayı ne olacak? Eclipse + CDT kullanarak -12345'i 10 ile test ettim, ancak sonuç o kadar iyi değildi.
kenmux

Döngüden ullDivisor >>= 1önce neden yaptığını bana söyleyebilir misin while? Ayrıca, nPos >= 0hile yapmayacak mısın?
Vivekanand V

@kenmux Sadece ilgili sayıların büyüklüğünü göz önünde bulundurmalısınız, önce algoritmayı yapın ve sonra uygun karar verme ifadelerini kullanarak uygun işareti bölüm / kalan kısmına geri döndürün!
Vivekanand V

1
@VivekanandV İşareti daha sonra mı eklemek istiyorsunuz? Evet çalışıyor.
kenmux

5

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.


@greybeard Gösterge için teşekkürler, haklısın, temettü ile bölümü karıştırdım. Ben düzelteceğim.
njuffa

4

İ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:


Bu en hızlı görünüyor, sadece en küçük sayının bitleri arasında döngü yapmak ve sonucu hesaplamak için biraz fazladan kodlama gerektiriyor.
Hellonearthis

2

Alındığı burada .

Bu sadece bölme içindir:


2

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


eaxgibi kesirli bir değeri tutamaz 1/4. (Tamsayı yerine sabit nokta kullanmıyorsanız, ancak bunu belirtmediyseniz)
Peter Cordes

1

Bu, çarpma için çalışmalıdır:


1
MIPS derlemesidir, eğer sorduğunuz buysa. Sanırım MARS'yi yazmak / çalıştırmak için kullandım.
Melsi

1

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.

Kod

Çarpma işlemi için:


Bu sözdizimi nedir? -(int)multiplyNumber:(int)num1 withNumber:(int)num2?
SS Anne

0

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:

Veya GCC satır içi montajda aynı:


-1

Bunu dene. https://gist.github.com/swguru/5219592


5
Bu python'a benziyor. Soru montaj ve / veya C için soruldu
geçersiz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.