Batch macro vs function call test

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Batch macro vs function call test

#1 Post by carlsomo » 28 Jul 2013 17:01

I modified Don Cross' Pi.bat with function calls and replaced the 6 easy ones with macros.
The remaining functions contain loops or are not called till the end of the program.

It uses the formula:
π = 48*arctan(1/18) + 32*arctan(1/57) - 20*arctan(1/239)
and calculates pi to 108 decimal places.

Time trials on my Win7 laptop (64 bit Intel dual core i3 @ 2.10 GHz):

Function Version Average(8): 5.81375 secs
Macros not loaded Avg time: 4.65125 secs
. Difference: 1.16250 secs
Macros loaded Average time: 4.63625 secs
. Difference: 0.015 secs

I didn't see much baggage to loading the macros at routine startup vs pre loading in this case.
Pi.bat will accept an optional Arg1 to load the result into, then all display is suppressed.
Macro version has a 'load' option to run once and put macros into environment for future runs.

Code: Select all

::pi_function_version.bat
@echo off
setlocal EnableDelayedExpansion
if not [%1]==[] (set GNL=^>nul) else (set GNL=)
echo.pi.bat  -  By Don Cross  -  http://cosinekitty.com%GNL%
set /a NumQuads = 30
set /a MaxQuadIndex = NumQuads - 1
echo.%GNL%
echo.%time% - started%GNL%
echo.%GNL%

call :PiEngine 48 18 32 57 -20 239 tempi
::call :PiEngine 16 5 -4 239 tempi
endlocal&if not [%1]==[] call set "%1=3%%%tempi%%%"
exit /b 0

:PiEngine
    call :SetToInteger Pi 0
    set Formula=
    :PiTermLoop
        if "%1" == "tempi" (
            call :Print pi %1
            echo.%GNL%
            echo.!time! - finished !Formula!%GNL%
            echo.%GNL%
            goto :EOF
        )
        call :ArctanRecip PiTerm %2
        set /a PiEngineFactor=%1
        if !PiEngineFactor! lss 0 (
            set /a PiEngineFactor *= -1
            set Formula=!Formula!
            call :MultiplyByInteger PiTerm !PiEngineFactor!
            call :Subtract Pi PiTerm
            set Operator=-
        ) else (
            call :MultiplyByInteger PiTerm %1
            call :Add Pi PiTerm
            set Operator=+
        )
        if defined Formula (
            set Formula=!Formula! !Operator! !PiEngineFactor!*arctan^(1/%2^)
        ) else (
            set Formula=ã = %1*arctan^(1/%2^)
        )
        shift
        shift
    goto PiTermLoop
   
:SetToInteger
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a %1_%%i = 0
    )
    set /a %1_!MaxQuadIndex! = %2
    goto :EOF
   
:Print
    set PrintBuffer=x
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = MaxQuadIndex - 1
    for /L %%i in (!PrintMinQuadIndex!, 1, !PrintMaxQuadIndex!) do (
        set PrintDigit=!%1_%%i!
        if !PrintDigit! lss 1000 (
            if !PrintDigit! lss 100 (
                if !PrintDigit! lss 10 (
                    set PrintDigit=000!PrintDigit!
                ) else (
                    set PrintDigit=00!PrintDigit!
                )
            ) else (
                set PrintDigit=0!PrintDigit!
            )
        )
        set PrintBuffer=!PrintDigit!!PrintBuffer!
    )
    set PrintBuffer=!%1_%MaxQuadIndex%!.!PrintBuffer:x=!
    echo.|(set /p=ã = )%GNL%&echo.!PrintBuffer! %GNL%
    set "%2=!PrintBuffer!"
    goto :EOF
   
:DivideByInteger
    if defined PiDebug echo.DivideByInteger %1 %2
    set /a DBI_Carry = 0
    for /L %%i in (!MaxQuadIndex!, -1, 0) do (
        set /a DBI_Digit = DBI_Carry*10000 + %1_%%i
        set /a DBI_Carry = DBI_Digit %% %2
        set /a %1_%%i = DBI_Digit / %2
    )
    goto :EOF
   
:MultiplyByInteger
    if defined PiDebug echo.MultiplyByInteger %1 %2
    set /a MBI_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a MBI_Digit = %1_%%i * %2 + MBI_Carry
        set /a %1_%%i = MBI_Digit %% 10000
        set /a MBI_Carry = MBI_Digit / 10000
    )
    goto :EOF   
   
:ArctanRecip
    if defined PiDebug echo.ArctanRecip %1 %2
    call :SetToInteger %1 1
    call :DivideByInteger %1 %2
    call :CopyValue AR_Recip %1
    set /a AR_Toggle = -1   
    set /a AR_K = 3
    :ArctanLoop
        if defined PiDebug (
            echo.
            echo.ArctanRecip  AR_K=!AR_K!    ---------------------------------------------------------
        )
        call :DivideByInteger AR_Recip %2
        call :DivideByInteger AR_Recip %2
        call :CopyValue AR_Term AR_Recip
        call :DivideByInteger AR_Term !AR_K!
        call :CopyValue AR_PrevSum %1
        if !AR_Toggle! lss 0 (
            call :Subtract %1 AR_Term
        ) else (
            call :Add %1 AR_Term
        )
        call :Compare AR_EqualFlag %1 AR_PrevSum
        if !AR_EqualFlag! == true goto :EOF
        set /a AR_K += 2
        set /a AR_Toggle *= -1
    goto ArctanLoop
   
:CopyValue
    if defined PiDebug echo.CopyValue %1 %2
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a %1_%%i = %2_%%i
    )
    goto :EOF
   
:Add
    if defined PiDebug echo.Add %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Add_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a Add_Digit = Add_Carry + %1_%%i + %2_%%i
        set /a %1_%%i = Add_Digit %% 10000
        set /a Add_Carry = Add_Digit / 10000
    )
    goto :EOF   

:Subtract
    if defined PiDebug echo.Subtract %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Subtract_Borrow = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a Subtract_Digit = %1_%%i - %2_%%i - Subtract_Borrow
        if !Subtract_Digit! lss 0 (
            set /a Subtract_Digit += 10000
            set /a Subtract_Borrow = 1
        ) else (
            set /a Subtract_Borrow = 0
        )
        set /a %1_%%i = Subtract_Digit
    )
    goto :EOF   
   
:Compare
    if defined PiDebug echo.Compare %1 %2 %3
    if defined PiDebug call :Print %2
    if defined PiDebug call :Print %3
    set /a Compare_Index = 0
    set %1=true
    :CompareLoop
        if not !%2_%Compare_Index%! == !%3_%Compare_Index%! (
            if defined PiDebug echo.!%2_%Compare_Index%! neq !%3_%Compare_Index%!
            set %1=false
            goto :EOF
        )
        set /a Compare_Index += 1
        if !Compare_Index! gtr !MaxQuadIndex! (
            if defined PiDebug echo.Compare equal
            goto :EOF
        )
    goto CompareLoop   
   
REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM


Here is the macro version:

Code: Select all

::pi_macro_version.bat
@echo off
if /i "%~1" equ "load" goto :Pi_Macros
setlocal DisableDelayedExpansion
if not defined Pi_Macros_Loaded call :Pi_Macros
setlocal EnableDelayedExpansion
if not [%1]==[] (set GNL=^>nul) else (set GNL=)
echo.pi.bat  -  By Don Cross  -  http://cosinekitty.com%GNL%
echo.%GNL%
echo.%time% - started%GNL%
echo.%GNL%
set pidebug=
set /a NumQuads = 30
set /a MaxQuadIndex = NumQuads - 1

call :PiEngine 48 18 32 57 -20 239 tempi
::call :PiEngine 16 5 -4 239 tempi
endlocal&endlocal&if not [%1]==[] call set "%1=3%%%tempi%%%"
exit /b 0

:PiEngine
    %SetToInteger% Pi 0 MaxQuadIndex
    set Formula=
    :PiTermLoop
        if "%1" == "tempi" (
            call :Print pi %1
            echo.%GNL%
            echo.!time! - finished !Formula!%GNL%
            echo.%GNL%
            goto :EOF
        )
        call :ArctanRecip PiTerm %2
        set /a PiEngineFactor=%1
        if !PiEngineFactor! lss 0 (
            set /a PiEngineFactor *= -1
            set Formula=!Formula!
            %MultiplyByInteger% PiTerm !PiEngineFactor! MaxQuadIndex
            %Subtract% Pi PiTerm MaxQuadIndex
            set Operator=-
        ) else (
            %MultiplyByInteger% PiTerm %1 MaxQuadIndex
            %Add% Pi PiTerm MaxQuadIndex
            set Operator=+
        )
        if defined Formula (
            set Formula=!Formula! !Operator! !PiEngineFactor!*arctan^(1/%2^)
        ) else (
            set Formula=ã = %1*arctan^(1/%2^)
        )
        shift
        shift
    goto PiTermLoop
   
:ArctanRecip
    if defined PiDebug echo.ArctanRecip %1 %2
    %SetToInteger% %1 1 MaxQuadIndex
    %DivideByInteger% %1 %2 MaxQuadIndex
    %CopyValue% AR_Recip %1 MaxQuadIndex
    set /a AR_Toggle = -1   
    set /a AR_K = 3
    :ArctanLoop
        if defined PiDebug (
            echo.
            echo.ArctanRecip  AR_K=!AR_K!    ---------------------------------------------------------
        )
        %DivideByInteger% AR_Recip %2 MaxQuadIndex
        %DivideByInteger% AR_Recip %2 MaxQuadIndex
        %CopyValue% AR_Term AR_Recip MaxQuadIndex
        %DivideByInteger% AR_Term !AR_K! MaxQuadIndex
        %CopyValue% AR_PrevSum %1 MaxQuadIndex
        if !AR_Toggle! lss 0 (
            %Subtract% %1 AR_Term MaxQuadIndex
        ) else (
            %Add% %1 AR_Term MaxQuadIndex
        )
        call :Compare AR_EqualFlag %1 AR_PrevSum
        if !AR_EqualFlag! == true goto :EOF
        set /a AR_K += 2
        set /a AR_Toggle *= -1
    goto ArctanLoop
   
:Compare
    if defined PiDebug echo.Compare %1 %2 %3
    if defined PiDebug call :Print %2
    if defined PiDebug call :Print %3
    set /a Compare_Index = 0
    set %1=true
    :CompareLoop
        if not !%2_%Compare_Index%! == !%3_%Compare_Index%! (
            if defined PiDebug echo.!%2_%Compare_Index%! neq !%3_%Compare_Index%!
            set %1=false
            goto :EOF
        )
        set /a Compare_Index += 1
        if !Compare_Index! gtr !MaxQuadIndex! (
            if defined PiDebug echo.Compare equal
            goto :EOF
        )
    goto CompareLoop   

