循環小数の計算

 投稿者:山中和義  投稿日:2009年 7月 2日(木)10時50分2秒
  循環小数を分数に変換して、分数どうしで計算する。結果を循環小数に戻す。
変換する関数をつくれば、有理数モードで計算できる。
OPTION ARITHMETIC RATIONAL

LET MaxLevel=200 !循環節の最大桁数
DIM s(MaxLevel) !循環節の候補


!●循環小数を分数へ

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

!LET x$="2.2[234]" !2.2234234234…
!LET x$="0.0[90]" !0.0909090…
!LET x$="0.[142857]" !0.142857142857…
LET x$="0.1[23]" !0.1232323…

PRINT ExVAL(x$)



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

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


FUNCTION ExVAL(x$) !数値を表現する文字列を数値に変換する
   LET L=LEN(x$) !文字列長を得る

   LET p=0 !有限小数の桁数
   LET k=0 !循環節の桁数。有限小数の場合、0
   LET A=0
   LET cSGN=1 !符号
   LET flag=0 !「整数」 ※数値の型

   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 t$="[" THEN !循環小数なら
         IF flag<>1 THEN !小数部か?
            PRINT "循環小数の開始位置が不正です。"; x$
            STOP
         END IF
         LET flag=2 !「循環小数」
      ELSEIF t$="]" THEN
         IF i<>L THEN !右端か?
            PRINT "循環小数の終了位置が不正です。"; x$
            STOP
         END IF
      ELSE
         LET A=A*10+VAL(t$) !多項式(( … ((a[1]*10+a[2])*10+a[3])*10 … +a[i-2])*10+a[i-1])*10+a[i] ※左シフト
         IF flag=1 THEN LET p=p+1
         IF flag=2 THEN LET k=k+1
      END IF

      LET i=i+1 !次へ
   LOOP

   !PRINT A; flag;cSGN;p;k !debug
   IF flag<2 THEN !有限小数(整数も含む)の場合
      LET ExVAL=cSGN * A/10^p
   ELSE !循環小数の場合
      LET B=INT(A/10^k)
      LET ExVAL=cSGN * (A-B)/(10^(k+p)-10^p)
   END IF
END FUNCTION

FUNCTION ExSTR$(x) !数値式を表示するときの文字列に変換する
   LET a=ABS(x)


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

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

   DO WHILE aa>=10
      LET b$=STR$(MOD(aa,10))&b$ !a=b[k]*10^k+b[k-1]*10^(k-1)+ … +b[1]*10^1+b[0]*10^0より

      LET aa=INT(aa/10) !次の桁へ ※右シフト
   LOOP
   LET b$=STR$(aa)&b$


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

   LET k=1 !小数桁

   DO UNTIL aa=0 !小数第1位から順に
      IF k=1 THEN !初回のみ
         LET b$=b$&"." !小数点をつける
         LET p=POS(b$,".")
      ELSE
         FOR i=1 TO k-1 !循環したか確認する
            IF s(i)=aa THEN !循環節
               LET b$(i+p:i+p)="["&b$(i+p:i+p) !開始記号を挿入
               LET b$=b$&"]" !終了記号
               EXIT DO
            END IF
         NEXT i
      END IF
      LET s(k)=aa

      LET aa=aa*10 !左シフト
      LET b$=b$&STR$(INT(aa)) !a=S[-1]*10^(-1)+S[-2]*10^(-2)+ … +S[-(k-1)]*10^(-(k-1))+S[-k]*10^(-k)より

      LET aa=aa-INT(aa) !整数部分を削除して、次の桁へ

      LET k=k+1
      IF k>MaxLevel THEN !循環小数によるループを回避する
         PRINT "変換を打ち切りました。"
         EXIT DO
      END IF
   LOOP

   IF x<0 THEN LET ExSTR$="-"&b$ ELSE LET ExSTR$=b$ !….…形式
END FUNCTION

END
 

Re: 循環小数の計算

 投稿者:山中和義  投稿日:2009年 7月 3日(金)15時27分2秒
  > No.422[元記事へ]

