SET/A: Macro to expand function results in SET /A expression

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
Aacini
Expert
Posts: 1885
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

SET/A: Macro to expand function results in SET /A expression

#1 Post by Aacini » 26 Dec 2011 23:51

Batch macros with parameters make possible to redefine the syntax of Batch language and give access to complex procedures in a simple way. Let's start with a smaller example before we immerse in SET/A macro description.

Code: Select all

%SET/S% variable:~start,size=substring to insert
Previous "SET-Substring" macro replace part of the current "variable" value with another substring; the part to replace is given in the same way as the standard Batch substring extraction with the sole difference that it does not end at the last character by default, so this point must be explicitly indicated by -0 in "size". If a substring is inserted beyond the variable end, the original value is padded with spaces up to that place. To append a substring at variable end, put -0 in "start".

Code: Select all

@echo off
::SET/S - Macro that replace a substring in a variable
::Antonio Perez Ayala - Dec/26/2011

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

:SET/S variable:~start[,size]=substring
set set/s=for %%n in (1 2) do if %%n==2 (%\n%
      for /F "tokens=1* delims==" %%1 in ("!argv!") do (%\n%
         set argv=%%1%\n%
         set substring=%%2%\n%
      )%\n%
      for /F "tokens=1-3 delims=:~, " %%1 in ("!argv!") do (%\n%
         set "value=!%%1!"%\n%
         if not defined value set "value= "%\n%
         set size=%%3%\n%
         if not defined size (%\n%
            if "%%2" == "-0" (%\n%
               set "result=!value!!substring!"%\n%
            ) else (%\n%
               if "!value:~%%2,1!" == "" (%\n%
                  for /L %%A in (1,1,%%2) do set "value=!value! "%\n%
                  set "result=!value:~0,%%2!!substring!"%\n%
               ) else (%\n%
                  set "result=!value:~0,%%2!!substring!!value:~%%2!"%\n%
               )%\n%
            )%\n%
         ) else (%\n%
            if "%%2" == "-0" (%\n%
                set "result=!value!!substring!"%\n%
            ) else if "!value:~%%2,1!" == "" (%\n%
               for /L %%A in (1,1,%%2) do set "value=!value! "%\n%
               set "result=!value:~0,%%2!!substring!"%\n%
            ) else if not "!size:~0,1!" == "-" (%\n%
               set /A restart=%%2+%%3%\n%
               for %%r in (!restart!) do set "result=!value:~0,%%2!!substring!!value:~%%r!"%\n%
            ) else (%\n%
               set "result=!value:~0,%%2!!substring!"%\n%
               if not "%%3" == "-0" set "result=!result!!value:~%%3!"%\n%
            )%\n%
         )%\n%
         for %%v in ("!result!") do endlocal ^& set "%%1=%%~v"%\n%
      )%\n%
   ) else setlocal enableDelayedExpansion ^& set argv=

for /F "skip=4 delims=pR tokens=1,2" %%a in (
       'reg query hkcu\environment /v temp' ) do set TAB=%%b


setlocal EnableDelayedExpansion

cls
echo/
echo                      SET STRING=0123456789
echo/
for %%a in (0 4 -3 -0 13) do (
    set string=0123456789
    %set/s% string:~%%~a=abc
    echo %%SET/S%% STRING:~%%~a=abc!TAB!!TAB!!string!
)
echo/
for %%a in ("0,3" "4,4") do (
    set string=0123456789
    %set/s% string:~%%~a=abc
    echo %%SET/S%% STRING:~%%~a=abc!TAB!!TAB!!string!
)
for %%a in ("-3,1" "-0,2" "13,4") do (
    set string=0123456789
    %set/s% string:~%%~a=abc
    echo %%SET/S%% STRING:~%%~a=abc!TAB!!string!
)
echo/
for %%a in ("0,-3" "4,-4" "-3,-1" "-0,-2" "13,-4") do (
    set string=0123456789
    %set/s% string:~%%~a=abc
    echo %%SET/S%% STRING:~%%~a=abc!TAB!!string!
)
echo/
for %%a in ("0,-0" "4,-0" "-3,-0" "-0,-0" "13,-0") do (
    set string=0123456789
    %set/s% string:~%%~a=abc
    echo %%SET/S%% STRING:~%%~a=abc!TAB!!string!
)
echo/
Results:

Code: Select all

                     SET STRING=0123456789

%SET/S% STRING:~0=abc           abc0123456789
%SET/S% STRING:~4=abc           0123abc456789
%SET/S% STRING:~-3=abc          0123456abc789
%SET/S% STRING:~-0=abc          0123456789abc
%SET/S% STRING:~13=abc          0123456789   abc

