自作TTLコンピュータで円周率を計算する(第3話)

8bitメモリにshort int(16bit変数)を格納する方法

本サイトは1970年代にオリジナルCPUの設計と、そのCPUを使ったコンピュータの自作を夢見ていた青年(当時)が40年の月日を経て、完成させたTTLコンピュータ、RETROF-8のソフトウェア作成編です

円周率を求める自作TTLコンピュータ
 2011年8月5日 teitterで限定公開。8月9日、一部を修正し正式公開。

【ご注意】ここで紹介するプログラムは個人の趣味で、TTL-ICのみで作った超貧弱8bitコンピュータで円周率を求めることを最優先かつ最終目的としています。断じて模範になるプログラムの紹介ではありません。 コーディングスタイルも超我流です。学校教育や企業の開発現場でのプログラミングテクニックの向上を目指す方々にとっては全く参考にはならないどころかむしろ有害です。何度も申しますが、この辺りの事情を十分に御理解の上、お読み頂けると幸いに存じます。

加減算と8ビット変数のみで円周率を求める

for文を全てdo〜whileに置き換える

ここから先はかなりマッド(Mad engineer)な世界となります。
まずは円周率の算出とは無関係の前処理ですが全てのfor文をdo〜while文に置き換えます。
こうした方が1行が1つの機械語に対応する形に近くなり、これから行う各種のコード変換が容易になるからです。

最終的にはこのdo〜while文は、goto文を経て、最終的には機械語のJMP命令とする予定です。

void main(void) {

    unsigned short i,j,k,n,m,nom[ARRAYMAX];
    
    i = 0;
    do{
        nom[i] = 2; 
        ++i;
    }while(i < ARRAYMAX); 
    i = ARRAYMAX-1;
    do{
        m = 0;
        j = i - 1;
        do{
            if(j == 0) break;
            n = 0;
            k = 0;
            do{
                n += m;
                ++k;
            }while(k < j);
            m = 0;
            k = 0;
            do{
                m += nom[j];
                ++k;
            }while(k <; 10);
            m += n;
            n = j+j-1;
            k = 0;
            do{
                if(m < n) break;
                m -= n;
                ++k;
            }while(1);
            nom[j] = m;
            m = k;
            --j;
        }while(1) ;
        dig[ARRAYMAX - i - 1] = m;
        --i ;
    }while(i>0) ;

    debug_print(0,200) ; //200桁まで表示する
}

unsigned short int を unsigned char[2] に書き換える

ターゲットマシンであるRETROF-8は8bitまでの整数しか扱うことができません。unsigned short int の様に16bitを要する変数は8bitの変数を2つ使って実現するしかありません。とりあえず連続した2バイトを1つの16bit変数の如く扱うマクロを作り、i,j,k の3つの変数を8bit化してみました。

目的 16bit変数の普通の書き方  8bit変数で同等処理を行うための等価コード マクロ形式 
 定義  unsigned short a;  unsigned char a[2] ;  
 代入  a=X ; //Xは定数  a[0]=N/256; a[1]=N%256;  SET(x,y)
 一増  ++a ;  ++a[1]; if (a[1]==0)++a[0];  INC(x)
 一減  --a ;  --a; if(a[1]==255) --a[0] ;  DEC(x)
 参照  X = a;  //Xは16bit変数  X = a[0]*256+a[1];  VAL(x)

以下が変換後のコードです。 

#define SET(x,y) x[0]=y/256;x[1]=y%256 
#define INC(x)   ++x[1]; if(x[1]==  0) ++x[0] 
#define DEC(x)   --x[1]; if(x[1]==255) --x[0]
#define VAL(x)   (x[0]*256+x[1])