:Print
    set PrintBuffer=x
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = MaxQuadIndex - 1
    for /L %%i in (!PrintMinQuadIndex!, 1, !PrintMaxQuadIndex!) do (
        set PrintDigit=!%1_%%i!
        if !PrintDigit! lss 1000 (
            if !PrintDigit! lss 100 (
                if !PrintDigit! lss 10 (
                    set PrintDigit=000!PrintDigit!
                ) else (
                    set PrintDigit=00!PrintDigit!
                )
            ) else (
                set PrintDigit=0!PrintDigit!
            )
        )
        set PrintBuffer=!PrintDigit!!PrintBuffer!
    )
    set PrintBuffer=!%1_%MaxQuadIndex%!.!PrintBuffer:x=!
    echo.|(set /p=ã = )%GNL%&echo.!PrintBuffer! %GNL%
    set "%2=!PrintBuffer!"
    goto :EOF

REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM

:Pi_Macros
::                     Pi Macros
::
:: Define a Carriage Return string, only useable as !#CR!
for /f %%a in ('copy /Z "%~dpf0" nul') do set "#CR=%%a"

:: Define a Line Feed (newline) string (normally only used as !#LF!)
set #LF=^


:: Above 2 blank lines are required - do not remove

:: Define a newline with line continuation
set ^"\n=^^^%#LF%%#LF%^%#LF%%#LF%^^"

:MultiplyByInteger
set MultiplyByInteger=for %%n in (1 2) do if %%n==2 (%\n%
  for /F "tokens=1,2,3 delims=, " %%1 in ("!args!") do (%\n%
    set /a "MBI_Carry = 0"%\n%
    for /L %%i in (0, 1, !%%~3!) do (%\n%
      set /a "MBI_Digit = %%~1_%%i * %%~2 + MBI_Carry"%\n%
      set /a "%%~1_%%i = MBI_Digit %% 10000"%\n%
      set /a "MBI_Carry = MBI_Digit / 10000"%\n%
    )%\n%
  )%\n%
) else set args=,

:Subtract %1 %2 MaxQuadIndex
set Subtract=for %%n in (1 2) do if %%n==2 (%\n%
  for /F "tokens=1,2,3 delims=, " %%1 in ("!args!") do (%\n%
    set /a "Subtract_Borrow = 0"%\n%
    for /L %%i in (0, 1, !%%~3!) do (%\n%
      set /a "Subtract_Digit = %%~1_%%i - %%~2_%%i - Subtract_Borrow"%\n%
      if !Subtract_Digit! lss 0 (%\n%
        set /a "Subtract_Digit += 10000"%\n%
        set /a "Subtract_Borrow = 1"%\n%
      ) else set /a "Subtract_Borrow = 0"%\n%
      set /a "%%~1_%%i = Subtract_Digit"%\n%
    )%\n%
  )%\n%
) else set args=,

:Add  %1 %2 MaxQuadIndex
set Add=for %%n in (1 2) do if %%n==2 (%\n%
  for /F "tokens=1,2,3 delims=, " %%1 in ("!args!") do (%\n%
    set /a "Add_Carry = 0"%\n%
    for /L %%i in (0, 1, !%%~3!) do (%\n%
      set /a "Add_Digit = Add_Carry + %%~1_%%i + %%~2_%%i"%\n%
      set /a "%%~1_%%i = Add_Digit %% 10000"%\n%
      set /a "Add_Carry = Add_Digit / 10000"%\n%
    )%\n%
  )%\n%
) else set args=,

:DivideByInteger %1 %2 MaxQuadIndex
set DivideByInteger=for %%n in (1 2) do if %%n==2 (%\n%
  for /F "tokens=1,2,3 delims=, " %%1 in ("!args!") do (%\n%
    set /a "DBI_Carry = 0"%\n%
    for /L %%i in (!%%~3!, -1, 0) do (%\n%
      set /a "DBI_Digit = DBI_Carry*10000 + %%~1_%%i"%\n%
      set /a "DBI_Carry = DBI_Digit %% %%~2"%\n%
      set /a "%%~1_%%i = DBI_Digit / %%~2"%\n%
    )%\n%
  )%\n%
) else set args=,

:SetToInteger %1 %2 MaxQuadIndex
set SetToInteger=for %%n in (1 2) do if %%n==2 (%\n%
  for /F "tokens=1,2,3 delims=, " %%1 in ("!args!") do (%\n%
    for /L %%i in (0, 1, !%%~3!) do (%\n%
      set /a "%%~1_%%i = 0"%\n%
    )%\n%
    set /a "%%~1_!%%~3! = %%~2"%\n%
  )%\n%
) else set args=,

:CopyValue  %1 %2 MaxQuadIndex
set CopyValue=for %%n in (1 2) do if %%n==2 (%\n%
  for /F "tokens=1,2,3 delims=, " %%1 in ("!args!") do (%\n%
    for /L %%i in (0, 1, !%%~3!) do (%\n%
      set /a "%%~1_%%i = %%~2_%%i"%\n%
    )%\n%
  )%\n%
) else set args=,

set/a Pi_Macros_Loaded=1

exit/b 0


I actually thought the macro version would be faster than it turned out and I am a bit surprised that preloading the macros didn't shave off much time on my pc.

Carl

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#2 Post by einstein1969 » 24 Feb 2016 05:43

The major problem for achieve better performance is reduce the number of call/goto.
Then there is a possibility to empy the environment and use this in clever mode (variable position, naming).

In the arctan function there is a while loop too (to implement).

Einstein1969

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#3 Post by einstein1969 » 01 Oct 2017 08:59

I post the original code too (not modified):

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
echo.pi.bat  -  By Don Cross  -  http://cosinekitty.com
set /a NumQuads = 30
set /a MaxQuadIndex = NumQuads - 1

echo.
echo.%time% - started
echo.

call :PiEngine 48 18 32 57 -20 239
call :PiEngine 16 5 -4 239
goto :EOF

:PiEngine
    call :SetToInteger Pi 0
    set Formula=
    :PiTermLoop
        if "%1" == "" (
            call :Print pi
            echo.
            echo.!time! - finished !Formula!
            echo.
            goto :EOF
        )
        call :ArctanRecip PiTerm %2
        set /a PiEngineFactor=%1
        if !PiEngineFactor! lss 0 (
            set /a PiEngineFactor *= -1
            set Formula=!Formula!
            call :MultiplyByInteger PiTerm !PiEngineFactor!
            call :Subtract Pi PiTerm
            set Operator=-
        ) else (
            call :MultiplyByInteger PiTerm %1
            call :Add Pi PiTerm
            set Operator=+
        )
        if defined Formula (
            set Formula=!Formula! !Operator! !PiEngineFactor!*arctan^(1/%2^)
        ) else (
            set Formula=pi = %1*arctan^(1/%2^)
        )
        shift
        shift
    goto PiTermLoop
   
:SetToInteger
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a %1_%%i = 0
    )
    set /a %1_!MaxQuadIndex! = %2
    goto :EOF
   
:Print
    set PrintBuffer=x
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = MaxQuadIndex - 1
    for /L %%i in (!PrintMinQuadIndex!, 1, !PrintMaxQuadIndex!) do (
        set PrintDigit=!%1_%%i!
        if !PrintDigit! lss 1000 (
            if !PrintDigit! lss 100 (
                if !PrintDigit! lss 10 (
                    set PrintDigit=000!PrintDigit!
                ) else (
                    set PrintDigit=00!PrintDigit!
                )
            ) else (
                set PrintDigit=0!PrintDigit!
            )
        )
        set PrintBuffer=!PrintDigit!!PrintBuffer!
    )
    set PrintBuffer=!%1_%MaxQuadIndex%!.!PrintBuffer:x=!
    echo.%1 = !PrintBuffer!
    goto :EOF
   
:DivideByInteger
    if defined PiDebug echo.DivideByInteger %1 %2
    set /a DBI_Carry = 0
    for /L %%i in (!MaxQuadIndex!, -1, 0) do (
        set /a DBI_Digit = DBI_Carry*10000 + %1_%%i
        set /a DBI_Carry = DBI_Digit %% %2
        set /a %1_%%i = DBI_Digit / %2
    )
    goto :EOF
   
:MultiplyByInteger
    if defined PiDebug echo.MultiplyByInteger %1 %2
    set /a MBI_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a MBI_Digit = %1_%%i * %2 + MBI_Carry
        set /a %1_%%i = MBI_Digit %% 10000
        set /a MBI_Carry = MBI_Digit / 10000
    )
    goto :EOF   
   
:ArctanRecip
    if defined PiDebug echo.ArctanRecip %1 %2
    call :SetToInteger %1 1
    call :DivideByInteger %1 %2
    call :CopyValue AR_Recip %1
    set /a AR_Toggle = -1   
    set /a AR_K = 3
    :ArctanLoop
        if defined PiDebug (
            echo.
            echo.ArctanRecip  AR_K=!AR_K!    ---------------------------------------------------------
        )
        call :DivideByInteger AR_Recip %2
        call :DivideByInteger AR_Recip %2
        call :CopyValue AR_Term AR_Recip
        call :DivideByInteger AR_Term !AR_K!
        call :CopyValue AR_PrevSum %1
        if !AR_Toggle! lss 0 (
            call :Subtract %1 AR_Term
        ) else (
            call :Add %1 AR_Term
        )
        call :Compare AR_EqualFlag %1 AR_PrevSum
        if !AR_EqualFlag! == true goto :EOF
        set /a AR_K += 2
        set /a AR_Toggle *= -1
    goto ArctanLoop
   
:CopyValue
    if defined PiDebug echo.CopyValue %1 %2
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a %1_%%i = %2_%%i
    )
    goto :EOF
   
:Add
    if defined PiDebug echo.Add %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Add_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a Add_Digit = Add_Carry + %1_%%i + %2_%%i
        set /a %1_%%i = Add_Digit %% 10000
        set /a Add_Carry = Add_Digit / 10000
    )
    goto :EOF   

:Subtract
    if defined PiDebug echo.Subtract %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Subtract_Borrow = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a Subtract_Digit = %1_%%i - %2_%%i - Subtract_Borrow
        if !Subtract_Digit! lss 0 (
            set /a Subtract_Digit += 10000
            set /a Subtract_Borrow = 1
        ) else (
            set /a Subtract_Borrow = 0
        )
        set /a %1_%%i = Subtract_Digit
    )
    goto :EOF   
   
:Compare
    if defined PiDebug echo.Compare %1 %2 %3
    if defined PiDebug call :Print %2
    if defined PiDebug call :Print %3
    set /a Compare_Index = 0
    set %1=true
    :CompareLoop
        if not !%2_%Compare_Index%! == !%3_%Compare_Index%! (
            if defined PiDebug echo.!%2_%Compare_Index%! neq !%3_%Compare_Index%!
            set %1=false
            goto :EOF
        )
        set /a Compare_Index += 1
        if !Compare_Index! gtr !MaxQuadIndex! (
            if defined PiDebug echo.Compare equal
            goto :EOF
        )
    goto CompareLoop   
   
REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM



edit :correct number of quad to 30

einstein1969

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#4 Post by einstein1969 » 07 Oct 2017 07:35

I have done some test.

I have changed the original pi with input the NUMQUADS parameter.

Then I have tested at 15,30,45,60,75 NUMQUADS.

The time is show in this table:

Code: Select all

Quads | Time
---------------
15        3 sec.   |||
30        8 sec.   ||||||||
45       18 sec.   ||||||||||||||||||
60       34 sec.   ||||||||||||||||||||||||||||||||||
75       56 sec.   ||||||||||||||||||||||||||||||||||||||||||||||||||||||||


The time is not linear!

The time with the macro version at 75 NUMQUADS is 53 sec.

I thinks that the problem is the SET /A with a large environment.

What do you think?

einstein1969

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Batch macro vs function call test

#5 Post by Aacini » 07 Oct 2017 08:10


einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#6 Post by einstein1969 » 07 Oct 2017 08:45

Thanks for the link.

What is the best method for optimize this?

I have profiled for 30 Numquads and the results are this:

Code: Select all

C:\Users\ACER\Desktop>pi.bat 30
pi.bat  -  By Don Cross  -  http://cosinekitty.com

NumQuads=30


16:38:44,18 - started

pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086

16:38:54,15 - finished pi = 48*arctan(1/18) + 32*arctan(1/57) - 20*arctan(1/239)

SetToInteger=4
DivideByInteger=309
MultiplyByInteger=3
ArctanRecip=99
CopyValue=207
Add=52
Subtract=53
Compare=87

pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086

16:39:05,03 - finished pi = 16*arctan(1/5) - 4*arctan(1/239)

SetToInteger=3
DivideByInteger=317
MultiplyByInteger=2
ArctanRecip=103
CopyValue=212
Add=53
Subtract=54
Compare=107



this is the code:

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
echo.pi.bat  -  By Don Cross  -  http://cosinekitty.com

rem Add input parameter NumQuads

if "%~1" == "" (set /a NumQuads = 30) else (set /a NumQuads=%~1)
if !NumQuads! EQU 0  set /a NumQuads = 30
echo.
echo.NumQuads=!NumQuads!
echo.
set /a MaxQuadIndex = NumQuads - 1

pushd %tmp%

echo.
echo.%time% - started
echo.

call :PiEngine 48 18 32 57 -20 239
call :PiEngine 16 5 -4 239

popd

goto :EOF

:PiEngine
    call :SetToInteger Pi 0
    set Formula=
    :PiTermLoop
        if "%1" == "" (
            call :Print pi
            echo.
            echo.!time! - finished !Formula!
            echo.
            echo SetToInteger=!SetToInteger!
            echo DivideByInteger=!DivideByInteger!
            echo MultiplyByInteger=!MultiplyByInteger!
            echo ArctanRecip=!ArctanRecip!
            echo CopyValue=!CopyValue!
            echo Add=!Add!
            echo Subtract=!Subtract!
            echo Compare=!Compare!
            echo.
            set /a SetToInteger=0, DivideByInteger=0, MultiplyByInteger=0, ArctanRecip=0
            set /a CopyValue=0, Add=0, Subtract=0, Compare=0
            goto :EOF
        )
        call :ArctanRecip PiTerm %2
        set /a PiEngineFactor=%1
        if !PiEngineFactor! lss 0 (
            set /a PiEngineFactor *= -1
            set Formula=!Formula!
            call :MultiplyByInteger PiTerm !PiEngineFactor!
            call :Subtract Pi PiTerm
            set Operator=-
        ) else (
            call :MultiplyByInteger PiTerm %1
            call :Add Pi PiTerm
            set Operator=+
        )
        if defined Formula (
            set Formula=!Formula! !Operator! !PiEngineFactor!*arctan^(1/%2^)
        ) else (
            set Formula=pi = %1*arctan^(1/%2^)
        )
        shift
        shift
    goto PiTermLoop
   
:SetToInteger
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a %1_%%i = 0
    )
    set /a %1_!MaxQuadIndex! = %2
    set /a SetToInteger+=1
    goto :EOF
   
:Print
    set PrintBuffer=x
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = MaxQuadIndex - 1
    for /L %%i in (!PrintMinQuadIndex!, 1, !PrintMaxQuadIndex!) do (
        set PrintDigit=!%1_%%i!
        if !PrintDigit! lss 1000 (
            if !PrintDigit! lss 100 (
                if !PrintDigit! lss 10 (
                    set PrintDigit=000!PrintDigit!
                ) else (
                    set PrintDigit=00!PrintDigit!
                )
            ) else (
                set PrintDigit=0!PrintDigit!
            )
        )
        set PrintBuffer=!PrintDigit!!PrintBuffer!
    )
    set PrintBuffer=!%1_%MaxQuadIndex%!.!PrintBuffer:x=!
    echo.%1 = !PrintBuffer!
    goto :EOF
   
:DivideByInteger
    if defined PiDebug echo.DivideByInteger %1 %2
    set /a DBI_Carry = 0
    for /L %%i in (!MaxQuadIndex!, -1, 0) do (
        set /a DBI_Digit = DBI_Carry*10000 + %1_%%i
        set /a DBI_Carry = DBI_Digit %% %2
        set /a %1_%%i = DBI_Digit / %2
    )
    set /a DivideByInteger+=1
    goto :EOF
   
:MultiplyByInteger
    if defined PiDebug echo.MultiplyByInteger %1 %2
    set /a MBI_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a MBI_Digit = %1_%%i * %2 + MBI_Carry
        set /a %1_%%i = MBI_Digit %% 10000
        set /a MBI_Carry = MBI_Digit / 10000
    )
    set /a MultiplyByInteger+=1
    goto :EOF   
   
:ArctanRecip
    if defined PiDebug echo.ArctanRecip %1 %2
    call :SetToInteger %1 1
    call :DivideByInteger %1 %2
    call :CopyValue AR_Recip %1
    set /a AR_Toggle = -1   
    set /a AR_K = 3
    :ArctanLoop
        if defined PiDebug (
            echo.
            echo.ArctanRecip  AR_K=!AR_K!    ---------------------------------------------------------
        )
        call :DivideByInteger AR_Recip %2
        call :DivideByInteger AR_Recip %2
        call :CopyValue AR_Term AR_Recip
        call :DivideByInteger AR_Term !AR_K!
        call :CopyValue AR_PrevSum %1
        if !AR_Toggle! lss 0 (
            call :Subtract %1 AR_Term
        ) else (
            call :Add %1 AR_Term
        )
        call :Compare AR_EqualFlag %1 AR_PrevSum
        if !AR_EqualFlag! == true goto :EOF
        set /a AR_K += 2
        set /a AR_Toggle *= -1
        set /a ArctanRecip+=1
    goto ArctanLoop
   
:CopyValue
    if defined PiDebug echo.CopyValue %1 %2
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a %1_%%i = %2_%%i
    )
    set /a CopyValue+=1
    goto :EOF
   
:Add
    if defined PiDebug echo.Add %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Add_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a Add_Digit = Add_Carry + %1_%%i + %2_%%i
        set /a %1_%%i = Add_Digit %% 10000
        set /a Add_Carry = Add_Digit / 10000
    )
    set /a Add+=1
    goto :EOF   

:Subtract
    if defined PiDebug echo.Subtract %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Subtract_Borrow = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a Subtract_Digit = %1_%%i - %2_%%i - Subtract_Borrow
        if !Subtract_Digit! lss 0 (
            set /a Subtract_Digit += 10000
            set /a Subtract_Borrow = 1
        ) else (
            set /a Subtract_Borrow = 0
        )
        set /a %1_%%i = Subtract_Digit
    )
    set /a Subtract+=1
    goto :EOF   
   
:Compare
    if defined PiDebug echo.Compare %1 %2 %3
    if defined PiDebug call :Print %2
    if defined PiDebug call :Print %3
    set /a Compare_Index = 0
    set %1=true
    :CompareLoop
        if not !%2_%Compare_Index%! == !%3_%Compare_Index%! (
            if defined PiDebug echo.!%2_%Compare_Index%! neq !%3_%Compare_Index%!
            set %1=false
            goto :EOF
        )
        set /a Compare_Index += 1
        if !Compare_Index! gtr !MaxQuadIndex! (
            if defined PiDebug echo.Compare equal
            goto :EOF
        )
    set /a Compare+=1
    goto CompareLoop   
   
REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM

einstein1969

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Batch macro vs function call test

#7 Post by Aacini » 08 Oct 2017 17:01

I completed several different modifications to the original code in this topic. After each modification was done a certain amount of running time was decreased, so after all modifications were combined, the final code decreased the original running time in 55%. This is a summary of the modifications:

  • Several small modifications that simplify the code and increase the efficiency, like the method used to insert up to 3 leading zeros to a value in : Print routine:

    Code: Select all

            set /A PrintDigit=10000 + %1_%%i

    ... instead of the 3 nested IF commands; just use set "PrintBuffer=!PrintDigit:~1!!PrintBuffer!" to insert the digit.
  • The equivalent result of all FOR iterative routines were expanded, each one into a long arithmetic expression that is stored in a (macro) variable, so each original CALL command (and the execution of the subroutine with a FOR command) was replaced by a SET /A command. The only exception was :Compare routine that was preserved as a loop, but it was simplified and modified to make it more efficient (via a FOR instead of GOTO) and embedded into the calling code in order to avoid the CALL.
  • This procedure:

    Code: Select all

        :ArctanLoop
            call :DivideByInteger AR_Recip %2               AR_Recip  /= %2
            call :DivideByInteger AR_Recip %2               AR_Recip  /= %2
            call :CopyValue AR_Term AR_Recip                AR_Term    = AR_Recip
            call :DivideByInteger AR_Term !AR_K!            AR_Term   /= AR_K
            call :CopyValue AR_PrevSum PiTerm               AR_PrevSum = PiTerm
            if !AR_Toggle! lss 0 (
                call :Subtract PiTerm AR_Term                   PiTerm -= AR_Term
            ) else (
                call :Add PiTerm AR_Term                        PiTerm += AR_Term
            )
            set /a AR_K += 2,  AR_Toggle *= -1
            call :Compare AR_EqualFlag PiTerm AR_PrevSum    IF PiTerm neq AR_PrevSum
        if !AR_EqualFlag! neq true goto ArctanLoop              goto ArctanLoop

    ... was replaced by this simpler one:

    Code: Select all

        :ArctanLoop
            call :DivideByInteger AR_Recip %2               AR_Recip  /= %2
            call :DivideByInteger AR_Recip %2               AR_Recip  /= %2
            call :CopyValue AR_Term AR_Recip                AR_Term    = AR_Recip
            call :DivideByInteger AR_Term !AR_K!            AR_Term   /= AR_K
            call :IsZero AR_IsZeroFlag AR_Term              IF AR_Term equ 0
            if !AR_IsZeroFlag! equ true exit /B                 exit /B
            if !AR_Toggle! lss 0 (
                call :Subtract PiTerm AR_Term                   PiTerm -= AR_Term
            ) else (
                call :Add PiTerm AR_Term                        PiTerm += AR_Term
            )
            set /a AR_K += 2,  AR_Toggle *= -1
        goto ArctanLoop                                     goto ArctanLoop

    This modification not only eliminates the call :CopyValue AR_PrevSum PiTerm command, but also eliminates the last Add/Subtract operation in the loop and one array variable from the environment, that is, a NumQuads number of variables with values with up to four digits each.
  • The environment was emptied before the process start.