%SET/S% STRING:~0,3=abc         abc3456789
%SET/S% STRING:~4,4=abc         0123abc89
%SET/S% STRING:~-3,1=abc        0123456abc89
%SET/S% STRING:~-0,2=abc        0123456789abc
%SET/S% STRING:~13,4=abc        0123456789   abc

%SET/S% STRING:~0,-3=abc        abc789
%SET/S% STRING:~4,-4=abc        0123abc6789
%SET/S% STRING:~-3,-1=abc       0123456abc9
%SET/S% STRING:~-0,-2=abc       0123456789abc
%SET/S% STRING:~13,-4=abc       0123456789   abc

%SET/S% STRING:~0,-0=abc        abc
%SET/S% STRING:~4,-0=abc        0123abc
%SET/S% STRING:~-3,-0=abc       0123456abc
%SET/S% STRING:~-0,-0=abc       0123456789abc
%SET/S% STRING:~13,-0=abc       0123456789   abc
SET/A is a macro that execute a SET /A command, but first replace the values returned by Batch functions. For example:

Code: Select all

%SET/A% variable=Func2(Param1,Param2)+var*Func1(Param)+234-Func0()
The functions are standard Batch subroutines with a last parameter that indicate the variable that recieve the numeric result. For example:

Code: Select all

:Func2 Param1 Param2 Result=
setlocal EnableDelayedExpansion
rem Do some calculation on Param1 and Param2, and get Result
rem . . .
endlocal & set %3=%result%
exit /B
If the Batch function takes its numeric parameters via SET /A commands...

Code: Select all

set /A "Param1=%~1"
set /A "Param2=%~2"
... then function parameters in %SET/A% macro may also include expressions:

Code: Select all

%SET/A% variable=Func2(var3*var4,3*var5+6)+var*Func1(var1*2)+234-Func0()
Remember that Batch parameter delimiters may be comma, semicolon or equal-sign, besides spaces, so "FUNC3(P1,P2,P3)" is the same as "FUNC3(P1 P2 P3)" and also "FUNC3(P1;P2=P3)". If a parameter may include anyone of these characters, it must be enclosed in quotes.

This is SET/A macro:

Code: Select all