おおげさに考えずに、、、
!問 0.1[23] ÷ 0.[14] の結果を循環小数で表せ。 答え 0.8[714285]

OPTION ARITHMETIC RATIONAL


!●循環小数を分数に変換する筆算
! 有限小数(整数も含む)の部分
!  循環節までの部分 ※小数部分の桁数を「有限小数の桁数」とする。
! 循環小数の部分
!  分子: 循環節
!  分母: repeat$("9",循環節の桁数) & repeat$("0",有限小数の桁数)
!
! 例. 12.345[6789] の場合、12.345+6789/9999000 となる。

LET a=0.1+23/990 !上記に従って、式を組み立てる
LET b=0.+14/99
PRINT a; b; a/b
PRINT USING "#.###############################": a/b


!●無限等比級数の和を使って分数に変換する
! 例. 12.345[6789] の場合、12.345+0.0006789/(1-1/10^4) となる。
!  0.0006789: 最初の循環節を小数表現
!  4: 循環節の桁数
! ∵循環小数の部分は
!   0.0006789
!   +0.00000006789
!   +0.000000000006789
!   + ・・・ 、すなわち、初項0.0006789,公比1/10^4=0.0001の無限等比級数

LET a=0.1+0.023/(1-1/10^2)
LET b=0.+0.14/(1-1/10^2)
PRINT a; b; a/b
PRINT USING "#.###############################": a/b

END
 

Re: 循環小数の計算

 投稿者:荒田浩二  投稿日:2009年 7月 6日(月)20時22分7秒
  > No.422[元記事へ]

山中和義さんの FUNCTION ExSTR$(x) で分数を循環小数に変換するループを拝見し、これを10進15桁モードではできないかと思い立ち作ってみました。
計算過程は筆算と同じです。分母をnとすれば、n-1桁までで割り切れるか同じ余りが現れるかです。
計算結果の桁数に制限はないのです(真値が求まります)が、分母の大きさで配列sを宣言しているため、分母が大きすぎるとエラーになります。
1000桁モード,有理数モードでも実行できます。(2進モードでは小数で誤差が出てしまう)

DECLARE EXTERNAL FUNCTION Ex2STR$ ! 分数を小数,循環小数の文字列に変換する関数
DO
   READ IF MISSING THEN EXIT DO : x$
   PRINT x$ ; "  入力数値"
   CALL fraction(x$,a,b)  ! 整数,有限小数,循環小数,分数の文字列(x$)を既約分数(a/b)に変換
   IF a>=0 THEN LET m$=" " ELSE LET m$=""
   IF b=1 THEN LET f$=STR$(a) ELSE LET f$=STR$(a)&"/"&STR$(b)
   PRINT m$&f$ ; "  分数表示"
   PRINT a/b ; " 小数15桁表示"
   PRINT m$&Ex2STR$(a,b) ; "  小数真値[循環節]表示" ! 分数(a/b)を小数,循環小数の文字列に変換
   PRINT
LOOP

DATA "-18" , "47." , "-12.34" , "-.67" , "+972.[51]" , "-73.482[3058]"
DATA "0.000[217]" , "+6.0054[83]" , ".031[040]" , "-78/13" , "0/26" , "740.52/29.84"
DATA "634517/3637" , "24/56" , "-5/12" , "91/35" , "886240513930735/10485760"

END


EXTERNAL SUB fraction(x$,numer,denom) ! x$を分数numer/denomに変換
LET dec$=LTRIM$(RTRIM$(x$))
IF dec$(1:1)="-" THEN LET s=-1 ELSE LET s=1
IF dec$(1:1)="+" OR dec$(1:1)="-" THEN LET dec$=dec$(2:LEN(dec$))

LET sp=POS(dec$,"/")
IF sp>1 THEN ! 分数
   LET numer=VAL(dec$(1:sp-1))
   LET denom=VAL(dec$(sp+1:LEN(dec$)))
   CALL reduce(numer,denom) ! 約分
   LET numer=s*numer
   EXIT SUB