This is the new code:

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
echo pi.bat  -  By Don Cross  -  http://cosinekitty.com

REM Empty the environment
for /F "delims==" %%v in ('set') do set "%%v="

set /a NumQuads = 30
set /a MaxQuadIndex = NumQuads - 1

call :CreateMacros
echo %time% - started
echo/

call :PiEngine 48 18 32 57 -20 239
call :PiEngine 16 5 -4 239
goto :EOF

:PiEngine
    set /A "%SetToInteger:var=Pi%=0"
    set "Formula="
    :PiTermLoop
        call :ArctanRecip PiTerm %2
        if %1 lss 0 (
            set /A "N=-%1,%MultiplyByInteger:var=PiTerm%"
            set /A "%Subtract_Pi:v2=PiTerm%"
            set Operator=-
        ) else (
            set /A "N=%1,%MultiplyByInteger:var=PiTerm%"
            set /A "%Add_Pi:v2=PiTerm%"
            set Operator=+
        )
        if not defined Formula (
            set "Formula=pi = %1*arctan(1/%2)"
        ) else (
            set "Formula=%Formula% %Operator% %N%*arctan(1/%2)"
        )
        shift
        shift
    if "%1" neq "" goto PiTermLoop
    call :Print Pi
    echo %time% - finished %Formula%
exit /B


:Print
    set "PrintBuffer="
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = MaxQuadIndex - 1
    for /L %%i in (%PrintMinQuadIndex%, 1, %PrintMaxQuadIndex%) do (
        set /A PrintDigit=10000 + %1%%i
        set "PrintBuffer=!PrintDigit:~1!!PrintBuffer!"
    )
    set PrintBuffer=!%1%MaxQuadIndex%!.%PrintBuffer%
    echo %1 = %PrintBuffer%
    echo/
exit /B


:ArctanRecip   %1 = PiTerm *always*
    set /A "%SetToInteger:var=PiTerm%=1"
    set /A "N=%2,%DivideByInteger:var=PiTerm%"
    set /A "%CopyValue_AR_Recip:v2=PiTerm%"
    set /a AR_Toggle = -1,  AR_K = 3
    :ArctanLoop
        set /A "N=%2,%DivideByInteger:var=AR_Recip%"
        set /A "N=%2,%DivideByInteger:var=AR_Recip%"
        set /A "%CopyValue_AR_Term:v2=AR_Recip%"
        set /A "N=AR_K,%DivideByInteger:var=AR_Term%"
        REM call :IsZero AR_IsZeroFlag AR_Term
        for /L %%i in (0, 1, %MaxQuadIndex%) do (
            if !AR_Term%%i! neq 0 goto :Accum_AR_Term
        )
        exit /B
        :Accum_AR_Term
        if !AR_Toggle! lss 0 (
            set /A "%Subtract_PiTerm:v2=AR_Term%"
        ) else (
            set /A "%Add_PiTerm:v2=AR_Term%"
        )
        set /a AR_K += 2, AR_Toggle *= -1
    goto ArctanLoop


REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM


:CreateMacros

    set "SetToInteger="
    for /L %%i in (0, 1, %MaxQuadIndex%) do (
        set "SetToInteger=!SetToInteger!var%%i="
    )
    set "SetToInteger=%SetToInteger%0,var%MaxQuadIndex%"

    set "MultiplyByInteger=F=10000,C=0"
    for /L %%i in (0, 1, %MaxQuadIndex%) do (
        set "MultiplyByInteger=!MultiplyByInteger!,D=C+var%%i*N,var%%i=D%%F,C=D/F"
    )

    set "DivideByInteger=F=10000,C=0"
    for /L %%i in (%MaxQuadIndex%, -1, 0) do (
        set "DivideByInteger=!DivideByInteger!,D=C*F+var%%i,C=D%%N,var%%i=D/N"
    )

    set "CopyValue=v10=v20"
    for /L %%i in (1, 1, %MaxQuadIndex%) do (
        set "CopyValue=!CopyValue!,v1%%i=v2%%i"
    )
    set "CopyValue_AR_Recip=%CopyValue:v1=AR_Recip%"
    set "CopyValue_AR_Term=%CopyValue:v1=AR_Term%"
    set "CopyValue="

    set "Add=F=10000,C=0"
    for /L %%i in (0, 1, %MaxQuadIndex%) do (
        set "Add=!Add!,D=C+v1%%i+v2%%i,v1%%i=D%%F,C=D/F"
    )
    set "Add_Pi=%Add:v1=Pi%"
    set "Add_PiTerm=%Add:v1=PiTerm%"
    set "Add="

    set "Subtract=F=10000,B=0"
    for /L %%i in (0, 1, %MaxQuadIndex%) do (
        set "Subtract=!Subtract!,D=v1%%i-v2%%i+B,B=D>>31,v1%%i=D-B*F"
    )
    set "Subtract_Pi=%Subtract:v1=Pi%"
    set "Subtract_PiTerm=%Subtract:v1=PiTerm%"
    set "Subtract="

exit /B

In my (old and cheap) computer the original program takes 38.16 seconds:

Code: Select all

pi.bat  -  By Don Cross  -  http://cosinekitty.com
15:37:47.18 - started

pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086

15:38:05.86 - finished pi = 48*arctan(1/18) + 32*arctan(1/57) - 20*arctan(1/239)

pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086

15:38:25.34 - finished pi = 16*arctan(1/5) - 4*arctan(1/239)

... but the modified code takes just 17.33 seconds:

Code: Select all

pi.bat  -  By Don Cross  -  http://cosinekitty.com
15:41:29.65 - started

Pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086

15:41:38.14 - finished pi = 48*arctan(1/18) + 32*arctan(1/57) - 20*arctan(1/239)

Pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086

15:41:46.98 - finished pi = 16*arctan(1/5) - 4*arctan(1/239)

This means that the modified code requires just the 45% of the original running time (or decreases the original time in 55%).

I completed several tests with the modified code varying the number of quads in the arrays; this is the result:

Code: Select all

Quads        Digits   Time in seconds

30           110      17.25
45 (1.5)     170      52.54  (3.05)
60 (2.0)     230      118.75 (6.88)
75 (2.5)     290      224.42 (13.01)
90 (3.0)     350      380.84 (22.08)

The non-linear increment of the running time vs. the number of quads is the expected one as described elsewhere.

A drawback that this code have is that the maximum number of quads that can be managed is limited by the 8191 characters that the expanded macro expression over all the array elements may have, so this number mainly depends on the length of the names used for the arrays.

The next step is modify the name of the array variables in a way that all of them have just one character, select the letter used for each array so the most frequently modified variables be towards the bottom of the environment, and pre-allocate enough environment space as described in my posts at this topic. I am pretty sure that these modifications may gain another (perhaps not so) small percentage of the running time that should be larger as the number of quads increase. As comparison, the "Empty Environment" modification alone caused to gain 13% in the running time!

Antonio

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#8 Post by einstein1969 » 10 Oct 2017 09:02

:shock:

very clever solution for minimize number of SET istruction.

The limit on 8192 is in also the variable PRINTBUFFER. It is possible eliminate this problem on variable PRINTBUFFER?

I'm working on a version that eliminate the use of variable. It use some files but is very slow at moment but the time is better for big NUMQUADS.

I thinks that is possible use both solutions for a ideal faster solution.

for example:
the code for subroutine DIVIDEBYINTEGER is like this

Code: Select all

:DivideByInteger
    if defined PiDebug echo.DivideByInteger %1 %2
    set /a DBI_Carry = 0
    for /L %%i in (!MaxQuadIndex!, -1, 0) do (
        if not defined %1_%%i (set/p "%1_%%i="<%1_%%i.txt)
        set /a DBI_Digit = DBI_Carry*10000 + %1_%%i
        set /a DBI_Carry = DBI_Digit %% %2
        set /a %1_%%i = DBI_Digit / %2
        echo.!%1_%%i!>%1_%%i.txt
        set "%1_%%i="
    )
    set "DBI_Carry="
    set "DBI_Digit="
    goto :EOF

I'm working on this method for big NUMQUAD.

Francesco
Last edited by einstein1969 on 11 Oct 2017 07:02, edited 1 time in total.

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#9 Post by einstein1969 » 11 Oct 2017 06:38

I have made some tests.

The original pi are better for low NUMQUAD, but the pi on file is faster on big NUMQUAD.

These are results. (Time in seconds)
QUADS
Image

The pi on file include major change token from AAcini version 8)

This is the version on file. EDIT: I have resolved the PRINTBUFFER problem.

the code:

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
echo.pi.bat  -  By Don Cross  -  http://cosinekitty.com

pushd %tmp%

rem empty the environment for faster execution
(
  for /F "Tokens=1 delims==" %%v in ('set') do set "%%v="
  set pidebug=%pidebug%
)

rem Add input parameter NumQuads

if "%~1" == "" (set /a NumQuads = 30) else (set /a NumQuads=%~1)
if !NumQuads! EQU 0  set /a NumQuads = 30
echo.
echo.NumQuads=!NumQuads!
echo.
set /a MaxQuadIndex = NumQuads - 1
set "NumQuads="

echo.
echo.%time% - started
echo.

rem Pi = 48*arctan(1/18) + 32*arctan(1/57) - 20*arctan(1/239)
call :PiEngine 48 18 32 57 -20 239

set _CC=0

rem Pi = 16*arctan(1/5) - 4*arctan(1/239)
call :PiEngine 16 5 -4 239

set _CC=0

rem pi = 176*arctan(1/57) + 28*arctan(1/239) - 48*arctan(1/682) + 96*arctan(1/12943)
call :PiEngine 176 57 28 239 -48 682 96 12943


popd

goto :EOF


:PiEngine
    call :SetToInteger Pi 0
    set "_Formula="
    :PiTermLoop
        call :ArctanRecip PiTerm %2
        set /a "N=(((%1)>>31|1)*(%1))" & rem N=ABS(%1)
        call :MultiplyByInteger PiTerm %N%
        if %1 lss 0 (
            call :Subtract Pi PiTerm
            set Operator=-
        ) else (
            call :Add Pi PiTerm
            set Operator=+
        )
        if defined _Formula (
            set "_Formula=%_Formula% %Operator% %N%*arctan(1/%2)"
        ) else (
            set "_Formula=pi = %1*arctan(1/%2)"
        )
        shift
        shift
    if "%1" neq "" goto PiTermLoop
    call :Print Pi
    echo.
    echo %time% - finished %_Formula%
    echo.