@echo off
::SET/A - Macro that expand function results in arithmetic expressions
::Antonio Perez Ayala
::Dec/26/2011 - v1.0 Parse functions, replace their return values, execute SET /A

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set set/a=for %%n in (1 2) do if %%n==2 (%\n%
      rem 1- Store functions start and size in setA.F# array %\n%
      for %%v in (N opPos exprLen) do set setA.%%v=0%\n%
      for /L %%p in (12,-1,0) do (%\n%
         set /A "setA.exprLen|=1<<%%p"%\n%
         for %%l in (!setA.exprLen!) do if "!setA.expr:~%%l,1!"=="" set /A "setA.exprLen&=~1<<%%p"%\n%
      )%\n%
      set "setA.copyExpr=!setA.expr!"%\n%
      for %%o in (+ - / %%%% ^^! ^^^> ^^^< ^^^& ^^^|) do set "setA.copyExpr=!setA.copyExpr:%%o=*!"%\n%
      for %%v in (parenLevel namePos funcLevel) do set setA.%%v=%\n%
      for /L %%c in (0,1,!setA.exprLen!) do (%\n%
         set setA.char=!setA.copyExpr:~%%c,1!%\n%
         if defined setA.funcLevel (%\n%
            if "!setA.char!" == ")" (%\n%
               set /A setA.funcLevel-=1%\n%
               if !setA.funcLevel! == 0 (%\n%
                  set /A setA.N+=1, setA.funcLevel=%%c-setA.namePos%\n%
                  for %%n in (!setA.N!) do (%\n%
                     set "setA.F%%n=!setA.namePos! !setA.funcLevel!"%\n%
                  )%\n%
                  for %%v in (namePos funcLevel) do set setA.%%v=%\n%
               rem noelse%\n%
               )%\n%
            ) else if "!setA.char!" == "(" (%\n%
               set /A setA.funcLevel+=1%\n%
            )%\n%
         ) else if defined setA.namePos (%\n%
            if "!setA.char!" == "(" (%\n%
               set setA.funcLevel=1%\n%
            ) else (%\n%
               if "!setA.char!" == "=" set setA.opPos=%%c^& set setA.namePos=%\n%
               if "!setA.char!" == "*" set setA.opPos=%%c^& set setA.namePos=%\n%
               if "!setA.char!" == "~" set setA.opPos=%%c^& set setA.namePos=%\n%
            )%\n%
         ) else if defined setA.parenLevel (%\n%
            if "!setA.char!" == ")" (%\n%
               set /A setA.parenLevel-=1%\n%
               if !setA.parenLevel! == 0 set setA.parenLevel=%\n%
            ) else if "!setA.char!" == "(" (%\n%
               set /A setA.parenLevel+=1%\n%
            )%\n%
         ) else if defined setA.opPos (%\n%
            if "!setA.char!" == "(" (%\n%
               set setA.parenLevel=1%\n%
               set setA.opPos=%\n%
            ) else if not "!setA.char!" == " " (%\n%
               set setA.namePos=%%c%\n%
               set setA.opPos=%\n%
            )%\n%
         ) else (%\n%
            if "!setA.char!" == "=" set setA.opPos=%%c%\n%
            if "!setA.char!" == "*" set setA.opPos=%%c%\n%
            if "!setA.char!" == "~" set setA.opPos=%%c%\n%
         )%\n%
      )%\n%
      rem 2- Call setA.F# functions and replace they by their return values %\n%
      set setA.copyExpr=%\n%
      set setA.lastStartCopy=0%\n%
      for /L %%i in (1,1,!setA.N!) do (%\n%
         for /F "tokens=1,2" %%a in ("!setA.F%%i!") do (%\n%
            set setA.func=!setA.expr:~%%a,%%b!%\n%
            set /A setA.lastSizeCopy=%%a-setA.lastStartCopy%\n%
            set /A setA.newStartCopy=%%a+%%b+1%\n%
         )%\n%
         for /F "tokens=1* delims=(" %%a in ("!setA.func!") do call %%a %%b setA.R=%\n%
         for /F "tokens=1,2" %%a in ("!setA.lastStartCopy! !setA.lastSizeCopy!") do (%\n%
            set "setA.copyExpr=!setA.copyExpr!!setA.expr:~%%a,%%b!!setA.R!"%\n%
         )%\n%
         set setA.lastStartCopy=!setA.newStartCopy!%\n%
      )%\n%
      set /A setA.lastSizeCopy=setA.exprLen-setA.lastStartCopy+1%\n%
      for /F "tokens=1,2" %%a in ("!setA.lastStartCopy! !setA.lastSizeCopy!") do (%\n%
         set "setA.copyExpr=!setA.copyExpr!!setA.expr:~%%a,%%b!"%\n%
      )%\n%
      rem 3- Execute SET /A command on final expression %\n%
      set /A !setA.copyExpr!%\n%
   ) else set setA.expr=


setlocal EnableDelayedExpansion

set "string=this has a length of 23"
%set/a% length=:StrLen(string)
echo The result is: %length%
%set/a% sum=:StrLen(string) + :StrLen("And this have 16")
echo The sum of two lengths is: %sum%
%set/a% result=:StrLen(String)*1000 / sum
echo The first lenght times 1000 divided by the previous sum is: %result%
goto :EOF


:StrLen string [result=[adjust]]
setlocal enableDelayedExpansion
set str=%1
set str=!str:"= !
if "!str:~0,1!" == " " (
    set "str=0%~1"
) else (
    set "str=0!%1!"
)
set len=0
for /L %%A in (12,-1,0) do (
   set /A "len|=1<<%%A"
   for %%B in (!len!) do if "!str:~%%B,1!" == "" set /A "len&=~1<<%%A"
)
for %%v in (!len!) do endlocal&if not "%2" == "" (set /A "%2=%%v%3") else echo %%v
exit /B
Results:
Output wrote: The result is: 23
The sum of two lengths is: 39
The first lenght times 1000 divided by the previous sum is: 589
You must be aware that SET/A macro does NOT include the usual SETLOCAL/ENDLOCAL commands.

The original SET /A command may assign values to several variables in the same expression:

Code: Select all

SET /A var1=1*2+3, var2=123*456/321, var3=var1+var2
To do the same thing in a local environment, SET/A macro would need to complete a much more detailed analysis of the expression and end with an ENDLOCAL command followed by several variable assignments; this behavior is beyond its original design. If SETLOCAL/ENDLOCAL commands are omitted in SET/A macro, the variable assignments in its expression will be directly done in the same environment of the caller program.

To avoid modification of local variables, all variable names used in SET/A macro have the "setA." prefix. However, if %set/a% macro would be used in a nested way, the function that create the nesting MUST include a SETLOCAL/ENDLOCAL pair, as described below.

