n進法での循環小数の計算

 投稿者:山中和義  投稿日:2009年 7月11日(土)10時48分41秒
  進数変換は、n進⇔10進をベースに計算します。
各進法での数字は、0123456789ABCDEFGHIJKL … XYZ … です。(表示は36進法まで)
!n進法での整数、小数(有限小数、循環小数)、分数の計算

OPTION ARITHMETIC RATIONAL

FUNCTION ExBVAL(x$,RADIX) !(RADIX)進法の数値を表現する文字列を10進法数値に変換する
   IF RADIX<1 OR RADIX<>INT(RADIX) THEN
      PRINT "基数は2以上の整数を指定してください。"; RADIX
      STOP
   END IF

   CALL dec2frac(x$,RADIX, m,n) !小数を分数へ
   LET ExBVAL=m/n
END FUNCTION

FUNCTION ExVAL(x$) !数値を表現する文字列を10進法数値に変換する
   LET ExVAL=ExBVAL(x$,10)
END FUNCTION

!下位のルーチン
FUNCTION VAL1(x$,N) !N進法の数値  ※ASCIIコード表から
   IF x$>"9" THEN
      LET t=ORD(x$)-ORD("A")+10
   ELSE
      LET t=ORD(x$)-ORD("0") !※t=VAL(x$)でも可
   END IF
   IF t<0 OR t>=N THEN !0~N-1の範囲かどうか確認する
      PRINT x$;"は範囲外の値です。"
      STOP
   END IF
   LET VAL1=t
END FUNCTION
SUB dec2frac(x$,RADIX, m,n) !(RADIX)進数数値を表現する文字列を10進法分数(m/n)に変換する
   LET L=LEN(x$) !文字列長を得る

   LET p=0 !有限小数の桁数
   LET k=0 !循環節の桁数。有限小数の場合、0
   LET A=0
   LET cSGN=1 !仮数の符号
   LET cSGN2=1 !指数部の符号
   LET flag=0 !「整数」  ※数値の型
   !書式  整数、小数: ±9{.}{E±9} | ±{9}.9{E±9}
   !      分数: ±9/9、循環小数: ±{9}.{9}[9]

   LET i=1 !数字列の読み込み位置
   DO WHILE i<=L !上位の桁から順に
      LET t$=UCASE$(x$(i:i))
      IF t$="." THEN !小数点なら
         IF flag<>0 THEN
            PRINT "小数点の位置が不正です。"; x$
            STOP
         END IF
         LET flag=1 !「有限小数」
      ELSEIF t$="+" THEN !+符号なら
         IF i<>1 THEN
            PRINT "+符号の位置が不正です。"; x$
            STOP
         END IF
      ELSEIF t$="-" THEN !-符号なら
         IF i<>1 THEN
            PRINT "-符号の位置が不正です。"; x$
            STOP
         END IF
         LET cSGN=-1
      ELSEIF (RADIX<14 AND t$="E") OR t$="@" THEN !指数部なら  ※14進法以上は、E.E@E(E.E*E^Eの意)
         IF flag>1 THEN !浮動小数点数か?
            PRINT "仮数は 9{.}|{9}.9 形式ではありません。"; x$
            STOP
         END IF

         IF i+1<=L THEN !指数部の符号を得る
            LET t$=UCASE$(x$(i+1:i+1))
            IF t$="+" THEN !+符号なら
               LET i=i+1
            ELSEIF t$="-" THEN !-符号なら
               LET cSGN2=-1
               LET i=i+1
            END IF
         END IF
         IF i=L THEN !右端か?
            PRINT "指数がありません。"; x$
            STOP
         END IF
         LET flag=2 !「指数部あり」
         LET B=A !save it as fraction
         LET A=0 !exponent
      ELSEIF t$="/" THEN !分数なら
         IF flag<>0 THEN !整数か?
            PRINT "分子は整数ではありません。"; x$
            STOP
         END IF
         LET flag=3 !「分数」
         LET B=A !save it as numerator
         LET A=0 !denominator
      ELSEIF t$="[" THEN !循環小数なら
         IF flag<>1 THEN !小数部か?
            PRINT "循環小数の開始位置が不正です。"; x$
            STOP
         END IF
         LET flag=4 !「循環小数」
      ELSEIF t$="]" THEN
         IF i<>L THEN !右端か?
            PRINT "循環小数の終了位置が不正です。"; x$
            STOP
         END IF
      ELSE
         LET A=A*RADIX+VAL1(t$,RADIX) !多項式(( … ((a[1]*RADIX+a[2])*RADIX+a[3])*RADIX … +a[i-2])*RADIX+a[i-1])*RADIX+a[i]  ※左シフト
         IF flag=1 THEN LET p=p+1
         IF flag=4 THEN LET k=k+1
      END IF

      LET i=i+1 !次へ
   LOOP

   !PRINT A;B; flag;cSGN;cSGN2;p;k !debug
   IF flag<2 THEN !有限小数(整数も含む)の場合
      LET m=cSGN * A
      LET n=RADIX^p
   ELSEIF flag=2 THEN !指数部あり小数の場合
      IF cSGN2*A< p THEN
         LET m=cSGN * B
         LET n=RADIX^(p-cSGN2*A)
      ELSE
         LET m=cSGN * B*RADIX^(cSGN2*A-p)
         LET n=1
      END IF
   ELSEIF flag=3 THEN !分数の場合
      IF A=0 THEN
         PRINT "0で割ることはできません。"; x$
         STOP
      END IF
      LET m=cSGN * B
      LET n=A
   ELSE !循環小数の場合
      LET B=INT(A/RADIX^k)
      LET m=cSGN * (A-B)
      LET n=(RADIX^(k+p)-RADIX^p)
   END IF

   LET B=GCD(m,n) !最大公約数で分子と分母を約分する
   LET m=m/B
   LET n=n/B
