ビットシフトと加算のみを使用して乗算と除算を行うにはどうすればよいですか?


86

ビットシフトと加算のみを使用して乗算と除算を行うにはどうすればよいですか?


20
中学校の紙で行うように、10進数ではなく2進数のみを使用します。
パスカルキュオック2010

1
@mtk:この回答には何が欠けていますか?Cまたはアセンブリの実装、特定のオペランド幅、特定の除算方法(復元と非復元など)をお探しですか?
njuffa 2015

引き算は大丈夫ですか?すべてがカバーされているようです
mksteve 2015

この質問の背後にある必要性は何ですか?CPUは、乗算と除算の演算をビットシフトと加算または減算に変換します。その場合は、コンパイラがまだ変換していません。
ケリーS.フランス語

@ KellyS.French好奇心だけで、制限された命令セットでコンパイラがどのように機能するかを想像する方法です。
Spidfire 2016年

回答:


77

加算とシフトの観点から乗算するには、次のように、数値の1つを2の累乗で分解します。

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)ではなくO(n ^ 2)です。実際のコンピューターシステム(理論上のコンピューターシステムとは対照的に)のビット数は有限であるため、乗算は加算やシフトに比べて一定の倍数の時間がかかります。私が正しく思い出せば、最新のプロセッサは、適切にパイプライン化されていれば、プロセッサ内のALU(算術ユニット)の利用をいじることによって、加算とほぼ同じ速さで乗算を行うことができます。


4
少し前のことですが、除算の例を挙げていただけますか?ありがとう
GniruT 2015年

42

アンドリュートゥールーズによる答えは、除算に拡張することができます。

整数定数による除算については、ヘンリーS.ウォーレンの著書「ハッカーのたのしみ」(ISBN 9780201914658)で詳しく説明されています。

分数を実装するための最初のアイデアは、基数2に分母の逆の値を書き込むことです。

例えば、 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)

除算と剰余を計算するためのよりエキサイティングな方法があります。

編集1:

OPが定数による除算ではなく、任意の数の乗算と除算を意味する場合、このスレッドが役立つ可能性があります:https//stackoverflow.com/a/12699549/1182653

EDIT2:

整数定数で除算する最も速い方法の1つは、モジュラー算術とモンゴメリリダクションを利用することです。整数を3で除算する最も速い方法は何ですか?


ハッカーのたのしみのリファレンスをありがとう!
alecxe 2015

2
ええと、この答え(定数による除算)は部分的にしか正しくありません。「3/3」を実行しようとすると、0になります。ハッカーのたのしみでは、実際には、補正する必要のあるエラーがあると説明されています。この場合:b += r * 11 >> 5with r = a - q * 3。リンク:hackersdelight.org/divcMore.pdfページ2+。
atlaste 2016

30

X * 2 = 1ビット左シフト
X / 2 = 1ビット右シフト
X * 3 =左シフト1ビットしてからXを追加


4
add X最後の意味ですか?
マーク・バイヤーズ

1
それはまだ間違っています-最後の行は次のようになります: "X * 3 =左に1ビットシフトしてからXを追加する"
Paul R

1
「X / 2 = 1ビット右シフト」は、完全ではありませんが、除算の通常の実装である0(負の数の場合)ではなく、無限大に切り捨てられます(少なくとも私が見た限りでは)。
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

これらのシフトを使用して、任意の乗算演算を実行できます。例えば:

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

数値を2の累乗以外で除算するために、低レベルのロジックを実装し、他の2項演算を使用し、何らかの形式の反復を使用する場合を除いて、簡単な方法はわかりません。


@IVlad:上記の操作をどのように組み合わせて、たとえば3で割って実行しますか?
ポールR

@Paul R-本当、それは難しい。私は自分の答えを明確にしました。
IVlad 2010

定数による除算はそれほど難しくありませんが(魔法の定数を掛けてから2の累乗で割る)、変数による除算は少し注意が必要です。
ポールR