goto :EOF
   
:Print
    call :load %1
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = MaxQuadIndex - 1
    set /p "=%1 = !%1_%MaxQuadIndex%!." < nul:
    for /L %%i in (%PrintMaxQuadIndex%, -1, %PrintMinQuadIndex%) do (
        set /A PrintDigit=10000 + %1_%%i
        set /p "=!PrintDigit:~1!" < nul:
    )
    echo.
    call :unload %1
    set "PrintDigit="
    set "PrintMaxQuadIndex="
    set "PrintMinQuadIndex="
    goto :EOF
   
:SetToInteger
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        rem set /a %1_%%i = 0
        echo.0>%1_%%i.txt
    )
    set /a %1_%MaxQuadIndex% = %2
    echo.!%1_%MaxQuadIndex%!>%1_%MaxQuadIndex%.txt
    set "%1_%MaxQuadIndex%="
    goto :EOF
 
:DivideByInteger
    if defined PiDebug echo.DivideByInteger %1 %2
    rem C=Carry, D=Digit
    set /a C = 0
    for /L %%i in (!MaxQuadIndex!, -1, 0) do (
        if not defined %1_%%i (set/p "%1_%%i="<%1_%%i.txt)
        set /a D = C*10000 + %1_%%i, C = D %% %2, %1_%%i = D / %2
        echo.!%1_%%i!>%1_%%i.txt
        set "%1_%%i="
    )
    set /a _CC+=1, perc=_CC*100/(MaxQuadIndex*11)
    title %perc%%% (%_CC%) %_Formula% [%time:~0,-3%]
    set "C="
    set "D="
    set "perc="
    goto :EOF
   
:MultiplyByInteger
    if defined PiDebug echo.MultiplyByInteger %1 %2
    call :load %1
    set /a MBI_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a MBI_Digit = %1_%%i * %2 + MBI_Carry
        set /a %1_%%i = MBI_Digit %% 10000
        set /a MBI_Carry = MBI_Digit / 10000
    )
    call :unload %1
    set "MBI_Carry="
    set "MBI_Digit="
    goto :EOF   
   
:ArctanRecip
    if defined PiDebug echo.ArctanRecip %1 %2
    call :SetToInteger %1 1
    call :DivideByInteger %1 %2
    call :CopyValue AR_Recip %1
    set /a AR_Toggle = -1, AR_K = 3
    :ArctanLoop
        if defined PiDebug (
            echo.
            echo.ArctanRecip  AR_K=!AR_K!    ---------------------------------------------------------
        )
        call :DivideByInteger AR_Recip %2
        call :DivideByInteger AR_Recip %2
        call :CopyValue AR_Term AR_Recip
        call :DivideByInteger AR_Term !AR_K!
        REM call :IsZero AR_IsZeroFlag AR_Term
        for /L %%i in (0, 1, %MaxQuadIndex%) do (
            if not defined AR_Term_%%i (set/p "AR_Term_%%i="<AR_Term_%%i.txt)
            if !AR_Term_%%i! neq 0 (
               set AR_Term_%%i=
               goto :Accum_AR_Term
            ) else (
               set AR_Term_%%i=
            )
        )
        set "AR_K="
        set "AR_Toggle="
        exit /B
        :Accum_AR_Term

        if !AR_Toggle! lss 0 (
            call :Subtract %1 AR_Term
        ) else (
            call :Add %1 AR_Term
        )
        set /a AR_K += 2, AR_Toggle *= -1
    goto ArctanLoop
   
:CopyValue
    if defined PiDebug echo.CopyValue %1 %2
   
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        if not defined %2_%%i (set/p "%2_%%i="<%2_%%i.txt)
        rem set /a %1_%%i = %2_%%i
        echo.!%2_%%i!>%1_%%i.txt
        set "%2_%%i="
    )
    goto :EOF
   
:Add
    if defined PiDebug echo.Add %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Add_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        if not defined %1_%%i (set/p "%1_%%i="<%1_%%i.txt)
        if not defined %2_%%i (set/p "%2_%%i="<%2_%%i.txt)
        set /a Add_Digit = Add_Carry + %1_%%i + %2_%%i
        set /a %1_%%i = Add_Digit %% 10000
        set /a Add_Carry = Add_Digit / 10000
        echo.!%1_%%i!>%1_%%i.txt
        set "%1_%%i="
        set "%2_%%i="
    )
    set "Add_Carry="
    set "Add_Digit="
    goto :EOF   

:Subtract 
    if defined PiDebug echo.Subtract %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    rem B=Subtract_Borrow, D=Subtract_Digit
    set /a B = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        if not defined %1_%%i (set/p "%1_%%i="<%1_%%i.txt)
        if not defined %2_%%i (set/p "%2_%%i="<%2_%%i.txt)
        set /a "D = %1_%%i - %2_%%i + B, B=D>>31, %1_%%i=D-B*10000"
        echo.!%1_%%i!>%1_%%i.txt
        set "%1_%%i="
        set "%2_%%i="
    )
    set "B="
    set "D="
    goto :EOF

:load
   for /L %%i in (0,1,!MaxQuadIndex!) do (
     set/p "%1_%%i="<%1_%%i.txt
   )
   goto :EOF

:unload
   for /L %%i in (0,1,!MaxQuadIndex!) do (
     echo.!%1_%%i!>%1_%%i.txt
     set "%1_%%i="
   )
   goto :EOF
 
   
REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM



EDIT: Correct the print subroutine on above code.
EDIT2:Add faster method :
pi = 176*arctan(1/57) + 28*arctan(1/239) - 48*arctan(1/682) + 96*arctan(1/12943)
call :PiEngine 176 57 28 239 -48 682 96 12943


Francesco

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Batch macro vs function call test

#10 Post by Aacini » 13 Oct 2017 09:24

I completed the additional modifications described in my previous (version1 code) post. The new, version2 code achieved an additional 29% running time decrease from version1; this means that version2 code just requires 32% the running time of original code, that is, a 68% decrease.

The variables in version2 code are renamed and distributed in the environment accordingly to this schema:

  1. Static, macro and less-used variables
    1. A_NumQuads (static)
    2. A_MaxQuadIndex (static)
    3. C, D, F (used in SET /A expressions)
    4. M_Add
    5. M_CopyValue
    6. M_DivideByIntegerN
    7. M_MultiplyByIntegerN
    8. M_SetToInteger
    9. M_Subtract
    10. N_Formula
  2. Array variables
    1. P <- Pi
    2. Q <- PiTerm
    3. R <- AR_Recip
    4. T <- AR_Term
  3. Single variables
    1. V_AR_Toggle
    2. V_AR_K

This is the new version2 code:

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
echo pi.bat  -  By Don Cross  -  http://cosinekitty.com

REM Empty the environment
for /F "delims==" %%v in ('set') do set "%%v="

set /a A_NumQuads = 30
set /a A_MaxQuadIndex = A_NumQuads-1, F = 10000

call :CreateMacros
echo %time% - started

call :PiEngine 48 18 32 57 -20 239
call :PiEngine 16 5 -4 239
goto :EOF

:PiEngine
    set /A "%M_SetToInteger:var=P%=0"
    set "N_Formula="
    :PiTermLoop
        call :ArctanRecip PiTerm %2
        if %1 lss 0 (
            set /A "N=-%1,%M_MultiplyByIntegerN:var=Q%"
            set /A "%M_Subtract_Pi:v=Q%"
            set Operator=-
        ) else (
            set /A "N=%1,%M_MultiplyByIntegerN:var=Q%"
            set /A "%M_Add_Pi:v=Q%"
            set Operator=+
        )
        if not defined N_Formula (
            set "N_Formula=pi = %1*arctan(1/%2)"
        ) else (
            set "N_Formula=%N_Formula% %Operator% %N%*arctan(1/%2)"
        )
        shift
        shift
    if "%1" neq "" goto PiTermLoop
    call :Print P
    echo %time% - finished %N_Formula%
exit /B


:Print
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = A_MaxQuadIndex - 1
    echo/
    <nul (
    set /P "=%1 = !%1%A_MaxQuadIndex%!."
    for /L %%i in (%PrintMaxQuadIndex%, -1, %PrintMinQuadIndex%) do (
        set /A PrintDigit=10000 + %1%%i
        set /P "=!PrintDigit:~1!"
    )
    )
    echo/
    echo/
    for %%v in (PrintMinQuadIndex PrintMaxQuadIndex PrintDigit) do set "%%v="
exit /B


:ArctanRecip   %1 = PiTerm *always*
    set /A "%M_SetToInteger:var=Q%=1"
    set /A "N=%2,%M_DivideByIntegerN:var=Q%"
    set /A "%M_CopyValue_AR_Recip:var=Q%"
    set /A V_AR_Toggle = -1,  V_AR_K = 3
    :ArctanLoop
        set /A "N=%2,%M_DivideByIntegerN:var=R%"
        set /A "N=%2,%M_DivideByIntegerN:var=R%"
        set /A "%M_CopyValue_AR_Term:var=R%"
        set /A "N=V_AR_K,%M_DivideByIntegerN:var=T%"
        REM call :IsZero AR_IsZeroFlag AR_Term
        for /L %%i in (0, 1, %A_MaxQuadIndex%) do (
            if !T%%i! neq 0 goto :Accum_AR_Term
        )
        exit /B
        :Accum_AR_Term
        if !V_AR_Toggle! lss 0 (
            set /A "%M_Subtract_PiTerm:v=T%"
        ) else (
            set /A "%M_Add_PiTerm:v=T%"
        )
        set /a V_AR_K += 2, V_AR_Toggle *= -1
    goto ArctanLoop
exit /B


REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM


:CreateMacros

    set /A A_MaxQuadIndexM1 = A_MaxQuadIndex - 1
    set "M_SetToInteger="
    for /L %%i in (0, 1, %A_MaxQuadIndexM1%) do (
        set "M_SetToInteger=!M_SetToInteger!var%%i="
    )
    set "M_SetToInteger=%M_SetToInteger%0,var%A_MaxQuadIndex%"
    set "A_MaxQuadIndexM1="

    set "M_MultiplyByIntegerN=C=0"
    for /L %%i in (0, 1, %A_MaxQuadIndex%) do (
        set "M_MultiplyByIntegerN=!M_MultiplyByIntegerN!,D=C+var%%i*N,var%%i=D%%F,C=D/F"
    )

    set "M_DivideByIntegerN=C=0"
    for /L %%i in (%A_MaxQuadIndex%, -1, 0) do (
        set "M_DivideByIntegerN=!M_DivideByIntegerN!,D=C*F+var%%i,C=D%%N,var%%i=D/N"
    )

    set "M_CopyValue=u0=var0"
    for /L %%i in (1, 1, %A_MaxQuadIndex%) do (
        set "M_CopyValue=!M_CopyValue!,u%%i=var%%i"
    )
    set "M_CopyValue_AR_Recip=%M_CopyValue:u=R%"
    set "M_CopyValue_AR_Term=%M_CopyValue:u=T%"
    set "M_CopyValue="

    set "M_Add=C=0"
    for /L %%i in (0, 1, %A_MaxQuadIndex%) do (
        set "M_Add=!M_Add!,D=C+u%%i+v%%i,u%%i=D%%F,C=D/F"
    )
    set "M_Add_Pi=%M_Add:u=P%"
    set "M_Add_PiTerm=%M_Add:u=Q%"
    set "M_Add="

    set "M_Subtract=C=0"
    for /L %%i in (0, 1, %A_MaxQuadIndex%) do (
        set "M_Subtract=!M_Subtract!,D=u%%i-v%%i+C,C=D>>31,u%%i=D-C*F"
    )
    set "M_Subtract_Pi=%M_Subtract:u=P%"
    set "M_Subtract_PiTerm=%M_Subtract:u=Q%"
    set "M_Subtract="

exit /B

I repeated previous tests with version2 code varying the number of quads in the arrays; this is the result.

Code: Select all

Quads        Digits   Time version1     Time version2

30           110      17.25             12.30
45 (1.5)     170      52.54  (3.05)     35.90  (2.91)
60 (2.0)     230      118.75 (6.88)     80.12  (6.51)
75 (2.5)     290      224.42 (13.01)    152.08 (12.36)
90 (3.0)     350      380.84 (22.08)    255.45 (20.77)


Antonio

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#11 Post by einstein1969 » 15 Oct 2017 07:52

I have just realized that SET/P is slower than FOR/F

this version substitute the precedent. The break even point (BEP) is passed from about 500 NumQuads to about 400 NumQuads.

Image

the new code:

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
echo.pi.bat  -  By Don Cross  -  http://cosinekitty.com

pushd %tmp%

rem empty the environment for faster execution
(
  for /F "Tokens=1 delims==" %%v in ('set') do set "%%v="
  set pidebug=%pidebug%
)

rem Add input parameter NumQuads

if "%~1" == "" (set /a NumQuads = 30) else (set /a NumQuads=%~1)
if !NumQuads! EQU 0  set /a NumQuads = 30
echo.
echo.NumQuads=!NumQuads!
echo.
set /a MaxQuadIndex = NumQuads - 1
set "NumQuads="

set /a _RNDL=32768/10

echo.
echo.%time% - started
echo.


rem Pi = 48*arctan(1/18) + 32*arctan(1/57) - 20*arctan(1/239)
call :PiEngine 48 18 32 57 -20 239

set _CC=0

rem Pi = 16*arctan(1/5) - 4*arctan(1/239)
call :PiEngine 16 5 -4 239

set _CC=0

rem ref.   http://www.jjj.de/arctan/   
rem others http://caramba.loria.fr/sem-slides/200805231000.pdf

rem pi = 176*arctan(1/57) + 28*arctan(1/239) - 48*arctan(1/682) + 96*arctan(1/12943)
call :PiEngine 176 57 28 239 -48 682 96 12943

set _CC=0

rem pi = 352*arctan(1/192) + 156*arctan(1/239) + 400*arctan(1/515) - 128*arctan(1/1068) - 224*arctan(1/173932)
call :PiEngine 352 192 156 239 400 515 -128 1068 -224 173932

popd

goto :EOF


:PiEngine
    call :SetToInteger Pi 0
    set "_Formula="
    :PiTermLoop
        call :ArctanRecip PiTerm %2
        set /a "N=(((%1)>>31|1)*(%1))" & rem N=ABS(%1)
        call :MultiplyByInteger PiTerm %N%
        if %1 lss 0 (
            call :Subtract Pi PiTerm
            set Operator=-
        ) else (
            call :Add Pi PiTerm
            set Operator=+
        )
        if defined _Formula (
            set "_Formula=%_Formula% %Operator% %N%*arctan(1/%2)"
        ) else (
            set "_Formula=pi = %1*arctan(1/%2)"
        )
        shift
        shift
    if "%1" neq "" goto PiTermLoop
    call :Print Pi
    echo.
    echo %time% - finished %_Formula%
    echo.
goto :EOF
   
:Print
    call :load %1
    REM  Omit a couple of least significant quads, because they will have roundoff errors.
    if defined PiDebug (
        set /a PrintMinQuadIndex=0
    ) else (
        set /a PrintMinQuadIndex=2
    )
    set /a PrintMaxQuadIndex = MaxQuadIndex - 1
    set /p "=%1 = !%1_%MaxQuadIndex%!." < nul:
    for /L %%i in (%PrintMaxQuadIndex%, -1, %PrintMinQuadIndex%) do (
        set /A PrintDigit=10000 + %1_%%i
        set /p "=!PrintDigit:~1!" < nul:
    )
    echo.
    call :unload %1
    set "PrintDigit="
    set "PrintMaxQuadIndex="
    set "PrintMinQuadIndex="
    goto :EOF
   
:SetToInteger
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        rem set /a %1_%%i = 0
        echo.0>%1_%%i.txt
    )
    echo.%2>%1_%MaxQuadIndex%.txt
    goto :EOF
 
:DivideByInteger
    if defined PiDebug echo.DivideByInteger %1 %2
    rem C=Carry, D=Digit
    set /a C = 0
    for /L %%i in (!MaxQuadIndex!, -1, 0) do (
        if not defined %1_%%i for /f %%f in (%1_%%i.txt) do set "%1_%%i=%%f"
        set /a D = C*10000 + %1_%%i, C = D %% %2, %1_%%i = D / %2
        echo(!%1_%%i!>%1_%%i.txt
        set "%1_%%i="
    )
    set /a _CC+=1, perc=_CC*100/(MaxQuadIndex*11)
    title %perc%%% (%_CC%) %_Formula% [%time:~0,-3%]
    set "C="
    set "D="
    set "perc="
    goto :EOF
   
:MultiplyByInteger
    if defined PiDebug echo.MultiplyByInteger %1 %2
    call :load %1
    set /a MBI_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        set /a MBI_Digit = %1_%%i * %2 + MBI_Carry
        set /a %1_%%i = MBI_Digit %% 10000
        set /a MBI_Carry = MBI_Digit / 10000
    )
    call :unload %1
    set "MBI_Carry="
    set "MBI_Digit="
    goto :EOF   
   
:ArctanRecip
    if defined PiDebug echo.ArctanRecip %1 %2
    call :SetToInteger %1 1
    call :DivideByInteger %1 %2
    call :CopyValue AR_Recip %1
    set /a AR_Toggle = -1, AR_K = 3
    :ArctanLoop
        if defined PiDebug (
            echo.
            echo.ArctanRecip  AR_K=!AR_K!    ---------------------------------------------------------
        )
        call :DivideByInteger AR_Recip %2
        call :DivideByInteger AR_Recip %2
        call :CopyValue AR_Term AR_Recip
        call :DivideByInteger AR_Term !AR_K!
        REM call :IsZero AR_IsZeroFlag AR_Term
        for /L %%i in (0, 1, %MaxQuadIndex%) do (
            if not defined AR_Term_%%i for /f %%f in (AR_Term_%%i.txt) do set "AR_Term_%%i=%%f"
            if !AR_Term_%%i! neq 0 (
               set AR_Term_%%i=
               goto :Accum_AR_Term
            ) else (
               set AR_Term_%%i=
            )
        )
        set "AR_K="
        set "AR_Toggle="
        exit /B
        :Accum_AR_Term

        if !AR_Toggle! lss 0 (
            call :Subtract %1 AR_Term
        ) else (
            call :Add %1 AR_Term
        )
        set /a AR_K += 2, AR_Toggle *= -1
    goto ArctanLoop
   
:CopyValue
    if defined PiDebug echo.CopyValue %1 %2
   
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        if not defined %2_%%i for /f %%f in (%2_%%i.txt) do set "%2_%%i=%%f"
        rem set /a %1_%%i = %2_%%i
        echo.!%2_%%i!>%1_%%i.txt
        set "%2_%%i="
    )
    goto :EOF
   
:Add
    if defined PiDebug echo.Add %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    set /a Add_Carry = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        if not defined %1_%%i for /f %%f in (%1_%%i.txt) do set "%1_%%i=%%f
        if not defined %2_%%i for /f %%f in (%2_%%i.txt) do set "%2_%%i=%%f
        set /a Add_Digit = Add_Carry + %1_%%i + %2_%%i
        set /a %1_%%i = Add_Digit %% 10000
        set /a Add_Carry = Add_Digit / 10000
        echo.!%1_%%i!>%1_%%i.txt
        set "%1_%%i="
        set "%2_%%i="
    )
    set "Add_Carry="
    set "Add_Digit="
    goto :EOF   

:Subtract 
    if defined PiDebug echo.Subtract %1 %2
    if defined PiDebug call :Print %1
    if defined PiDebug call :Print %2
    rem B=Subtract_Borrow, D=Subtract_Digit
    set /a B = 0
    for /L %%i in (0, 1, !MaxQuadIndex!) do (
        if not defined %1_%%i for /f %%f in (%1_%%i.txt) do set "%1_%%i=%%f"
        if not defined %2_%%i for /f %%f in (%2_%%i.txt) do set "%2_%%i=%%f"
        set /a "D = %1_%%i - %2_%%i + B, B=D>>31, %1_%%i=D-B*10000"
        echo.!%1_%%i!>%1_%%i.txt
        set "%1_%%i="
        set "%2_%%i="
    )
    set "B="
    set "D="
    goto :EOF

:load
   for /L %%i in (0,1,!MaxQuadIndex!) do for /f %%f in (%1_%%i.txt) do set "%1_%%i=%%f"
exit /B

:unload
   for /L %%i in (0,1,!MaxQuadIndex!) do (echo.!%1_%%i!>%1_%%i.txt & set "%1_%%i=")
exit /B
 
   
REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM


Francesco

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Batch macro vs function call test

#12 Post by Aacini » 17 Oct 2017 12:01

I completed a new version of this code based on a different method. In this Version 3 all big numbers are managed in files. This phrase means that a big number is stored in a text file with lines that contain a group of 25 quads each plus a heading line number. For example, this is the big number 1 (file One.txt) when the number of lines is 4 (NumQuads=100):

Code: Select all

01 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
03 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
04 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

Each number is read from a file, processed and stored back in its file. The processing of each number is done via a FOR /F loop that read the 25 quads per line, operate all quads via a single SET /A command, and store the 25 results in a result file via an ECHO command. This approach allows to keep the number of environment variables low and constant no matter how many lines and quads have the big numbers, so this method should run faster than the other methods that increase the number of environment variables with the quads.