END SUB
FUNCTION GCD(a,b) !最大公約数を求める
   DO UNTIL b=0
      LET r=MOD(a,b)
      LET a=b
      LET b=r
   LOOP
   LET GCD=a
END FUNCTION


LET Precision=1000 !循環節の最大桁数 ※必要に応じて変更のこと
DIM s(Precision) !循環節の候補

FUNCTION ExBSTR$(x,RADIX) !10進法数値式を(RADIX)進法で小数表示するときの文字列に変換する
   IF RADIX<1 OR RADIX<>INT(RADIX) THEN
      PRINT "基数は2以上の整数を指定してください。"; RADIX
      STOP
   END IF

   CALL dec2frac(STR$(x),10, m,n) !小数を分数へ
   LET b$=frac2dec$(m,n,RADIX) !分数を小数へ
   IF RADIX<>10 THEN LET b$=b$&"("&STR$(RADIX)&")" !進法(RADIX)をつける
   LET ExBSTR$=b$
END FUNCTION

FUNCTION ExSTR$(x) !10進法数値式を小数表示するときの文字列に変換する
   LET ExSTR$=ExBSTR$(x,10) !指数つき小数と分数は、STR$を使う

   !!!LET b$=ExBSTR$(x,10)
   !!!IF POS(b$,"[")>0 THEN LET ExSTR$=b$ ELSE LET ExSTR$=STR$(x) !循環小数のみ採用する
END FUNCTION

!下位のルーチン
FUNCTION STR1$(x) !N進法の数字記号  ※ASCIIコード表から
   IF x>9 THEN
      LET STR1$=CHR$(x-10+ORD("A")) !ABCD…XYZ  ※N88系はASC("A")
   ELSE
      LET STR1$=CHR$(x+ORD("0")) !0123…789  ※STR$(x)でも可
   END IF
END FUNCTION

つづく
 

Re: n進法での循環小数の計算

 投稿者:山中和義  投稿日:2009年 7月11日(土)10時50分12秒
  > No.453[元記事へ]