EDIT: I slightly modified StrLen function so it can get the string both as variable name or "constant" in quotes.

Antonio
Last edited by Aacini on 01 Nov 2012 10:28, edited 4 times in total.

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

SET/A: Macro to expand function results in SET /A expression

#2 Post by Aacini » 27 Dec 2011 00:09

2- Library of functions to be used in SET/A macro.

Let's define some stuff designed to be used with SET/A macro. Note that these functions does not check for any errors in order to keep them simple and fast. For example:

Code: Select all

:NEG N R=-N
set /A "%2=-(%~1)"
exit /B

:ABS N R=ABS(N)
set /A "%2=%~1"
if !%2! LSS 0 set /A %2=-%2
exit /B
Et cetera, the complete listing is shown later; it includes ISQRT function that get the integer square root of a number.

Code: Select all

:IHYPOT X Y R=SQRT(X^2+Y^2)
%set/a% %3=ISQRT((%~1)*(%~1)+(%~2)*(%~2))
exit /B
Note that when a program use IHYPOT, i.e.: %set/a% Radius=IHYPOT(X,Y), IHYPOT in turn use %set/a% macro. Mean this detail that SET/A code is RE-ENTRANT? Not exactly. Execution of %set/a% in caller program use a different expansion of SET/A macro in IHYPOT function so, strictly speaking, both parts does not execute the same code. However, because SET/A macro does not include its own SETLOCAL/ENDLOCAL commands, IHYPOT MUST include them as previously explained.

Code: Select all

:IHYPOT X Y R=SQRT(X^2+Y^2)
setlocal
%set/a% r=ISQRT((%~1)*(%~1)+(%~2)*(%~2))
endlocal & set %3=%r%
exit /B
(Yes, I know that IHYPOT could just CALL ISQRT (%~1)*(%~1)+(%~2)*(%~2) %3= to achieve the same thing, but this way is not as interesting as the former one, right?)

Let's define now some Boolean stuff. To compare two numbers, the first idea is to define a series of relational functions:

Code: Select all

:EQU A B R=
set %3=0
if %1 EQU %2 set %3=1
exit /B

:NEQ A B R=
set %3=0
if %1 NEQ %2 set %3=1
exit /B

:Etc. A B R=
set %3=0
if %1 Etc. %2 set %3=1
exit /B
However, it is more convenient to define a unique function that achieve any desired test:

Code: Select all

:TEST A VS B R=
set %4=0
if %1 %2 %3 set %4=1
exit /B
This way, TEST function may be modified to achieve additional tests. For example, to add TEST c IN stringVar test:

Code: Select all

:TEST A VS B R=
setlocal EnableDelayedExpansion
set r=0
if /I %2 == IN goto else
   %set/a% "a=%~1"
   %set/a% "b=%~2"
   if !a! %2 !b! set r=1
   goto endif
:else
   REM TEST c IN stringVar
   rem Next lines use ASCII 255 character, NOT a space
   set "temp=!%3:%1= !"
   if not "!temp!" == "!%3!" (
      for /F "delims= " %%a in ("0!temp!") do set "temp=%%a"
      call :StrLen temp temp=-1
      for %%i in (!temp!) do if "!%3:~%%i,1!" == "%1" set /A r=%%i+1
   )
:endif
endlocal & set %4=%r%
exit /B

:NOT A R=!A
set %2=0
if %1 EQL 0 set %2=1
exit /B

:AND A B R=A&B
set /A %3=%1*%2
if %3 NEQ 0 set %3=1
exit /B

:OR A B R=A|B
set /A %3=%1+%2
if %3 NEQ 0 set %3=1
exit /B

:IFTE C X Y R=C?X:Y
if %1 NEQ 0 (
   set %4=%2
) else (
   set %4=%3
)
exit /B
Suppose we want to define a function to get the maximum of three values:

Code: Select all

:MAX3 P1 P2 P3 R=MAX3(P1,P2,P3)
setlocal
%set/a% r=IFTE("TEST(%1 GTR %2)",MAX(%1,%3),MAX(%2,%3))
endlocal & set %4=%r%
exit /B
Previous code needs to expand the value of three functions placed as parameters of a first one, but functions can not be enclosed in parentheses in original SET/A macro design. To solve this problem, take nested functions out from parentheses and use their return values in a separate line in the first function:

Code: Select all