This method also allows to manage certain operations over numbers using the usual commands to manage files. For example, the call :SetToInteger Pi 0 line becomes copy Zero.txt Pi.txt command, call :CopyValue AR_Term AR_Recip becomes copy AR_Recip.txt AR_Term.txt, and to check if AR_Term is not zero, just use fc AR_Term.txt Zero.txt > nul followed by if errorlevel 1 goto AR_Term_not_zero.

This is the new Version 3 code:

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
echo pi.bat  -  By Don Cross  -  http://cosinekitty.com

REM Empty the environment
for /F "delims=" %%a in ('where sort') do set "sort=%%a"
(
for /F "delims==" %%v in ('set') do set "%%v="
set "sort=%sort%"
)

REM Define the number of groups = 25 Quads = 100 digits
set /A A_NumGroups = 3
set /A A_NumQuads = NumGroups*25, A_MaxQuadIndex = A_NumQuads-1, @ = 10000

call :CreateMacros
echo %time% - started

call :PiEngine 48 18 32 57 -20 239
call :PiEngine 16 5 -4 239
goto :EOF

:PiEngine
    REM call :SetToInteger Pi 0
    copy /Y Zero.txt Pi.txt > NUL
    set "N_Formula="

    :PiTermLoop
        call :ArctanRecip PiTerm %2
        if %1 lss 0 (

            REM call :MultiplyByInteger PiTerm -%1
            set /A "#=-%1,'=0"
            (for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /A "%M_MultiplyByInteger#%"
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt

            REM call :Subtract Pi PiTerm
            set "'=0"
            < PiTerm.txt (
            for /F "tokens=1-26" %%A in (Pi.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Subtract%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del Pi.txt & ren Result.txt Pi.txt

            set "Operator=-"

        ) else (

            REM call :MultiplyByInteger PiTerm %1
            set /A "#=%1,'=0"
            (for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /A "%M_MultiplyByInteger#%"
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt

            REM call :Add Pi PiTerm
            set "'=0"
            < PiTerm.txt (
            for /F "tokens=1-26" %%A in (Pi.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Add%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del Pi.txt & ren Result.txt Pi.txt

            set "Operator=+"

        )
        if not defined N_Formula (
            set "N_Formula=pi = %1*arctan(1/%2)"
        ) else (
            set "N_Formula=%N_Formula% %Operator% %#%*arctan(1/%2)"
        )
        shift
        shift
    if "%1" neq "" goto PiTermLoop
    call :Print Pi
    echo %time% - finished %N_Formula%
exit /B


:ArctanRecip   %1 = PiTerm *always*

    REM call :SetToInteger PiTerm 1
    copy /Y One.txt PiTerm.txt > NUL

    REM call :DivideByInteger PiTerm %2
    set /A "#=%2,'=0"
    (for /F "tokens=1-26" %%A in ('"%sort%" /R PiTerm.txt') do (
        set /A "%M_DivideRbyInteger#%"
        echo %%A%M_Result%
    )) > Result.txt
    "%sort%" Result.txt /O PiTerm.txt

    REM call :CopyValue AR_Recip PiTerm
    copy /Y PiTerm.txt AR_Recip.txt > NUL

    set /A V_AR_Toggle = -1,  V_AR_K = 3

    :ArctanLoop

        REM call :DivideByInteger AR_Recip %2
        set /A "#=%2,'=0"
        (for /F "tokens=1-26" %%A in ('"%sort%" /R AR_Recip.txt') do (
            set /A "%M_DivideRbyInteger#%"
            echo %%A%M_Result%
        )) > Result.txt
        REM call :DivideByInteger AR_Recip %2
        set "'=0"
        (for /F "tokens=1-26" %%A in (Result.txt) do (
            set /A "%M_DivideRbyInteger#%"
            echo %%A%M_Result%
        )) > AR_Recip.txt
        "%sort%" AR_Recip.txt /O AR_Recip.txt

        REM call :CopyValue AR_Term AR_Recip
        copy /Y AR_Recip.txt AR_Term.txt > NUL

        REM call :DivideByInteger AR_Term !AR_K!
        set /A "#=V_AR_K,'=0"
        (for /F "tokens=1-26" %%A in ('"%sort%" /R AR_Term.txt') do (
            set /A "%M_DivideRbyInteger#%"
            echo %%A%M_Result%
        )) > Result.txt
        "%sort%" Result.txt /O AR_Term.txt

        REM call :IsZero AR_IsZeroFlag AR_Term
        for %%a in ("%sort%") do "%%~DPafc.exe" AR_Term.txt Zero.txt > NUL
        if %errorlevel% equ 0 exit /B

        if !V_AR_Toggle! lss 0 (

            REM call :Subtract PiTerm AR_Term
            set "'=0"
            < AR_Term.txt (
            for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Subtract%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt

        ) else (

            REM call :Add PiTerm AR_Term
            set "'=0"
            < AR_Term.txt (
            for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Add%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt
        )
        set /a V_AR_K += 2, V_AR_Toggle *= -1

    goto ArctanLoop
exit /B


:Print
    echo/
    < NUL (
    set /P "=%1 = "
    set "line=0"
    for /F "tokens=1-26" %%A in ('"%sort%" /R %1.txt') do (
        set /A "line+=1,%M_PrintAdjust%"
        if !line! equ 1 (
            set /P "=%M_PrintFormat:!Z:~1!=!Z:~-1!.%"
        ) else if !line! lss %A_NumGroups% (
            set /P "=%M_PrintFormat%"
        ) else (
            set /P "=%M_PrintFormat:!C:~1!!B:~1!=%"
        )
    ))
    echo/
    echo/
exit /B


REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM


:CreateMacros

    set "n="
    for /L %%i in (2,1,25) do set "n=!n!0 "
    set /A "i=101"
    (
    for /L %%i in (2,1,%A_NumGroups%) do (
        echo !i:~1! %n%0
        set /A i+=1
    )
    echo !i:~1! %n%1
    ) > One.txt
    set /A "i=101"
    (for /L %%i in (1,1,%A_NumGroups%) do (
        echo !i:~1! %n%0
        set /A i+=1
    )) > Zero.txt

    set  "upCase=A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
    set "lowCase=a b c d e f g h i j k l m n o p q r s t u v w x y z"

    REM One-char variables: $=Digit, '=Carry, @=10000, #=int number for multiply/divide

    set "M_MultiplyByInteger#="
    for %%i in (%upCase:~2%) do (
        set "M_MultiplyByInteger#=!M_MultiplyByInteger#!,$='+%%%%i*#,%%i=$%%@,'=$/@"
    )
    set "M_MultiplyByInteger#=!M_MultiplyByInteger#:~1!"

    set "M_DivideRbyInteger#="
    for %%i in (%upCase:~2%) do (
        set "M_DivideRbyInteger#=$='*@+%%%%i,%%i=$/#,'=$%%#,!M_DivideRbyInteger#!"
    )
    set "M_DivideRbyInteger#=!M_DivideRbyInteger#:~0,-1!"

    set "lowCase2=%lowCase:~2%"
    set "M_Add="
    for %%i in (%upCase:~2%) do (
        for /F "tokens=1*" %%j in ("!lowCase2!") do (
            set "M_Add=!M_Add!,$='+%%%%i+%%%%j,%%i=$%%@,'=$/@"
            set "lowCase2=%%k"
        )
    )
    set "M_Add=%M_Add:~1%"

    set "lowCase2=%lowCase:~2%"
    set "M_Subtract="
    for %%i in (%upCase:~2%) do (
        for /F "tokens=1*" %%j in ("!lowCase2!") do (
            set "M_Subtract=!M_Subtract!,$=%%%%i-%%%%j+','=$>>31,%%i=$-'*@"
            set "lowCase2=%%k"
        )
    )
    set "M_Subtract=%M_Subtract:~1%"

    set "M_Result="
    for %%i in (%upCase:~2%) do (
        set "M_Result=!M_Result! ^!%%i^!"
    )

    set "M_PrintAdjust="
    set "M_PrintFormat="
    for %%i in (%upCase:~2%) do (
        set "M_PrintAdjust=!M_PrintAdjust!,%%i=@+%%%%i"
        set "M_PrintFormat=^!%%i:~1^!!M_PrintFormat!"
    )
    set "M_PrintAdjust=%M_PrintAdjust:~1%"

    set "upCase="
    set "lowCase="

exit /B

I repeated a series of tests with the original and the three different versions of my code. These are the results; all times are in seconds:

Code: Select all

Quads   Original    V1:Efficient   V2:V1+Environ   V3:File

  25       27.93         10.81           8.01        27.15
  50      103.00         69.97          48.18        62.31
 100      511.39        515.69         345.57       150.95
 150     1458.53       1759.33        1158.11       266.01
 200     3157.66       -------        2737.64       400.80
 250                   -------                      570.67
 300                   -------                      740.29

These results show some interesting points: in the original code, the time vs. the number of quads increase in a non-linear (exponential) relation as expected. Although the Version1 code run faster than the original, because it is more efficient, it also presents the same behaviour related to the environment grow. However, when the number of quads is about 100, the large environment combined with the complexity of the very long SET /A commands increments the running time above the original time. Conclusion: a SET /A command that becomes every time larger may eventually be less efficient than an equivalent FOR loop. Also, this Version1 have a limit in the number of quads restricted by the largest SET /A expression that can be stored in a variable, so it can not process 200 quads.

It is interesting to note that the only difference between Version2 and Version1 is that the size of the environment is lesser, but this modification alone decrease the running time in an important amount and also allows to manage a larger number of quads.

The results of the Version3 code are the expected ones: the objective of this version is to decrease the size of the environment as much as possible, so the big numbers are stored in disk files instead of environment variables. The idea is that the time involved in the management of disk files be lesser than the time involved in the management of a very large environment. The results confirmed this point: the times are not just much lesser, but the increment in the time vs. the number of quads is much more flat.

This Version3 code can be improved in several ways. For example, the Division FOR loop requires that the lines with 25 quads be in reverse order, so a SORT /R is required before the division loop and another SORT after it (in order to leave the result in the right order) Below :ArctanLoop label there are two divisions in series over the same number, so an improvement already made was that after the first division the number is not sorted again because the next division require it also in reversed order, so just the final result of the second division is sorted again; this mod saves two SORT commands. Several similar modifications can be made to this code; I will post they in the next version...

Antonio

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Batch macro vs function call test

#13 Post by Aacini » 17 Oct 2017 21:55

I completed several modifications to previous Version 3 code that decrease the running time. This is a list of the changes made in this new Version 4:

  • The One.txt file is now created in reversed order. This file is copied to PiTerm before the first division, so one SORT /R command was eliminated.
  • After first division, PiTerm in reversed order is copied into AR_Recip. Another SORT command is eliminated in second division.
  • AR_Recip is used in division operations only, so it is never re-sorted again. One SORT command was eliminated.
  • AR_Term is copied from AR_Recip and then used in a division, so its SORT /R command was eliminated.
  • The FC AR_Term.txt Zero.txt command was replaced by a SET /A expression that indicate if AR_Term is zero.
  • The loop at :ArctanLoop label assembled with GOTO was changed by an infinite FOR /L %%i in ( ) loop executed in a new cmd.exe context that is break by an EXIT (no /B switch) command (while loop). This modification not only runs this code segment faster, but also allows to remove all macro variables from the environment after they were expanded.

This is the new Version 4 code:

Code: Select all

@if defined talk (echo on) else (echo off)
setlocal EnableDelayedExpansion
if "%1" equ "ArctanLoop" goto ArctanLoop

echo pi.bat  -  By Don Cross  -  http://cosinekitty.com

REM Empty the environment
for /F "delims=" %%a in ('where sort') do set "sort=%%a"
(
for /F "delims==" %%v in ('set') do set "%%v="
set "sort=%sort%"
)

REM Define the number of groups = 25 Quads = 100 digits
set /A A_NumGroups = 3
set /A A_NumQuads = NumGroups*25, A_MaxQuadIndex = A_NumQuads-1, @ = 10000

call :CreateMacros
echo %time% - started

call :PiEngine 48 18 32 57 -20 239
call :PiEngine 16 5 -4 239
goto :EOF

:PiEngine
    REM call :SetToInteger Pi 0
    copy /Y Zero.txt Pi.txt > NUL
    set "N_Formula="

    :PiTermLoop
        call :ArctanRecip PiTerm %2
        if %1 lss 0 (

            REM call :MultiplyByInteger PiTerm -%1
            set /A "#=-%1,'=0"
            (for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /A "%M_MultiplyByInteger#%"
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt

            REM call :Subtract Pi PiTerm
            set "'=0"
            < PiTerm.txt (
            for /F "tokens=1-26" %%A in (Pi.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Subtract%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del Pi.txt & ren Result.txt Pi.txt

            set "Operator=-"

        ) else (

            REM call :MultiplyByInteger PiTerm %1
            set /A "#=%1,'=0"
            (for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /A "%M_MultiplyByInteger#%"
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt

            REM call :Add Pi PiTerm
            set "'=0"
            < PiTerm.txt (
            for /F "tokens=1-26" %%A in (Pi.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Add%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del Pi.txt & ren Result.txt Pi.txt

            set "Operator=+"

        )
        if not defined N_Formula (
            set "N_Formula=pi = %1*arctan(1/%2)"
        ) else (
            set "N_Formula=%N_Formula% %Operator% %#%*arctan(1/%2)"
        )
        shift
        shift
    if "%1" neq "" goto PiTermLoop
    call :Print Pi
    echo %time% - finished %N_Formula%
exit /B


:ArctanRecip   %1 = PiTerm *always*

    REM call :SetToInteger PiTerm 1
    copy /Y OneR.txt PiTerm.txt > NUL

    REM call :DivideByInteger PiTerm %2
    set /A "#=%2,'=0"
    (for /F "tokens=1-26" %%A in (PiTerm.txt) do (
        set /A "%M_DivideRbyInteger#%"
        echo %%A%M_Result%
    )) > AR_Recip.txt

    REM call :CopyValue AR_Recip PiTerm
    "%sort%" AR_Recip.txt /O PiTerm.txt

    set /A V_AR_Toggle = -1,  V_AR_K = 3


    REM Re-execute the code starting at ArctanLoop label in a new cmd.exe context
    for %%a in ("%sort%") do "%%~DPacmd.exe" /D /C "%~F0" ArctanLoop %2
    exit /B

    :ArctanLoop

    (
    REM Remove the macros from the environment after they have been expanded
    for /F "delims==" %%v in ('set M_') do set "%%v="

    for /L %%? in ( ) do (

        REM call :DivideByInteger AR_Recip %2
        set /A "#=%2,'=0"
        (for /F "tokens=1-26" %%A in (AR_Recip.txt) do (
            set /A "%M_DivideRbyInteger#%"
            echo %%A%M_Result%
        )) > Result.txt
        REM call :DivideByInteger AR_Recip %2
        set "'=0"
        (for /F "tokens=1-26" %%A in (Result.txt) do (
            set /A "%M_DivideRbyInteger#%"
            echo %%A%M_Result%
        )) > AR_Recip.txt

        REM call :CopyValue AR_Term AR_Recip
        copy /Y AR_Recip.txt AR_Term.txt > NUL

        REM call :DivideByInteger AR_Term !AR_K!
        set /A "#=V_AR_K,'=0, AR_Term=0"
        (for /F "tokens=1-26" %%A in (AR_Term.txt) do (
            set /A "%M_DivideRbyInteger#%"
            set /A "AR_Term+=%M_Sum%"
            echo %%A%M_Result%
        )) > Result.txt

        REM Exit from the infinite FOR /L loop (while loop)
        if !AR_Term! equ 0 exit

        "%sort%" Result.txt /O AR_Term.txt
        if !V_AR_Toggle! lss 0 (

            REM call :Subtract PiTerm AR_Term
            set "'=0"
            < AR_Term.txt (
            for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Subtract%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt

        ) else (

            REM call :Add PiTerm AR_Term
            set "'=0"
            < AR_Term.txt (
            for /F "tokens=1-26" %%A in (PiTerm.txt) do (
                set /P "oper2="
                for /F "tokens=1-26" %%a in ("!oper2!") do (
                    set /A "%M_Add%"
                )
                echo %%A%M_Result%
            )) > Result.txt
            del PiTerm.txt & ren Result.txt PiTerm.txt
        )
        set /a V_AR_K += 2, V_AR_Toggle *= -1

    ))

exit /B


:Print
    echo/
    < NUL (
    set /P "=%1 = "
    set "line=0"
    for /F "tokens=1-26" %%A in ('"%sort%" /R %1.txt') do (
        set /A "line+=1,%M_PrintAdjust%"
        if !line! equ 1 (
            set /P "=%M_PrintFormat:!Z:~1!=!Z:~-1!.%"
        ) else if !line! lss %A_NumGroups% (
            set /P "=%M_PrintFormat%"
        ) else (
            set /P "=%M_PrintFormat:!C:~1!!B:~1!=%"
        )
    ))
    echo/
    echo/
exit /B


REM    $Log: pi.bat,v $
REM    Revision 1.2  2007/09/06 21:49:15  Don.Cross
REM    Added time stamps and display of formula.
REM
REM    Revision 1.1  2007/09/06 21:12:36  Don.Cross
REM    Batch file for calculating pi
REM


:CreateMacros

    set "n="
    for /L %%i in (2,1,25) do set "n=!n!0 "
    set /A "i=100+A_NumGroups"
    (
    echo !i:~1! %n%1
    for /L %%i in (2,1,%A_NumGroups%) do (
        set /A i-=1
        echo !i:~1! %n%0
    )
    ) > OneR.txt
    set /A "i=101"
    (for /L %%i in (1,1,%A_NumGroups%) do (
        echo !i:~1! %n%0
        set /A i+=1
    )) > Zero.txt

    set  "upCase=A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
    set "lowCase=a b c d e f g h i j k l m n o p q r s t u v w x y z"

    REM One-char variables: $=Digit, '=Carry, @=10000, #=int number for multiply/divide

    set "M_MultiplyByInteger#="
    for %%i in (%upCase:~2%) do (
        set "M_MultiplyByInteger#=!M_MultiplyByInteger#!,$='+%%%%i*#,%%i=$%%@,'=$/@"
    )
    set "M_MultiplyByInteger#=!M_MultiplyByInteger#:~1!"

    set "M_DivideRbyInteger#="
    for %%i in (%upCase:~2%) do (
        set "M_DivideRbyInteger#=$='*@+%%%%i,%%i=$/#,'=$%%#,!M_DivideRbyInteger#!"
    )
    set "M_DivideRbyInteger#=!M_DivideRbyInteger#:~0,-1!"

    set "M_Add="
    set "M_Sum="
    set "lowCase2=%lowCase:~2%"
    for %%i in (%upCase:~2%) do (
        for /F "tokens=1*" %%j in ("!lowCase2!") do (
            set "M_Add=!M_Add!,$='+%%%%i+%%%%j,%%i=$%%@,'=$/@"
            set "lowCase2=%%k"
        )
        set "M_Sum=!M_Sum!+%%%%i"
    )
    set "M_Add=%M_Add:~1%"
    set "M_Sum=%M_Sum:~1%"

    set "M_Subtract="
    set "lowCase2=%lowCase:~2%"
    for %%i in (%upCase:~2%) do (
        for /F "tokens=1*" %%j in ("!lowCase2!") do (
            set "M_Subtract=!M_Subtract!,$=%%%%i-%%%%j+','=$>>31,%%i=$-'*@"
            set "lowCase2=%%k"
        )
    )
    set "M_Subtract=%M_Subtract:~1%"

    set "M_Result="
    for %%i in (%upCase:~2%) do (
        set "M_Result=!M_Result! ^!%%i^!"
    )

    set "M_PrintAdjust="
    set "M_PrintFormat="
    for %%i in (%upCase:~2%) do (
        set "M_PrintAdjust=!M_PrintAdjust!,%%i=@+%%%%i"
        set "M_PrintFormat=^!%%i:~1^!!M_PrintFormat!"
    )
    set "M_PrintAdjust=%M_PrintAdjust:~1%"

    set "upCase="
    set "lowCase="

exit /B

This is the final table that include the results from Version 4: :shock: 8)

Code: Select all

Quads   Original    V1:Efficient   V2:V1+Environ   V3:File   V4:V3+Efficient

  25       27.93         10.81           8.01        27.15        7.24
  50      103.00         69.97          48.18        62.31       18.52
  75      259.24        224.30         151.84       104.94       34.28
 100      511.39        515.69         345.57       150.95       51.58
 150     1458.53       1759.33        1158.11       266.01      100.17
 200     3157.66       -------        2737.64       400.80      163.49
 250                   -------                      570.67      228.17
 300                   -------                      740.29      316.05


Antonio

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#14 Post by einstein1969 » 18 Oct 2017 09:44

:shock: :shock: 8)

Hi Antonio,

this version is very efficiency.

There is a problem when I press CONTROL-C, the batch don't stop. It's possible break ?

Francesco

EDIT

Code: Select all

set /A A_NumGroups = 3
set /A A_NumQuads = NumGroups*25, A_MaxQuadIndex = A_NumQuads-1, @ = 10000

There is an error on A_NumQuads. It uses NumGroup instead of A_NumGroups. But the batch work fine :?

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Batch macro vs function call test

#15 Post by einstein1969 » 18 Oct 2017 11:57

I have compared the last version of mine and your and the original :shock: :shock: :shock: :lol:

time in seconds.
Image

Francesco

Post Reply