数値計算
多桁(10進数64桁まで)の整数計算です。
コマンドプロンプトでは、SETコマンドで数値計算ができるようになっています。
2進数32ビットの整数計算しかサポートしていないので、少し工夫し10進数64桁まで計算させます。(掛け算結果で約128桁まで求められます。)
この計算では、10進数4桁を1ブロックとしてブロック単位で計算しています。(理論的には10000進数に相当します。)
計算桁数は、理論的にはPCのメモリ容量で制限されますが、現実的には計算時間や結果の画面表示方法などの問題が発生するため、10進数4桁/ブロックで64桁(16ブロック分)までに制限しています。
また、整数計算の方法としては、10進数2進数変換を行って、2進数で計算する方法もありますがここでは取り上げません。(10進数⇔2進数の変換に時間がかかるためです。)
ページトップへ戻る
このサブルーチンでは、負数の計算もサポートしています。
負数は、100000の補数形式で計算しますので結果を返すときに符号付10進数に変換しています。
@title 多桁計算(64桁まで)
@echo off
REM ----+----1----+----2----+----3----+----4----+----5----+----6----
set sp=0000000000000000000000000000000000000000000000000000000000000000
set sp=0000000000000000000000000000000000000000000000000000000000000000%sp%
set /p na=数字1:
set /p nb=数字2:
call :set_num a na
call :set_num b nb
echo.
REM 加算
set s=
set /a cb=0
call :add
echo 加算の結果:%add%
pause
echo on
@goto :EOF
REM -------------------------ここからサブルーチン
REM 数値を4桁づつのブロックに分割
:set_num
set arg=%2
call set arg=%%%arg%%%
set sign%1=
if "%arg:~0,1%" == "-" set arg=%arg:~1%&set sign%1=-
set wk_num=%sp%%arg%
set wk_num=%wk_num:~-128%
for /l %%I in (0, 1, 31) do call :num_div %1 %%I
goto :EOF
:num_div
set /a %1[%2]=1%wk_num:~-4% - 10000
set wk_num=%wk_num:~0,-4%
goto :EOF
REM 足し算
:add
for /l %%I in (0,1,31) do call :add_main %%I
call :complement9
goto :EOF
:add_main
call set /a wk_num=(%signa%%%a[%1]%%) + (%signb%%%b[%1]%%) + (%cb%) + 100000
if %wk_num:~0,1% == 9 (
set /a cb=-1
set /a wk_num=%wk_num% - 90000 + 100000
) else (
if %wk_num:~0,1% == 8 (
set /a cb=-2
set /a wk_num=%wk_num% - 80000 + 100000
) else (
set /a cb=%wk_num:~1,1%
set /a wk_num=%wk_num% - %wk_num:~0,2% * 10000 + 100000
)
)
set wk_num=%wk_num:~2,4%
set s=%wk_num%%s%
goto :EOF
REM 表示形式の変換
:complement9
set sign=
if %s:~0,1% == 9 goto :nine
if %s:~0,1% == 0 goto :zero
set add=%s%
goto :EOF
:nine
set sign=-
set sol=
for /l %%I in (0,1,31) do call :set_sol %%I
set s=%sol%
:zero
set s=%s%.
:loop_zero
if %s:~0,1% == 0 set s=%s:~1%& goto loop_zero
set s=%s:~0,-1%
if "%s%" == "" set s=0
set add=%sign%%s%
goto :EOF
REM 補数を通常数に変換
:set_sol
set /a si=%1 * 4
call set /a wk_num = 9999 - %%s:~%si%,4%%
if %1 == 31 set /a wk_num = %wk_num% + 1
set wk_num=0000%wk_num%
set sol=%sol%%wk_num:~-4%
goto :EOF
ページトップへ戻る
引き算(減算)も負数を扱うと足し算(加算)とほぼ同じプログラムになります。(足し算の記述と見比べてください。)
@title 多桁計算(64桁まで)
@echo off
REM ----+----1----+----2----+----3----+----4----+----5----+----6----
set sp=0000000000000000000000000000000000000000000000000000000000000000
set sp=0000000000000000000000000000000000000000000000000000000000000000%sp%
set /p na=数字1:
set /p nb=数字2:
call :set_num a na
call :set_num b nb
echo.
REM 減算
set s=
set /a cb=0
call :sub
echo 減算の結果:%sub%
pause
echo on
@goto :EOF
REM -------------------------ここからサブルーチン
REM 数値を4桁づつのブロックに分割
:set_num
set arg=%2
call set arg=%%%arg%%%
set sign%1=
if "%arg:~0,1%" == "-" set arg=%arg:~1%&set sign%1=-
set wk_num=%sp%%arg%
set wk_num=%wk_num:~-128%
for /l %%I in (0, 1, 31) do call :num_div %1 %%I
goto :EOF
:num_div
set /a %1[%2]=1%wk_num:~-4% - 10000
set wk_num=%wk_num:~0,-4%
goto :EOF
REM 引き算
:sub
for /l %%I in (0,1,31) do call :subtract %%I
call :complement9
goto :EOF
:subtract
call set /a wk_num=(%signa%%%a[%1]%%) - (%signb%%%b[%1]%%) + (%cb%) + 100000
rem call echo set /a %wk_num%=(%signa%%%a[%1]%%) - (%signb%%%b[%1]%%) + (%cb%) + 100000
if %wk_num:~0,1% == 9 (
set /a cb=-1
set /a wk_num=%wk_num% - 90000 + 100000
) else (
if %wk_num:~0,1% == 8 (
set /a cb=-2
set /a wk_num=%wk_num% - 80000 + 100000
) else (
set /a cb=%wk_num:~1,1%
set /a wk_num=%wk_num% - %wk_num:~0,2%0000 + 100000
)
)
rem echo wk_num=%wk_num:~2,4%
set wk_num=%wk_num:~2,4%
set s=%wk_num%%s%
rem echo %s%
goto :EOF
REM 表示形式の変換
:complement9
set sign=
if %s:~0,1% == 9 goto :nine
if %s:~0,1% == 0 goto :zero
set sub=%sol%
goto :EOF
:nine
set sign=-
set sol=
for /l %%I in (0,1,31) do call :set_sol %%I
set s=%sol%
:zero
set s=%s%.
:loop_zero
if %s:~0,1% == 0 set s=%s:~1%& goto loop_zero
set s=%s:~0,-1%
if "%s%" == "" set s=0
set sub=%sign%%s%
goto :EOF
REM 補数を通常数に変換
:set_sol
set /a si=%1 * 4
call set /a wk_num = 9999 - %%s:~%si%,4%%
if %1 == 31 set /a wk_num = %wk_num% + 1
set wk_num=0000%wk_num%
set sol=%sol%%wk_num:~-4%
goto :EOF
ページトップへ戻る
筆算と同じ計算方法を採っているので比較的理解しやすいと思います。
計算を全ブロックで行っているため少し遅いです。
計算中のイライラを避けるために、計算中は「.」を表示するようにしてあります。
@title 多桁計算(64桁まで)
@echo off
REM ----+----1----+----2----+----3----+----4----+----5----+----6----
set sp=0000000000000000000000000000000000000000000000000000000000000000
set sp=0000000000000000000000000000000000000000000000000000000000000000%sp%
set /p na=数字1:
set /p nb=数字2:
call :set_num a na
call :set_num b nb
echo.
REM 乗算
set s=
set /a cb=0
call :mult
echo 乗算の結果:%mult%
echo on
@exit /b
REM -------------------------ここからサブルーチン
REM 数値の分割
:set_num
set arg=%2
call set arg=%%%arg%%%
set sign%1=
if "%arg:~0,1%" == "-" set arg=%arg:~1%&set sign%1=-
set wk_num=%sp%%arg%
set wk_num=%wk_num:~-128%
for /l %%I in (0, 1, 31) do call :num_div %1 %%I
goto :EOF
:num_div
set /a %1[%2]=1%wk_num:~-4% - 10000
set wk_num=%wk_num:~0,-4%
goto :EOF
REM 掛け算
:mult
for /l %%I in (0, 1, 31) do set temp[%%I]=0
set /p dmy=掛け算の計算中.<nul
for /l %%I in (0, 1, 31) do call :multiply %%I
set s=
for /l %%I in (0, 1, 31) do call :mult_s %%I
set s=%s%.
:mult_loop_zero
if %s:~0,1% == 0 set s=%s:~1%& goto mult_loop_zero
set s=%s:~0,-1%
if "%s%" == "" set s=0
set /a sign = %signa%1 * %signb%1
if %sign% LSS 0 set s=-%s%
echo 計算終了
set mult=%s%
goto :EOF
REM 掛け算の結果を文字列に変換します。
:mult_s
call set /a temp[%1]=10000+%%temp[%1]%%
call set s=%%temp[%1]:~1%%%s%
goto :EOF
REM 掛け算を行います。
:multiply
set /a i=0
set /a cb=0
:multiply_loop_01
set /a pos=%i%+%1
call set /a wk_num=%%a[%i%]%% * %%b[%1]%% + %cb% + %%temp[%pos%]%%
set /a cb=%wk_num%/10000
set /a temp[%pos%]=%wk_num%-%cb%*10000
set /a i+=1
if %i% LSS 16 goto multiply_loop_01
set /p dmy=.
goto :EOF
ページトップへ戻る
四則計算の中で最も難易度の高い計算です。
「被除数(a)÷除数(b)=商(q)・・・余り(r)の計算結果を求めます。
アルゴリズムは最も簡単な部類である、除数を2倍する操作を繰り返して被除数を超えない最大の倍数を引いていきます。
「除数を2倍する操作」と「被除数と除数の倍数の比較」、「被除数から除数の倍数を引く操作」が繰り返されるため非常に計算時間がかかります。
Celeron2.4G、メモリ512MB(うちフリー126MB)、WindowsXPの環境で 12345678987654321 ÷ 111111111 = 111111111 ・・・ 0 の計算に344秒かかりました。
桁数、ブロック数の調整を行えば、もう少し処理時間も改善できると思います。
@title 多桁計算(64桁まで)
@echo off
REM ----+----1----+----2----+----3----+----4----+----5----+----6----
set sp=0000000000000000000000000000000000000000000000000000000000000000
set sp=0000000000000000000000000000000000000000000000000000000000000000%sp%
set /p na=数字1:
set /p nb=数字2:
rem pause
call :set_num a na
call :set_num b nb
echo.
rem set /a tm0=%time:~0,2%*3600+%time:~3,2%*60+%time:~6,2%
REM 除算
call :divid
rem set /a tm1=%time:~0,2%*3600+%time:~3,2%*60+%time:~6,2%
rem set /a tm=tm1-tm0
echo 除算の結果:%divid% 余り:%remainder%
rem echo 計算時間:%tm% [秒]
pause
echo on
@goto :EOF
REM -------------------------ここからサブルーチン
REM 数値の分割
:set_num
set arg=%2
call set arg=%%%arg%%%
set sign%1=
if "%arg:~0,1%" == "-" set arg=%arg:~1%&set sign%1=-
set wk_num=%sp%%arg%
set wk_num=%wk_num:~-128%
for /l %%I in (0, 1, 31) do call :num_div %1 %%I
goto :EOF
:num_div
set /a %1[%2]=1%wk_num:~-4% - 10000
set wk_num=%wk_num:~0,-4%
goto :EOF
REM 割り算
:divid
REM a[]:数値a, b[]:数値b
REM k[]:数値aの作業変数, w[]:数値bの作業変数(これを2倍づつ増加させてk[]と比較します。)
REM c[]:数値aを超えない数値bの倍数(w[]の1/2)を格納しておきます。
REM q[]:数値bの倍率を求めます。
REM qq[]:数値aを超えない数値bの倍率を記録しておきます。(q[]の1/2)
REM temp[]:数値qq[]の合計を記録します。(これが商の値となります。)
REM q:w[]を2倍した回数を記録します。
REM kk:k[i]の数値を入れてwwと比較します。, ww:w[i]の数値を入れてkkと比較します。
REM dmy:ダミー変数で、一時的な作業に使用します。
REM cb:各種計算の桁上がりや桁借りを入れておきます。
REM wk_num:計算の途中経過を一時的に格納する変数です。
REM signa:数値aの符号, signb:数値bの符号, sign:計算結果の符号
REM i:繰り返し回数をカウントするための変数です。
REM s:計算結果を表示します。
REM r:余りを表示します。
:divid_label1
set /p dmy=割り算の計算中.<nul
set s=
for /l %%I in (0,1,31) do call set k[%%I]=%%a[%%I]%%
for /l %%I in (0,1,31) do call set w[%%I]=%%b[%%I]%%
for /l %%I in (0,1,31) do set q[%%I]=0&set qq[%%I]=0
for /l %%I in (0,1,31) do set temp[%%I]=0
set q[0]=1&set qq[0]=1
set q=0
set k[-1]=1
set w[-1]=0
set i=32
:divid_label2
set /a i-=1
call set kk=%%k[%i%]%%
call set /a ww=%%w[%i%]%%
if %ww% EQU %kk% goto divid_label2
set /p dmy=.<nul
if %ww% LSS %kk% call :doubler&goto divid_label2
if %q% LSS 1 goto divid_label3
set cb=0
for /l %%I in (0,1,31) do call :divid_temp %%I
set q[0]=1
call :subtracter
for /l %%I in (0,1,31) do call set w[%%I]=%%b[%%I]%%
set i=32
set q=0
goto divid_label2
:divid_label3
call :remainder
for /l %%I in (0, 1, 31) do call :divid_s %%I
set s=%s%.
:divid_loop_zero2
if %s:~0,1% == 0 set s=%s:~1%& goto divid_loop_zero2
set s=%s:~0,-1%
if "%s%" == "" set s=0
set /a sign = %signa%1 * %signb%1
if %sign% LSS 0 set s=-%s%
echo 計算終了
set divid=%s%
set remainder=%r%
goto :EOF
REM 倍率の集計を行います。
:divid_temp
call set /a wk_num = %%temp[%1]%% + %%qq[%1]%% + %cb%
set /a cb=%wk_num%/10000
set /a temp[%1]=%wk_num%-%cb%*10000
set q[%1]=0
goto :EOF
REM 割り算結果(商)の表示形式を調整します。
:divid_s
call set /a wk_num = %%temp[%1]%% + 10000
set s=%wk_num:~1,4%%s%
goto :EOF
REM 除数を2倍します。
:doubler
set cb=0
set i=0
:doubler_loop_01
call set c[%i%]=%%w[%i%]%%
call set /a w[%i%]=%%c[%i%]%% + %%c[%i%]%% + %cb%
call set /a cb=%%w[%i%]%%/10000
call set /a w[%i%]=%%w[%i%]%% - %cb% * 10000
set /a i+=1
if %i% LSS 32 goto doubler_loop_01
set cb=0
set /a q+=1
for /l %%I in (0,1,31) do call :calc_q %%I
set i=32
goto :EOF
REM 倍率を計算します。
:calc_q
call set qq[%1]=%%q[%1]%%
call set /a wk_num=2*%%q[%1]%%+%cb%
set /a cb=%wk_num%/10000
set /a q[%1]=%wk_num%-%cb%*10000
goto :EOF
REM 被除数から除数の倍数を引きます。
:subtracter
set /a i=0
set cb=0
:subtracter_loop
call set /a ww=%%k[%i%]%%-%%c[%i%]%%-%cb%
if %ww% LSS 0 (
set /a ww=10000+%ww%
set /a cb=1
) else (
set cb=0
)
set k[%i%]=%ww%
set /a i+=1
if %i% LSS 32 goto subtracter_loop
goto :EOF
REM 余りを求めます。
:remainder
set r=
for /l %%I in (0, 1, 31) do call set /a k[%%I]=%%k[%%I]%%+10000
set i=0
:remainder_loop
call set r=%%k[%i%]:~1%%%r%
set /a i+=1
if %i% LSS 32 goto remainder_loop
set r=%r%.
:remainder_loop_zero
if %r:~0,1% == 0 set r=%r:~1%& goto remainder_loop_zero
set r=%r:~0,-1%
if "%r%" == "" set r=0
goto :EOF
ページトップへ戻る