バッチファイルでProject Euler(28)

べき乗は、ふつうはバイナリ法を使うと速くなるのですが、バッチファイルの場合は事情が変わってきます。バッチファイルではforループが相対的に速いため単純に一つずつ掛けていくのが速くなりやすいのです。

@echo off

set /a N = 100
set /a e = 128
call :start_time
for /L %%i in (1, 1, %N%) do call :pow1 2 %e% 3
echo %ERRORLEVEL%
call :check_time

call :start_time
for /L %%i in (1, 1, %N%) do call :pow2 2 %e% 3
echo %ERRORLEVEL%
call :check_time

rem // 単純法
:pow1
    setlocal
    set /a m = 1
    for /L %%i in (1, 1, %2) do (
        set /a m *= %1
        set /a m %%= %3
    )
    exit /b %m%

rem // バイナリ法
:pow2
    setlocal
    if %2 == 0 exit /b 1
    set /a half_e = %2 / 2
    call :pow2 %1 %half_e% %3
    set /a m = %ERRORLEVEL%
    set /a is_odd = %2 %% 2
    if %is_odd% == 1 (
        set /a s = %m% * %m% * %1 %% %3
    ) else (
        set /a s = %m% * %m% %% %3
    )
    exit /b %s%

rem // 以下略
指数 単純法 バイナリ法
4 0.25s 1.32s
8 0.28s 1.71s
16 0.42s 2.08s
32 0.55s 2.47s
64 0.81s 2.88s
128 1.36s 3.22s
256 2.33s 3.64s
512 4.49s 4.14s
1024 8.81s 4.56s

ここでは500くらいに分岐点があるようです。
大きい指数の場合は途中までバイナリ法を使い、指数が小さくなったら単純法を使えばよいでしょう。64を分岐点にすると倍くらい速くなるようです。しかし、あまり速くはならないですね。

@echo off

set /a N = 100
set /a e = 1024
call :start_time
for /L %%i in (1, 1, %N%) do call :pow 2 %e% 3
echo %ERRORLEVEL%
call :check_time
exit /b 0

:pow
    setlocal
    if %2 == 0 exit /b 1
    set /a half_e = %2 / 2
    if %half_e% GTR 64 (
        call :pow %1 %half_e% %3
    ) else (
        call :pow_simple %1 %half_e% %3
    )
    set /a m = %ERRORLEVEL%
    set /a is_odd = %2 %% 2
    if %is_odd% == 1 (
        set /a s = %m% * %m% * %1 %% %3
    ) else (
        set /a s = %m% * %m% %% %3
    )
    exit /b %s%

:pow_simple
    setlocal
    set /a m = 1
    for /L %%i in (1, 1, %2) do (
        set /a m *= %1
        set /a m %%= %3
    )
    exit /b %m%

:start_time
    call :get_time
    set /a __t0 = %ERRORLEVEL%
    exit /b 0

:check_time
    setlocal
    call :get_time
    set /a t = %ERRORLEVEL% - %__t0%
    if %t% LSS 10 (
        echo 0.0%t%s
    ) else (
        if %t% LSS 100 (
            echo 0.%t%s
        ) else (
            echo %t:~0,-2%.%t:~-2%s
        )
    )
    exit /b 0

:get_time
    setlocal
    set t=%TIME%
    set /a h = %t:~0,2%
    set /a m = 1%t:~3,2% %% 100
    set /a s = 1%t:~6,2% %% 100
    set /a ss = 1%t:~-2% %% 100
    set /a ret = ((%h% * 60 + %m%) * 60 + %s%) * 100 + %ss%
    exit /b %ret%