void main(void) {

    unsigned char I[2],J[2],K[2];
    unsigned short n,m,nom[ARRAYMAX];
    
    SET(I,0);
    do{
        nom[VAL(I)] = 2; 
        INC(I);
    }while(VAL(I) < ARRAYMAX); 
    SET(I,(ARRAYMAX-1));
    do{
        m = 0;
        SET(J,(VAL(I) - 1));
        do{
            if(VAL(J) == 0) break;
            n=0;
            SET(K,0);
            do{
                n += m;
                INC(K);
            }while(VAL(K) < VAL(J));
            m = 0;
            SET(K,0);
            do{
                m += nom[VAL(J)];
                INC(K);
            }while(VAL(K) < 10);
            m += n;
            n = VAL(J)+VAL(J)-1;
            SET(K,0);
            do{
                if(m < n) break;
                m -= n;
                INC(K);
            }while(1);
                    
            nom[VAL(J)] = m;
            m = VAL(K);
            DEC(J);
        }while(1) ;
        dig[ARRAYMAX - VAL(I) - 1] = m;
        DEC(I) ;
    }while(VAL(I)>0) ;
    debug_print(0,160) ; //引数で任意範囲を表示可
}

他の変数も8bit化する

変数n,mの8bit化

変数n,mも、変数i,j,k同様に、要素数2にunsigned char型配列に置き換えます。
n,mは加算や減算の対象となりますので、新たに加算マクロと減算マクロを定義します。

目的 普通の書き方  8bit変数で同等処理を行うための等価コード マクロ形式 
 加算  a+=b;  C=x[1];x[0]+=y[0];x[1]+=y[1];if(x[1]<C)++x[0]  ADD(X,Y)
 減算  a-=b;  C=x[1];x[0]-=y[0];x[1]-=y[1];if(x[1]>C)--x[0]  SUB(x,Y)

変数Cは、加算(減算)において、下位8bit同志の加算(減算)が溢れ(借り)を生じたか否かを判断するためのワーク変数です。何かを足した(引いた)のに値が減って(増えて)いたら、溢れ(借り)が生じたと判断し、上位8bitの演算結果を補正します。

配列nom[512]の8bit化

nom[512]は、元々が配列ですから、他の変数の様に単に配列化して8bit化することはできません。
nom[512][2]やnom[2][512]とする方法もありますが、各配列要素のみではなく添字自身も8bit化が必要です。ここでは、要素数が256の配列を4つ定義して、各要素と添字を共に8bit化します。

H0[256] ← nom[0..255]の各要素の上位8bitを記憶する配列
L0[256] ← nom[0..255]の各要素の下位8bitを記憶する配列
H1[256] ← nom[256..511]の各要素の上位8bitを記憶する配列
L1[256] ← nom[256..511]の各要素の下位8bitを記憶する配列

マクロは添え字の上位8bitが0か1かを条件としたif文でアクセスする配列を選びます。
かなり複雑になりますが、配列アクセス専用に用意したマクロを下記に示します。

細かい話ですが、下記のマクロの定義では、プログラムにマクロの記述する際の末尾のセミコロンは不要です。
一方、本頁末尾に添付したプログラムでは当該マクロ末尾にセミコロンを書いてます。
C系言語では単独セミコロンはNULL文として扱われ無害であるので、見栄えを合わせる為に書いています。

目的 普通の書き方  8bit変数で同等処理を行うための等価コード マクロ形式 
 配列代入  nom[a]=X;  if(x[0]==0){H0[x[1]]=y/256;L0[x[1]]=y%256;}\
else {H256[x[1]]=y/256;L256[x[1]]=y%256;}
 SETARRAY(a,X)
 配列加算  a+=nom[b];  if(y[0]==0){C=x[1];x[0]+=H0[y[1]];x[1]+=L0[y[1]];\
if(x[1]<C)++x[0];}\ else{C=x[1];x[0]+=H1[y[1]];x[1]+=L1[y[1]];\
if(x[1]<C)++x[0];}\
 ADDARRAY(a,b)

変換結果

8bit化のために書き換えたのはmain()だけですが、コピペで実行できる全ソースを以下に紹介します。ヘッダーファイルは処理系に依存しますのでご注意ください。尚、最終的に円周率の各桁の値が格納される配列dig[p]の格納位置をこれまでのプログラムと逆にしました。(3.141592...の「3」はこれまでdig[0]に格納してましたが、これをdig[511]に格納するように変更しています。こちらの方が配列への結果格納が簡単になることが判ったからです。デバッグ用の結果表示プログラムもこれに合わせて書き換えましたので、結果はこれまでと同様に表示されます)

