バッチファイルで浮動小数点数演算(1)

バッチファイルでは本来整数演算しかできないのですが、IEEE754の単精度をエミュレートすることにより浮動小数点数演算を実現します。Project Euler 25が解ければいいので、厳密にエミュレートはしません。

IEEE754の単精度は、簡単に言うと仮数部を23ビット、指数部を8ビット、符号を1ビットで表します。これをつなげて32ビットの整数とします。例えば、リンク先の0.15625は0x3E200000となります。整数とすることで、exit /bで関数から値を返すことができます。そうするとsetlocalが使えて安全にプログラミングすることができます。具体的には、例えば加算なら、

:add_float
    ...
    exit /b %n%

と2つの引数を整数で取って、1つの整数を返すようにします。

まず、エンコード/デコードを書きます。(符号付き整数, 指数) → 浮動小数点数エンコード、逆をデコードとします。さきほどの例なら、(0x200000, -3) → 0x3E200000をエンコードと呼びます。
エンコードは2つ引数を取って1つ値を返します。しかし、デコードは2つ値を返すわけにはいかないので、setlocalは使わずに、sig*, exp*という環境変数を作ってそれを呼び元でも使うようにします。

call :decode_float %n% 1

とすれば、デコードした値はsig1とexp1に入ってきますし、

call :decode_float %n%

とすればsigとexpになります。ちなみに、sigはsignificand(仮数部)の略です。
下のコードは5を浮動小数点にし、再び整数化(切捨て)するだけのものです。

@echo off

setlocal enabledelayedexpansion
call :int2float 5
call :float2int %ERRORLEVEL%
echo %ERRORLEVEL%
exit /b 0

:int2float
    setlocal
    call :encode_float %1 24
    exit /b %ERRORLEVEL%

:float2int
    setlocal
    call :decode_float %1
    if %exp% LSS 24 (
        set /a n = "sig >> (24 - exp)"
    ) else (
        set /a n = "sig << (exp - 24)"
    )
    exit /b %n%

:encode_float
    setlocal
    if %1 == 0 exit /b 0
    if %1 GTR 0 (
        set /a sig = %1
    ) else (
        set /a sig = -%1
    )
    set /a sign = "%1 & 0x80000000"
    set /a e = %2
    :loop_encode_float
        if %sig% GEQ 0x1000000 (
            set /a "sig >>= 1"
            set /a e += 1
            goto :loop_encode_float
        )
        if %sig% LSS 0x800000 (
            set /a "sig <<= 1"
            set /a e -= 1
            goto :loop_encode_float
        )
    
    set /a sig -= 0x800000
    set /a code = "sig | ((e + 127) << 23) | sign"
    exit /b %code%

:decode_float
    set /a sig%2 = "(%1 & 0x7FFFFF) + 0x800000"
    set /a exp%2 = "((%1 >> 23) & 0xFF) - 127"
    set /a sign = "%1 >> 31"
    if %sign% == -1 set /a sig%2 = "-sig%2"
    exit /b 0