シミュレーション

アセンブラで円周率を求める

2007年4月30日

16ビット長の変数で円周率が 3.1416 まで求められることが分かったので、アセンブラで計算してみた。インテル 80386 系は、レジスタが32ビット。16ビットどうしの乗算を行っても、結果が32ビット内に収まるので、比較的容易にアセンブラで書ける。

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

double sub(int step);

int main (){
    unsigned int step=65536;
    for(unsigned int i=0;i<(0x1fffffff-step);i+=step){
        double pai=sub((int)step);
        printf( "%d %f\n", i, pai);
        if (kbhit()) return 0;
    }
    while (!kbhit());
    return 0;
}

double sub(int step){
    int i;
    double pai;
    static unsigned int num,inside;
    unsigned int r1,r2;

    _asm mov ecx,step;
label05:
        //get 16 bit random values (note that max value of rand() is 32767)
        _asm push ecx;
        r1=(rand() & 0x7fff)*2 + (rand() & 1);
        r2=(rand() & 0x7fff)*2 + (rand() & 1);
        _asm pop ecx;

        //Determine if inside circle
        _asm mov eax,r1;
        _asm mul eax;
        _asm mov ebx,eax;
        _asm mov eax,r2;
        _asm mul eax;
        _asm add eax,ebx;
        _asm jc label10;
        _asm mov eax,inside;
        _asm inc eax;
        _asm mov inside,eax;
    label10:
        _asm mov eax,num;
        _asm inc eax;
        _asm mov num,eax;
    _asm loop label05;

    // Calculate the pai
    //pai=4*(double)inside/(double)num;
    unsigned int res=0;
    _asm mov eax,inside;
    _asm shl eax,2;
    _asm mov edx,num;
    _asm mov ebx,res;
    _asm mov ecx,6;
label20:
        _asm cmp eax,edx;
        _asm jb label30;
        _asm sub eax,edx;
        _asm inc ebx;
        _asm jmp label20;
    label30:
        _asm push edx;
        _asm mov edx,0ah;
        _asm mul edx;
        _asm pop edx;
        _asm shl ebx,4;
    _asm loop label20;
    _asm mov res,ebx;

    // Determine pai from the res
    pai=0;
    for (i=0;i<6;i++){
        res=res/16;
        pai=pai/10+(double)(res%16);
    }

    return pai;
}

このコードで、3.1416 をはじきだすことが出来る。

ここでは、乗算命令に mul を用いているが、これを加算命令のみで実行できるように改変する必要がある。

最後に除算で円周率を求める部分は、比較的容易なコードで実現できることが分かった。

上記のコードから、アセンブラの『mul』命令を除いてしまうと、次のようなコードになった。
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

double sub(int step);

int main (){
    unsigned int step=65536;
    for(unsigned int i=0;i<(0x1fffffff-step);i+=step){
        double pai=sub((int)step);
        printf( "%d %f\n", i, pai);
        if (kbhit()) return 0;
    }
    while (!kbhit());
    return 0;
}

double sub(int step){
    int i;
    double pai;
    static unsigned int num,inside;

    _asm mov ecx,step;
label05:
        //Determine if inside circle
        _asm call label_rand;//_asm mov eax,rand();
        //_asm mul eax;
        _asm mov edx,eax;
        _asm call label_mul; // _asm mul edx
        _asm mov ebx,eax;
        _asm call label_rand;//_asm mov eax,rand();
        _asm mov edx,eax;
        _asm call label_mul; // _asm mul edx
        _asm add eax,ebx;
        _asm jc label10;
        _asm mov eax,inside;
        _asm inc eax;
        _asm mov inside,eax;
    label10:
        _asm mov eax,num;
        _asm inc eax;
        _asm mov num,eax;
    _asm loop label05;

    // Calculate the pai
    //pai=4*(double)inside/(double)num;
    unsigned int res=0;
    _asm mov eax,inside;
    _asm shl eax,2;
    _asm mov edx,num;
    _asm mov ebx,res;
    _asm mov ecx,7;
label20:
        _asm cmp eax,edx;
        _asm jb label30;
        _asm sub eax,edx;
        _asm inc ebx;
        _asm jmp label20;
    label30:

        // eax=eax*10
        _asm push edx;
        _asm shl eax,1;
        _asm mov edx,eax;
        _asm shl eax,2;
        _asm add eax,edx;
        _asm pop edx;

        _asm shl ebx,4;
    _asm loop label20;
    _asm mov res,ebx;

    // Determine pai from the res (ebx)
    pai=0;
    for (i=0;i<7;i++){
        res=res/16;
        pai=pai/10+(double)(res%16);
    }

    return pai;

/* rand() */
label_rand:
    _asm push ebx;
    _asm push ecx;
    _asm push edx;
    i=(rand() & 0x7fff)*2 + (rand() & 1);
    _asm pop edx;
    _asm pop ecx;
    _asm pop ebx;
    _asm mov eax,i;
    _asm ret;

/* eax=eax*edx */
label_mul:
    _asm push ebx;
    _asm push ecx;
    _asm mov ebx,eax;
    _asm xor eax,eax;
    _asm mov ecx,010h;
label_mul_1:

        _asm shr ebx,1;
        _asm jnc label_mul_2;
        _asm add eax,edx;
    label_mul_2:
        _asm shl edx,1;

    _asm loop label_mul_1;

    _asm pop ecx;
    _asm pop ebx;
    _asm ret;
}

 ここまでのシミュレーションで分かったことは、以下のとおり。

1)フラグは、ゼロフラグとキャリーフラグの二つでよい。
2)初期の段階でスタックを実装することが、望ましい。

 レジスタの数を減らして、メモリとのアクセスを充実させるような設計(モトローラ型)の方が、CPUの設計としてはやりやすいかもしれない。当初の計画では、メモリを使う頻度を減らすインテル型のCPUにする予定であった(メモリをフリップフロップで作成するのに骨が折れるため)が、このあたりの仕様について再考すべし。

コメント

コメントはありません

コメント送信