#include "stdafx.h" // ヘッダは処理系に依存する
const int ARRAYMAX = 512 ;
unsigned char dig[ARRAYMAX];  // 小数各桁の値
void debug_print(int from,int to) {
    for(int p=0; p<ARRAYMAX-1; ++p)  //(注)
        if (dig[p]>9) dig[p]-=10,dig[p+1]++ ;
    for(int p=ARRAYMAX-from-1; p >= ARRAYMAX-to; --p) {
        if (p%10==1) printf(" ") ;
        if (p%50==11) printf("\n") ;
        printf("%d",dig[p]) ; 
    }
    getchar(); //一時停止(VC++コンソールアプリ)
}
#define VAL(x)   (x[0]*256+x[1]) 
#define SET(x,y) x[0]=y/256;x[1]=y%256 
#define INC(x)   ++x[1]; if(x[1]==  0) ++x[0] 
#define DEC(x)   --x[1]; if(x[1]==255) --x[0]
#define ADD(x,y) C=x[1];x[0]+=y[0];x[1]+=y[1];if(x[1]<C)++x[0]
#define SUB(x,y) C=x[1];x[0]-=y[0];x[1]-=y[1];if(x[1]>C)--x[0]
#define SETARRAY(x,y) if(x[0]==0){H0[x[1]]=y/256;L0[x[1]]=y%256;}\
                      else {H1[x[1]]=y/256;L1[x[1]]=y%256;} 
#define ADDARRAY(x,y) if(y[0]==0)\
      {C=x[1];x[0]+=H0[y[1]];x[1]+=L0[y[1]];if(x[1]<C)++x[0];}\
 else {C=x[1];x[0]+=H1[y[1]];x[1]+=L1[y[1]];if(x[1]<C)++x[0];}

void main(void) {
          
    unsigned char C,I[2],J[2],K[2],M[2],N[2];
    unsigned char H0[256],L0[256],H1[256],L1[256];
        
    SET(I,0);
    do{
        SETARRAY(I,2);
        INC(I);
    }while(VAL(I) < ARRAYMAX); 
    SET(I,(ARRAYMAX-1));
    do{
        SET(M,0);
        SET(J,VAL(I));
        DEC(J);
        do{
            if(VAL(J) == 0) break;
            SET(N,0); 
            SET(K,0);
            do{
                ADD(N,M); 
                INC(K);
            }while(VAL(K) < VAL(J));
            SET(M,0);
            SET(K,0);
            do{
                ADDARRAY(M,J);
                INC(K);
            }while(VAL(K) < 10);
                        ADD(M,N);
            SET(N,(VAL(J))); 
            ADD(N,J);
            DEC(N);
            SET(K,0);
            do{
                if(VAL(M) < VAL(N)) break; 
                SUB(M,N);
                INC(K);
            }while(1);
            SETARRAY(J,VAL(M)); 
            SET(M,VAL(K)); 
            DEC(J);
        }while(1) ;
        dig[VAL(I)] = VAL(M);
        DEC(I) ;
    }while(VAL(I)>0) ;
    debug_print(0,200) ; //引数で任意範囲を表示可
}

実行結果

上記プログラムを確認の為、VC++(2008 Express、コンソールアプリケーション)にコピペして実行した結果です。

8bit変数の加減算のみで円周率を求めた。


乗除算ふたたび...?

ドクペのブランデー割変数を全て8bitにする事には成功しましたが、よく見るとマクロの記述に一度は撤廃に成功した筈の乗除算がまた現れています。何となく、堂々巡りに陥っている感がありますが、次回は再度、乗除算の撤廃に挑む予定です。

左の写真と本文は何の関係もありません


TTLコンピュータ

円周率編

  1. C言語でのアルゴリズム検証
  2. 乗除算を加減算に書き換えた
  3. 8bitメモリにshort intを格納
  4. 人間プリプロセッサ
  5. 殆どアセンブラ