:MAX3 P1 P2 P3 R=MAX3(P1,P2,P3)
setlocal
%set/a% P1gtrP2="TEST(%1 GTR %2)", MaxP1P3=MAX(%1,%3), MaxP2P3=MAX(%2,%3)
%set/a% r=IFTE(%P1gtrP2%,%MaxP1P3%,%MaxP2P3%)
endlocal & set %4=%r%
The four assignments can not be achieved in the same line because the way SET/A macro works: it FIRST execute the functions and replace return results, and THEN execute the final SET /A command. Another possible solution is to force an immediate set of the first variables via a function specifically designed to transgress SET/A macro rules.

Code: Select all

:IMMSET setVar=Expr Result=0
%set/a% %1=%2
set %3=0
exit /B

:MAX3 P1 P2 P3 R=MAX3(P1,P2,P3)
setlocal EnableDelayedExpansion
%set/a% IMMSET(P1gtrP2="TEST(%1 GTR %2)"), %\n%
           IMMSET(MaxP1P3=MAX(%1,%3)),     %\n%
           IMMSET(MaxP2P3=MAX(%2,%3)),     %\n%
        r=IFTE(P1gtrP2,MaxP1P3,MaxP2P3)
endlocal & set %4=%r%
exit /B
To become more standard, IMMSET function should pass the same assigned value to its %set/a% macro caller. It could also show the assigned value when requested, so we may use it to review a partial result in a long expression.

Code: Select all

:IMMSET [/V] setVar=Expr R=
setlocal
if /I %1 == /V shift
%set/a% "v=%~2"
if /I %0 == /V echo IMMSET: %1=%v%
endlocal & set /A %1=%3=%v%
exit /B
If a function is frequently used with nested functions as parameters, it may take the values of its parameters via a nested %set/a% macro instead of the usual SET /A (like previous IMMSET). All Boolean functions of this library were written this way (in the library listing, not in the example text above). For example:

Code: Select all

:IFTE C X Y R=C?X:Y
setlocal
%set/a% "c=%~1"
if %c% neq 0 (
   %set/a% "r=%~2"
) else (
   %set/a% "r=%~3"
)
endlocal & set %4=%r%
exit /B
The only restriction to nest %set/a% macro in a function is the size of the final command to execute. For example, previous IF include two expansions of SET/A macro, so is important to keep it below 4 KB. Otherwise, the function must be rewritten in the old fashion way, with GOTO's.

This way, the MAX3 function can be finally written as shown in the first example above...

There is still a problem if both the first level and nested functions may require to enclose a parameter in quotes because in this case we should need nested quotes to correctly write they. The only solution to this problem is to improve SET/A macro capabilites to parse and execute nested functions. I am currently working on this point...

You may easily test any library function with this code:

Code: Select all

echo/
echo Available values for character testing:
echo/
echo set digits=0123456789
echo set upcase=ABCDEFGHIJKLMNOPQRSTUVWXYZ
echo set lowcase=abcdefghijklmnopqrstuvwxyz
echo/
set digits=0123456789
set upcase=ABCDEFGHIJKLMNOPQRSTUVWXYZ
set lowcase=abcdefghijklmnopqrstuvwxyz
:loop
   set expr=
   set /P expr=Expression:
   if not defined expr goto :EOF
   %set/a% result=%expr%
   echo Result:     %result%
goto loop
Some examples:
Output wrote:Available values for character testing:

set digits=0123456789
set upcase=ABCDEFGHIJKLMNOPQRSTUVWXYZ
set lowcase=abcdefghijklmnopqrstuvwxyz