1
x * 14 == x * 16-x * 2 ==(x << 4)-(x << 2)は、x <なので、実際には(x << 4)-(x << 1)になります。 <1はxに2を掛けていますか?
アレックススペンサー

18
  1. 1桁左シフトは、2を掛けるのに似ています。右シフトは、2で割るのに似ています。
  2. ループに追加して乗算することができます。ループ変数と加算変数を正しく選択することで、パフォーマンスを制限できます。それを調べたら、PeasantMultiplicationを使用する必要があります

9
1:しかし、左シフトは、それは2倍するにちょうど似ていない...少なくともオーバーフローするまで2倍する
ドン・ロビー

Shift-divisionは、負の数に対して誤った結果をもたらします。
デビッド

6

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;
}

負の数はどうですか?Eclipse + CDTを使用して-12345を10でテストしましたが、結果はそれほど良くありませんでした。
kenmux 2016年

ループのullDivisor >>= 1前に行う理由を教えてくださいwhile。また、nPos >= 0トリックをしませんか?
Vivekanand V

@kenmux関係する数値の大きさのみを考慮する必要があります。最初にアルゴリズムを実行し、次に適切な意思決定ステートメントを使用して、商/剰余に適切な符号を返します。
Vivekanand V

1
@VivekanandVつまり、記号を追加します-後で?はい、動作します。
kenmux

5

シフトと加算を使用して整数を除算する手順は、小学校で教えられているように、10進数のロングハンド除算から簡単に導き出すことができます。桁が0と1のいずれかであるため、各商の桁の選択が簡略化されます。現在の剰余が除数以上の場合、部分商の最下位ビットは1です。

10進数のロングハンド除算と同様に、配当の桁は、一度に1桁ずつ、最上位から最下位まで考慮されます。これは、2進除算の左シフトによって簡単に実現できます。また、商ビットは、現在の商ビットを1桁左にシフトしてから、新しい商ビットを追加することによって収集されます。

古典的な配置では、これらの2つの左シフトは、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

@greybeardポインタをありがとう、あなたは正しいです、私は配当と商を混ぜました。直します。
njuffa 2015

4

2つの数字、たとえば9と10を取り、それらを2進数(1001と1010)として記述します。

結果Rを0から始めます。

番号の1つ(この場合は1010)を取得し、それをAと呼び、1ビット右にシフトします。1をシフトアウトする場合は、最初の番号を追加します。これをBと呼びます。

ここで、Bを1ビット左にシフトし、すべてのビットがAからシフトアウトされるまで繰り返します。

書き出されているのを見ると、何が起こっているのかがわかりやすくなります。これが例です。

      0
   0000      0
  10010      1
 000000      0
1001000      1
 ------
1011010

これは最速のようです。最小数のビットをループして結果を計算するには、少し余分なコーディングが必要です。
Hellonearthis 2012年

2

ここから撮影。

これは除算専用です:

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

それは基本的に基本パワー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。(整数の代わりに固定小数点を使用しているが、それを指定しなかった場合を
除く

1

これは乗算で機能するはずです:

.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

組み立てのフレーバーは何ですか?
キースピンソン2013

1
これがあなたが求めているものであるならば、それはMIPSアセンブリです。私はそれを書く/実行するためにMARSを使用したと思います。
Melsi 2013

1

以下の方法は、両方の数値が正であることを考慮した2進除算の実装です。減算が問題になる場合は、二項演算子を使用してそれを実装することもできます。

コード

-(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
SSアン

0

16ビットx86ソリューションに興味のある人のために、ここにJasonKnightによるコードがあります1(彼には、私がテストしていない署名された乗算ピースも含まれています)。ただし、そのコードには、「add 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"
);

-1

これを試して。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])

5
これはPythonのように見えます。質問はアセンブリおよび/またはC.のために頼まれた
のボイド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.