END IF

LET dp=POS(dec$,".")
IF dp=0 OR dp=LEN(dec$) THEN ! 整数
   LET numer=s*VAL(dec$)
   LET denom=1
   EXIT SUB
END IF

IF dp=1 THEN LET intp=0 ELSE LET intp=VAL(dec$(1:dp-1)) ! 整数部
LET rp=POS(dec$,"[")
IF rp=0 THEN  ! 有限小数
   LET dl=LEN(dec$)-dp
   LET denom=10^dl
   LET numer=intp*denom+VAL(dec$(dp+1:LEN(dec$)))
   CALL reduce(numer,denom)
   LET numer=s*numer
   EXIT SUB
END IF

IF rp=dp+1 THEN
   LET dconst=0  ! 例"37.[61]"
   LET dconst_denom=1
   LET dl=0
ELSE
   LET dconst=VAL(dec$(dp+1:rp-1)) ! 小数定数部
   LET dl=rp-dp-1                  ! 小数定数部桁数
   LET dconst_denom=10^dl
   CALL reduce(dconst,dconst_denom)
END IF
LET rl=LEN(dec$)-rp-1                 ! 循環節桁数
LET recur=VAL(dec$(rp+1:LEN(dec$)-1)) ! 循環節
LET recur_denom=10^(LEN(dec$)-dp-2)*(1-1/10^rl)
CALL reduce(recur,recur_denom)

!PRINT intp;dconst;dconst_denom;intp+dconst/dconst_denom
!PRINT recur;recur_denom;recur/recur_denom

!!  intp/1 + dconst/dconst_denom + recur/recur_denom
LET numer=intp*dconst_denom+dconst
CALL reduce(numer,dconst_denom)
LET numer=numer*recur_denom+recur*dconst_denom
LET denom=dconst_denom*recur_denom
CALL reduce(numer,denom)
LET numer=s*numer

END SUB


EXTERNAL FUNCTION Ex2STR$(numer,denom) !分数を小数,循環小数の文字列に変換
!「No.422 循環小数の計算(山中和義氏)」FUNCTION ExSTR$(x) 参照

IF SGN(numer/denom)=-1 THEN LET u$="-" ELSE LET u$=""
LET numer=ABS(numer)
LET denom=ABS(denom)
CALL reduce(numer,denom) ! 約分

!整数部
IF denom=1 THEN
   LET Ex2STR$=u$&STR$(numer)
   EXIT FUNCTION
END IF

DIM s(denom-1) ! 剰余を格納する配列
LET aa=INT(numer/denom) !小数部を削除する
IF aa=0 THEN LET b$="." ELSE LET b$=STR$(aa)&"."

!小数部
LET p=POS(b$,".")
LET numer=MOD(numer,denom) ! 剰余(商=aa)
LET k=1 !小数桁

DO UNTIL numer=0
   FOR i=1 TO k-1 !循環したか確認する
      IF s(i)=numer THEN
         LET b$(i+p:i+p)="["&b$(i+p:i+p) !開始記号を挿入
         LET b$=b$&"]" !終了記号
         EXIT DO
      END IF
   NEXT i
   LET s(k)=numer
   LET b$=b$&STR$(INT(10*numer/denom))

   LET numer=MOD(10*numer,denom)

   LET k=k+1
   IF k>denom THEN ! 配列sの添字オーバーを回避する
      PRINT "変換を打ち切りました。"
      EXIT DO
   END IF
LOOP

LET Ex2STR$=u$&b$
END FUNCTION


EXTERNAL SUB reduce(p,q) ! 約分
!十進BASIC添付 "\BASICw32\Math\GCDLOOP.BAS" 参照
REM 互除法により,入力された2数の最大公約数を求める → その後,約分
LET a=p
LET b=q
DO
   LET r=MOD(a,b)
   IF r=0 THEN EXIT DO
   LET a=b
   LET b=r
LOOP
LET p=p/b
LET q=q/b
END SUB
 

戻る