つづき
FUNCTION frac2dec$(m,n,RADIX) !10進法分数(m/n)を(RADIX)進数で小数表示するときの文字列に変換する
   IF SGN(m)*SGN(n)<0 THEN LET cSGN$="-" ELSE LET cSGN$="" !符号を得る

   LET aa=ABS(m)
   LET b=ABS(n)

   LET t=GCD(aa,b) !最大公約数で分子と分母を約分する
   LET aa=aa/t
   LET b=b/t
   !!!PRINT aa;b;t !debug


   !整数部
   LET a=INT(aa/b) !小数部を削除する

   LET b$="" !変換後の数

   DO WHILE a>=RADIX !a=b[k]*RADIX^k+b[k-1]*RADIX^(k-1)+ … +b[1]*RADIX^1+b[0]*RADIX^0より
      LET b$=STR1$(MOD(a,RADIX))&b$ !一の位から求まる

      LET a=INT(a/RADIX) !次の桁へ  ※右シフト
   LOOP
   LET b$=STR1$(a)&b$


   !小数部
   LET aa=MOD(aa,b) !整数部を削除する

   LET k=0 !小数部の桁数

   DO UNTIL aa=0 !小数第1位から順に
      LET k=k+1
      IF k>MIN(b,Precision) THEN !循環小数によるループを回避する
         PRINT "変換を打ち切りました。"
         EXIT DO
      END IF

      IF k=1 THEN !初回のみ
         LET b$=b$&"." !小数点をつける
         LET p=LEN(b$) !位置を記録しておく
      ELSE
         FOR i=1 TO k-1 !循環したかどうか確認する
            IF s(i)=aa THEN EXIT DO !循環節なら、終了!
         NEXT i
      END IF
      LET s(k)=aa !小数第k位以降(未展開の小数部)の数を記録する

      LET aa=aa*RADIX !左シフトして、商を求める
      LET a=INT(aa/b)
      LET b$=b$&STR1$(a) !a=S[-1]*RADIX^(-1)+S[-2]*RADIX^(-2)+ … +S[-(k-1)]*RADIX^(-(k-1))+S[-k]*RADIX^(-k)より

      LET aa=MOD(aa,b) !剰余を求めて、次の桁へ
   LOOP

   IF aa=0 THEN !有限小数(整数も含む)なら
      LET p=k !有限小数の桁数
      LET k=0 !循環節の桁数
   ELSE !循環小数なら
      LET b$(i+p:i+p)="["&b$(i+p:i+p) !開始記号を挿入
      LET b$=b$&"]" !終了記号

      LET p=i-1 !有限小数の桁数
      LET k=k-i !循環節の桁数
   END IF

   LET frac2dec$=cSGN$&b$ !-9.9[9] 形式
END FUNCTION
!------------------------------ ここまでがサブルーチン



!● 0.[13](4)を10進法の分数で表現する  答え 7/15

!※等比数列の和  初項 0.13(4)=1/4^1+3/4^2、公比 0.01(4)=1/4^2
!  LET a = 0. + (1/4^1+3/4^2)/(1-1/4^2)
!  PRINT a

PRINT ExBVAL("0.[13]",4)




!● 1/7を3進法の小数(循環する)で表現する  答え 0.[010212](3)

PRINT ExBSTR$(1/7,3)




!● 0.1[23] ÷ 0.[14] の結果を循環小数で表せ。  答え 0.8[714285]

!筆算
!  x=0.1[23]とすると
!  100*x-x=12.3[23]-0.1[23]=12.2  ∴99*x=122/10  ∴x=61/495

LET t=ExVAL("0.1[23]") / ExVAL("0.[14]")
PRINT t, ExSTR$(t)




!● 3/7 を小数で表したとき、小数第800位の数字を求めよ。  答え 2

LET x$=ExSTR$(3/7) !0.[428571]
LET y$=x$(POS(x$,"[")+1:POS(x$,"]")-1) !循環節を切り出す
LET x=MOD(800,LEN(y$)) !800/6=133 余り 2
PRINT y$(x:x)



END


10進15桁モードの場合

先頭の OPTION ARITHMETIC RATIONAL をコメントアウトして、
計算部分のプログラムは以下のものに置き換えて、(小数⇔分数のルーチンを直接呼び出す)
10進15桁モードで実行します。
!● 0.[13](4)を10進法の分数で表現する  答え 7/15

!※等比数列の和  初項 0.13(4)=1/4^1+3/4^2、公比 0.01(4)=1/4^2
!  LET a = 0. + (1/4^1+3/4^2)/(1-1/4^2)
!  PRINT a

CALL dec2frac("0.[13]",4, m,n) !小数を分数へ
PRINT m;"/";n




!● 1/7を3進法の小数(循環する)で表現する  答え 0.[010212](3)

PRINT frac2dec$(1,7, 3) !分数を小数へ




!● 0.1[23] ÷ 0.[14] の結果を循環小数で表せ。  答え 0.8[714285]

!筆算
!  x=0.1[23]とすると
!  100*x-x=12.3[23]-0.1[23]=12.2  ∴99*x=122/10  ∴x=61/495

CALL dec2frac("0.1[23]",10, m,n)
CALL dec2frac("0.[14]",10, x,y)
PRINT frac2dec$(m*y,n*x,10) !(m/n)÷(x/y)=(m*y)÷(n*x)




!● 3/7 を小数で表したとき、小数第800位の数字を求めよ。  答え 2

LET x$=frac2dec$(3,7,10)
LET y$=x$(POS(x$,"[")+1:POS(x$,"]")-1) !循環節を切り出す
LET x=MOD(800,LEN(y$)) !800/6=133 余り 2
PRINT y$(x:x)
 

戻る