Expression: :StrLen("Ten letters")
Result: 11
Expression: :StrLen("Nineteen characters")
Result: 19
Expression: :StrLen(digits)*10+:StrLen(upcase)
Result: 126
Expression: :NEG(1)
Result: -1
Expression: result*(5*8+3)
Result: -43
Expression: :ABS(result)
Result: 43
Expression: :MAX(result,2*5+1)
Result: 43
Expression: :MIN(result,2*5+1)
Result: 11
Expression: result*result
Result: 121
Expression: result*result
Result: 14641
Expression: result*result
Result: 214358881
Expression: :ISQRT(result)
Result: 14641
Expression: :ISQRT(result)
Result: 121
Expression: :ISQRT(result)
Result: 11
Expression: :RANDOMAB(50*2,40*3)
Result: 105
Expression: :RANDOMAB(50*2,40*3)
Result: 109
Expression: :RANDOMAB(50*2,40*3)
Result: 103
Expression: :FACTORIAL(10)
Result: 3628800
Expression: :IPOW(2,10)
Result: 1024
Expression: result*1024
Result: 1048576
Expression: result*1024
Result: 1073741824
Expression: :IPOW(2,30)
Result: 1073741824
Expression: :IPOW(result,-20,2)
Result: 1024
Expression: :IHYPOT(3,4)
Result: 5
Expression: :TEST(3*15 GTR 2*20)
Result: 1
Expression: :IFTE(result,3*15,2*20)
Result: 45
Expression: :IFTE(":TEST(3*15 GTR 2*20)",3*15,2*20)
Result: 45
Expression: :IMMSET(/V,R1=3*15)+:IMMSET(/V,R2=2*20)
IMMSET: R1=45
IMMSET: R2=40
Result: 85
Expression: :IFTE(":TEST(R1 GTR R2)",R1,R2)
Result: 45
Expression: :TEST(x IN digits)
Result: 0
Expression: :IMMSET(/V,IsLower=":TEST(x IN lowcase)")*:IMMSET(/V,IsUpper=":TEST(x IN upcase)")*:IFTE(":OR(IsLower,IsUpper)",":OUT(x is Letter)",":OUT(x is NOT letter)")
IMMSET: IsLower=24
IMMSET: IsUpper=0
OUT: x is Letter
Result: 0
Expression: :IMMSET(/V,IsLower=":TEST(X IN lowcase)")*:IMMSET(/V,IsUpper=":TEST(X IN upcase)")*:IFTE(":OR(IsLower,IsUpper)",":OUT(X is Letter)",":OUT(X is NOT letter)")
IMMSET: IsLower=0
IMMSET: IsUpper=24
OUT: X is Letter
Result: 0
Expression: :IMMSET(/V,IsLower=":TEST(5 IN lowcase)")*:IMMSET(/V,IsUpper=":TEST(5 IN upcase)")*:IFTE(":OR(IsLower,IsUpper)",":OUT(5 is Letter)",":OUT(5 is NOT letter)")
IMMSET: IsLower=0
IMMSET: IsUpper=0
OUT: 5 is NOT letter
Result: 0
Expression: :IFTE(":TEST(5 IN digits)",":OUT(5 is Digit)",":OUT(5 is NOT Digit)")
OUT: 5 is Digit
Result: 0
Expression: :CHOOSE(1,"31 28 31 30 31 30 31 31 30 31 30 31")
Result: 31
Expression: :CHOOSE(2,"31 28 31 30 31 30 31 31 30 31 30 31")
Result: 28
Expression: :CHOOSE(4,"31 28 31 30 31 30 31 31 30 31 30 31")
Result: 30
Expression:
Below is the complete library functions listing.

Code: Select all

::SET/A Macro basic library functions
::Antonio Perez Ayala
::Dec/26/2011 - Arithmetic, Boolean and some Special functions.


REM Arithmetic functions

:NEG N R=-N
set /A "%2=-(%~1)"
exit /B

:ABS N R=ABS(N)
set /A "%2=%~1"
if !%2! LSS 0 set /A %2=-%2
exit /B

:MAX A B R=MAX(A,B)
setlocal EnableDelayedExpansion
set /A "r=(%~1), b=%~2"
if %b% GTR %r% set r=%b%
endlocal & set %3=%r%
exit /B

:MIN A B R=MIN(A,B)
setlocal EnableDelayedExpansion
set /A "r=(%~1), b=%~2"
if %b% LSS %r% set r=%b%
endlocal & set %3=%r%
exit /B

:RANDOMAB A B R=RANDOM in A..B range
set /A "%3=((%~2)-(%~1)+1)*%random%/32768+(%~1)"
exit /B
 
:FACTORIAL N R=N!
set %2=1
for /L %%i in (2,1,%1) do set /A %2*=%%i
exit /B

:IPOW N E [B] R=N^E
REM If the Exponent is negative, the Base must be given
setlocal EnableDelayedExpansion
set /A "n=(%~1), e=(%~2)"
if %e% geq 0 (
   set v=%3
   set r=1
   for /L %%i in (1,1,%e%) do set /A r*=n
) else (
   set v=%4
   set r=%n%
   for /L %%i in (%e%,1,-1) do set /A r/=%3
)
endlocal & set %v%=%r%
exit /B

:ISQRT N R=Integer Square Root(N)
setlocal EnableDelayedExpansion
set /A "n=%~1"
call :StrLen n d=/2
if %n% lss 36 (set r0=5) else set /A r0=n/!n:~0,%d%!
:isqrtLoop
   set /A r1=(n/r0+r0)/2
   if %r1% geq %r0% goto isqrtEnd
   set r0=%r1%
   goto isqrtLoop
