[パタヘネ][C]シフトと加算だけで、掛け算を行う

パタヘネの”第3章:コンピュータにおける算術演算”にある、3.3乗算の補足説明です。



“乗算は、ハードウェア的には、前述した加算処理とシフト処理で実装できる”を確認するために、Cでコードを作成しました。

当然ながら、乗算命令が下記のような形でソフトウェア的に実装されているというわけではないです。

下記のような振る舞いを行う”電子回路が組まれる”という意味だけど、
回路的として説明すると分かり辛いので、説明としてコードとして記述しただけです。

#include <stdio.h>
 
main() {
    int a = 11;
    int b = 12;
//  int c = multi( a, b );
    int c = multi_by_shift( a, b );
    printf( "c=%d\n", c );
}
 
//-------------------------------------------------
// 普通に掛け算を行う
//-------------------------------------------------
int multi( int a, int b ) {
    return a * b;
}
 
//-------------------------------------------------
// 加算とシフト命令だけで、掛け算を行う
//-------------------------------------------------
int multi_by_shift( int multiplicand, int multiplier ) {
 
    // 値の初期化
    int product      = 0; // 積
 
    //--------------------------------------------
    // multiplicandを全ビット処理するまで繰り返し
    //--------------------------------------------
    int loop = 0;
    for ( loop = 0; loop < 32; loop++ ) {
        // 最下位ビットを取り出す
        int t0 = multiplicand & 0x0001;
 
        // 最下位ビットが立っていたら、乗数を足しこむ
        int t1 = (t0 == 0) ? 0 : multiplier;
        product += t1;
 
        // for debug
        printf( "loop[%02d] 0x%02x 0x%08x -> %3d %3d\n", loop, t0, multiplier, t1, product );
 
        // 被乗数をシフト(次の処理対象ビットをずらす)
        multiplicand >>= 1;
 
        // 乗数をシフト(桁上げを行う)
        multiplier   <<= 1;
    }
 
    return product;
}




基本的な考えかた上記の通りだが、この形のままでCPU設計を行うと、掛け算にかかる処理時間が非常に長くなってしまう。
なので、通常はここからさらに最適化を行う。

方法の1つは、0~31bit分シフトされた値を一気に求めて、同時に2つづつ足しこみを行う。
そうするとビット数がnの場合の計算量が O(n)から O(log n)に減少する。
(一方でH/W的には、たくさんの加算器を用意する必要があるが、最近のトランジスタは十分に小さいので無視できるレベル)



また、下記はx86環境のアセンブリ(gccで生成したもの。本当はmipsのコードが欲しかったけど、環境がないのであきらめた。
multiは”imull”命令で乗算しているけど、_multi_by_shiftでは乗算命令がなくなっている。

_multi:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        imull   12(%ebp), %eax
        popl    %ebp
        ret
        .section .rdata,"dr"
        .align 4
 
 
_multi_by_shift
        pushl   %ebp
        movl    %esp, %ebp
        subl    $56, %esp
        movl    $0, -12(%ebp)
        movl    $0, -16(%ebp)
        movl    $0, -16(%ebp)
        jmp     L4
L7:
        movl    8(%ebp), %eax
        andl    $1, %eax
        movl    %eax, -20(%ebp)
        cmpl    $0, -20(%ebp)
        je      L5
        movl    12(%ebp), %eax
        jmp     L6
L5:
        movl    $0, %eax
L6:
        movl    %eax, -24(%ebp)
        movl    -24(%ebp), %eax
        addl    %eax, -12(%ebp)
        movl    -12(%ebp), %eax
        movl    %eax, 20(%esp)
        movl    -24(%ebp), %eax
        movl    %eax, 16(%esp)
        movl    12(%ebp), %eax
        movl    %eax, 12(%esp)
        movl    -20(%ebp), %eax
        movl    %eax, 8(%esp)
        movl    -16(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $LC1, (%esp)
        call    _printf
        sarl    8(%ebp)
        sall    12(%ebp)
        addl    $1, -16(%ebp)
L4:
        cmpl    $31, -16(%ebp)
        jle     L7
        movl    -12(%ebp), %eax
        leave
        ret

関連記事

コメントを残す

メールアドレスが公開されることはありません。