:isqrtEnd
endlocal & set %2=%r0%
exit /B

:IHYPOT X Y R=SQRT(X^2+Y^2)
call :ISQRT "(%~1)*(%~1)+(%~2)*(%~2)" %3=
exit /B


REM Boolean functions

:TEST A VS B R=
setlocal EnableDelayedExpansion
set r=0
if /I %2 == IN goto else
   %set/a% "a=%~1"
   %set/a% "b=%~2"
   if !a! %2 !b! set r=1
   goto endif
:else
   REM TEST strConst IN stringVar
   rem Next lines use ASCII 255 character, NOT a space
   set "temp=!%3:%~1= !"
   if not "!temp!" == "!%3!" (
      for /F "delims= " %%a in ("0!temp!") do set "temp=%%a"
      call :StrLen temp temp=-1
      call :StrLen "%~1" const=
      for /F "tokens=1,2" %%i in ("!temp! !const!") do if "!%3:~%%i,%%j!" == "%1" set /A r=%%i+1
   )
:endif
endlocal & set %4=%r%
exit /B

:NOT A R=!A
setlocal
%set/a% "a=%~1"
set r=0
if %a% eql 0 set r=1
endlocal & set %2=%r%
exit /B

:AND A B R=A&B
setlocal
%set/a% "a=%~1"
%set/a% "b=%~2"
set /A r=a*b
if %r% neq 0 set r=1
endlocal & set %3=%r%
exit /B

:OR A B R=A|B
setlocal
%set/a% "a=%~1"
%set/a% "b=%~2"
set /A r=a+b
if %r% neq 0 set r=1
endlocal & set %3=%r%
exit /B

:IFTE C X Y R=C?X:Y
setlocal
%set/a% "c=%~1"
if %c% neq 0 (
   %set/a% "r=%~2"
) else (
   %set/a% "r=%~3"
)
endlocal & set %4=%r%
exit /B


REM Special functions

:IMMSET [/V] setVar=Expr R=
setlocal
if /I %1 == /V shift
%set/a% "v=%~2"
if /I %0 == /V echo IMMSET: %1=%v%
endlocal & set /A %1=%3=%v%
exit /B

:OUT Message R=0
setlocal
set message=%1
:outLoop
   shift
   if "%2" == "" goto outEndloop
   set message=%message% %1
   goto outLoop
:outEndloop
echo OUT: %message%
endlocal & set %1=0
exit /B

:CHOOSE I "N1 N2 ..." R=N%I%
for /F "tokens=%1" %%a in (%2) do set /A %3=%%a
exit /B
Last edited by Aacini on 10 Jan 2012 23:52, edited 1 time in total.

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

SET/A: Macro to expand function results in SET /A expression

#3 Post by Aacini » 03 Jan 2012 19:38

3- Fractional (fixed point) operations using integer numbers.

Arithmetic operations of SET /A command use 32-bits signed numbers with this range of values: -2147483648 to 2147483647. If a certain result requires less integer digits, the rest may be used for fractional part. To do that, just choose a number of fractional digits and preserve it throughout the operations. We may develop a set of functions that manage fixed point numbers written in any way (even as floating point numbers with an exponent of ten) and adjust them as needed, but let's start from a simpler approach. The functions below assume that the numbers have the right value, so is up to us to write them correctly; this way, the functions are simpler and faster.

These are the rules to achieve FixedPoint operations using integer numbers:

  • "ONE" variable must contain a 1 followed by the right number of decimal zeros; this variable is used as the FP base value in several functions.
  • To convert a (well-written) FP number to integer, just remove the decimal point and delete left zeros.
  • If two FP numbers are added or subtracted, the result is correct.
  • If a FP number is multiplied or divided by an integer, the result is correct.
  • To multiply two FP numbers, divide the result by ONE: SET /A MUL=A*B/ONE.
  • To divide two FP numbers, multiply the first by ONE: SET /A DIV=A*ONE/B.

Code: Select all

::Jan/03/2012 - Fixed Point operations using integer numbers

:FPTOINT I.F INT=
setlocal EnableDelayedExpansion
set num=%1
set sign=
if %num:~0,1% == - set num=%num:~1%& set sign=-
set num=%num:.=%
:removeLeftZeros
   if %num:~0,1% == 0 set num=%num:~1%& if defined num goto removeLeftZeros
if not defined num set num=0& set sign=
endlocal & set %2=%sign%%num%
exit /B

:INTTOFP INT [I.F=]
setlocal EnableDelayedExpansion
set /A "num=%~1"
set sign=
if %num% lss 0 set /A num=-num & set sign=-
call :StrLen One digits=-1
call :StrLen num numLen=
for /L %%d in (%numLen%,1,%digits%) do set num=0!num!
set num=%sign%!num:~0,-%digits%!.!num:~-%digits%!
endlocal & if "%2" == "" (echo %num%) else set %2=%num%
exit /B

:FPMULFP A B R=A*B
set /A "%3=(%~1)*(%~2)/One"
exit /B

:FPDIVFP A B R=A/B
set /A "%3=(%~1)*One/(%~2)"
exit /B

:FPMUL_DIVFP A B C R=A*B/C
set /A "%4=(%~1)*(%~2)/(%~3)"
exit /B

:FPSQRT N R=SQRT(N)
call :ISQRT "(%~1)*One" %2=
exit /B

:FPHYPOT X Y R=SQRT(X^2+Y^2)
call :FPSQRT "(%~1)*(%~1)/One+(%~2)*(%~2)/One" %3=
exit /B
Note that the multiplication must always be done first; if the division would be achieved first, significant digits would be lost. This order limits the number of integer digits because the multiplication result must fit in the 32-bits signed value. FPMUL_DIVFP function provide a more precise result than two separated MUL and DIV.

Some examples:

Code: Select all

set One=100
%set/a% Result=:FPTOINT(1.25)*4-:FPTOINT(0.30)
call :INTTOFP Result Result=
echo 1.25 * 4 - 0.30 = %Result%

set One=1000000000
call :INTTOFP One/3 OneThird=
echo 1/3 with 9 decimals: %OneThird%

set One=1000000
call :INTTOFP 355*One/113 pi=
echo 355/113 approximation to PI with 6 decimals: %pi%

set One=10000
%set/a% SqrtOf2=:FPSQRT(2*One)
call :INTTOFP SqrtOf2 SqrtOf2=
echo Square root of 2 with 4 decimals: %SqrtOf2%

set One=1000
%set/a% Hypot=:FPHYPOT(3*One,4*One)
call :INTTOFP Hypot Hypot=
echo Hypotenuse of 3 and 4 catetos with 3 decimals: %Hypot%

echo/
echo Iterative calculation of number e:
set One=100000000
set /A num=0, fac=1, summation=0
echo #- fac  term=1/fac   summation
:nextTerm
   set /A term=One/fac, summation+=term
   echo %num%- %fac%     %term%    %summation%
   set /A num+=1, fac*=num
   if %term% gtr 0 goto nextTerm
call :INTTOFP summation e=
echo Number e after %num% terms with 8 decimals: %e%
goto :EOF
Results:

Code: Select all

1.25 * 4 - 0.30 = 4.70
1/3 with 9 decimals: 0.333333333
355/113 approximation to PI with 6 decimals: 3.141592
Square root of 2 with 4 decimals: 1.4142
Hypotenuse of 3 and 4 catetos with 3 decimals: 5.000

Iterative calculation of number e:
#- fac  term=1/fac   summation
0- 1     100000000    100000000
1- 1     100000000    200000000
2- 2     50000000    250000000
3- 6     16666666    266666666
4- 24     4166666    270833332
5- 120     833333    271666665
6- 720     138888    271805553
7- 5040     19841    271825394
8- 40320     2480    271827874
9- 362880     275    271828149
10- 3628800     27    271828176
11- 39916800     2    271828178
12- 479001600     0    271828178
Number e after 13 terms with 8 decimals: 2.71828178

jeb
Expert
Posts: 1041
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: SET/A: Macro to expand function results in SET /A expres

#4 Post by jeb » 11 Jan 2012 13:45

+1

Hi Aacini,

absolute cool solution 8)
You create a new language inside of the batch, this way you could change the complete syntax or create a complete new one.
B#, Batch-Sharp :D

I thought about writing an own interpreter with batch, but I never thought to interpret "just in line" :shock:

really impressive :!:

jeb

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

Re: SET/A: Macro to expand function results in SET /A expres

#5 Post by Aacini » 13 Jan 2012 20:40

jeb wrote:absolute cool solution 8)
Thanks a lot, jeb! :D
jeb wrote:You create a new language inside of the batch, this way you could change the complete syntax or create a complete new one.
In my opinion, this macro feature is much more important than the faster execution. Another example of this new syntax is WHILE macro.
jeb wrote:B#, Batch-Sharp :Djeb
I prefer Batch++, that is: "Post-Incremented Batch" :wink:

Post Reply