Definition and use of arithmetic "functions" in Batch files
Posted: 26 Oct 2015 21:16
In this thread I present a method that allows to use arithmetic expressions in Batch files in a way rather similar to arithmetic functions of other programming languages. Let's start with a ":Rand x result" subroutine that in regular Batch code would be written this way:
... and used this way:
This code can be converted into a "function" via a variable with "function(arg)" name and the arithmetic expression that calculate the result as value. For example:
To invoke this "function", you should use this format:
Although this example seems to be practically the same than previous one, the function method run faster because it does not require CALL command. Besides, a "function" called this way may work as a true function, so it may be placed into a larger expression and the parameter may be an arithmetic expression. To successfully achieve these points, just enclose in parentheses the whole expression and the parameter. For example:
Below there are two simple function examples: Sign function returns -1 if the argument is negative, and 1 otherwise; Abs function use the sign of the argument to get its absolute value.
An important feature in the definition of these functions is the comma operator that allows to perform a series of partial calculations with different variables and use they in the final calculation. For example, Abs function may be defined as shown below; if it is called with a large expression, the argument is evaluated just once:
There are several tricks that allows to use additional features in the definition of a function. For example, the value of "a-123" is positive or zero if "a" is greater or equal 123, so "((a-123)>>31)+1" subexpression is 1 if "a" is greater or equal 123 and 0 otherwise. This method allows to implement an if-then-else feature equivalent to C++ "?:" conditional operator via a large expression comprised of three parts: previous subexpression (that we may call "condition"), the "then" value multiplied by the condition and the "else" value multiplied by the NOT condition, and add the two last parts.
In the manipulation of the 0 and 1 values returned by conditions the multiplication may work as AND boolean operator, addition may work as OR, and exclamation-mark works as NOT. The expression "!!value" is 0 if "value" is 0 and is 1 otherwise, so it may be used to reduce several values accumulated by addition operator (OR) into a single 1. You may also use "&", "|" and "^" bitwise operators for AND, OR and XOR boolean operators, respectively, that directly gives proper results when the values are 0 or 1.
The Max function below get the maximum of two values. We can use one parameter only, so we need to place the other value in a variable:
Note that it is necessary to escape the exclamation-mark (NOT operator) with three carets if delayed expansion is enabled when the function is defined.
In certain cases we can simulate two arguments in a function if algebraic manipulations allows it; this depends on the specific formula used in the calculation. The function below is the Max function with two arguments:
In this case we insert the missing subtraction operation in the invocation itself:
The explanation of this code "is obvious" (as jeb would say! ), just use a little basic algebra... Note that in Batch files, differently than in other languages, the same name may be used in both a variable and a function (of course, the explanation is that "name" and "name(x)" are different variables).
The conversion functions from HHMMSSCC time to centiseconds and viceversa are straightforward:
The conversion functions from YYYYMMDD date to Julian Day Number and viceversa are very useful. You may review the original Batch code for these functions here.
Below there is a complete Batch file that includes all previous examples, so you don't need to copy each one of they in order to test them.
Enjoy it!
Antonio
Code: Select all
:Rand x result
set /A %2=%1 * %random% / 32768 + 1
exit /B
Code: Select all
call :Rand 25 num
echo Random number between 1 and 25: %num%
Code: Select all
set "Rand(x)=x * ^!random^! / 32768 + 1"
Code: Select all
set /A num=%Rand(x):x=25%
echo Random number between 1 and 25: %num%
Code: Select all
set "Rand(x)=( (x)*^!random^!/32768+1 )"
set /A num=%Rand(x):x=5*6% + 10
echo Random number between 11 and 40: %num%
Code: Select all
set "Sign(x)=((x)>>31|1)"
set "Abs(x)=(((x)>>31|1)*(x))"
Code: Select all
set "Abs(x)=(a=(x), (a>>31|1)*a)
In the manipulation of the 0 and 1 values returned by conditions the multiplication may work as AND boolean operator, addition may work as OR, and exclamation-mark works as NOT. The expression "!!value" is 0 if "value" is 0 and is 1 otherwise, so it may be used to reduce several values accumulated by addition operator (OR) into a single 1. You may also use "&", "|" and "^" bitwise operators for AND, OR and XOR boolean operators, respectively, that directly gives proper results when the values are 0 or 1.
The Max function below get the maximum of two values. We can use one parameter only, so we need to place the other value in a variable:
Code: Select all
REM Max1(x)= if (x geq y) then x else y
set "Max1(x)=( ?=((x-y)>>31)+1, ?*x + ^^^!?*y )"
set /P "pair=Enter two numbers separated by space: "
for /F "tokens=1,2" %%a in ("%pair%") do set /A "a=%%a, b=%%b"
set /A "y=b, max=%Max1(x):x=a%" // or: set /A "max=(y=b, %Max1(x):x=a%)"
echo The max is: %max%
In certain cases we can simulate two arguments in a function if algebraic manipulations allows it; this depends on the specific formula used in the calculation. The function below is the Max function with two arguments:
Code: Select all
set "Max(x,y)=( ?=((x,y)>>31)+1, ?*(2*x,y-(x,y)) + ^^^!?*(x,y-(x,y*2)) )"
Code: Select all
set /A "max=%Max(x,y):x,y=one-two%"
echo The max of %one% and %two% is: %max%
The conversion functions from HHMMSSCC time to centiseconds and viceversa are straightforward:
Code: Select all
set "HMSCtoCSec(HMSC)=( a=(HMSC), h=a/1000000, a%%=1000000, m=a/10000, a%%=10000, s=a/100, c=a%%100, ((h*60+m)*60+s)*100+c )"
set "CSecToHMSC(CSec)=( a=(CSec), c=a%%100, a/=100, s=a%%3600, a/=3600, m=a%%60, h=a/60, h*1000000+m*10000+s*100+c )"
echo %time% & for /F "tokens=1-4 delims=:." %%a in ("%time%") do set "startTime=%%a%%b%%c%%d"
place here any process
echo %time% & for /F "tokens=1-4 delims=:." %%a in ("%time%") do set "endTime=%%a%%b%%c%%d"
set /A "elapsed=%TimeToCSec(HMSC):HMSC=endTime% - %TimeToCSec(HMSC):HMSC=startTime%"
echo Elapsed time: %elapsed:~0,-2%.%elapsed:~-2% seconds
Code: Select all
set "DateToJDN(YMD)=( a=(YMD), y=a/10000, a%%=10000, m=a/100, d=a%%100, a=(m-14)/12, (1461*(y+4800+a))/4+(367*(m-2-12*a))/12-(3*((y+4900+a)/100))/4+d-32075 )"
set "JDNtoDate(JDN)=( a=(JDN), l=a+68569,n=(4*l)/146097,l=l-(146097*n+3)/4,i=(4000*(l+1))/1461001,l=l-(1461*i)/4+31,j=(80*l)/2447,d=l-(2447*j)/80,l=j/11,m=j+2-(12*l),y=100*(n-49)+i+l,y*10000+m*100+d )"
set /P "otherDate=Enter a date in YYYY/MM/DD format: "
for /F "tokens=1-3 delims=/" %%a in ("%date%") do set "today=%%c%%a%%b"
set /A "days=%DateToJDN(YMD):YMD=today% - %DateToJDN(YMD):YMD=!otherDate:/=!%"
echo/
echo There are %days% days between that date and today
Code: Select all
@echo off
setlocal EnableDelayedExpansion
rem Definition and use of arithmetic "functions" in Batch files
rem http://www.dostips.com/forum/viewtopic.php?f=3&t=6744
rem Antonio Perez Ayala
:: Define Rand(x) function
set "Rand(x)=x * ^!random^! / 32768 + 1"
:: Use Rand(x) function
set /A num=%Rand(x):x=25%
echo Random number between 1 and 25: %num%
:: Define Rand(x) to be used as a true function
set "Rand(x)=( (x)*!random!/32768+1 )"
set /A num=%Rand(x):x=5*6% + 10
echo Random number between 11 and 40: %num%
:: Define Max1(x) function with one parameter and one variable
REM Max1(x)= if (x geq y) then x else y
set "Max1(x)=( ?=((x-y)>>31)+1, ?*x + ^^^!?*y )"
echo/
echo Max1(x) function test
:nextPair1
set "pair="
set /P "pair=Enter two numbers separated by space: "
if not defined pair goto endPair1
for /F "tokens=1,2" %%a in ("%pair%") do set /A "a=%%a, b=%%b"
set /A "max=(y=b, %Max1(x):x=a%)"
echo The max is: %max%
goto nextPair1
:endPair1
:: Define Max(x,y) and Min(x,y) functions with two parameters
set "Max(x,y)=( ?=((x,y)>>31)+1, ?*(2*x,y-(x,y)) + ^^^!?*(x,y-(x,y*2)) )"
set "Min(x,y)=( ?=((x,y)>>31)+1, ?*(x,y-(x,y*2)) + ^^^!?*(2*x,y-(x,y)) )"
echo/
echo Max(x,y) function test
:nextPair
set "pair="
set /P "pair=Enter two numbers separated by space: "
if not defined pair goto endPair
for /F "tokens=1,2" %%a in ("%pair%") do set /A "a=%%a, b=%%b"
set /A "max=%Max(x,y):x,y=a-b%"
echo The max is: %max%
goto nextPair
:endPair
:: Define HHMMSSCC time to centiseconds conversion function and viceversa
set "TimeToCSec(HMSC)=( a=(HMSC), h=a/1000000, a%%=1000000, m=a/10000, a%%=10000, s=a/100, c=a%%100, ((h*60+m)*60+s)*100+c )"
set "CSecToTime(CSec)=( a=(CSec), c=a%%100, a/=100, s=a%%60, a/=60, m=a%%60, h=a/60, h*1000000+m*10000+s*100+c )"
:: Define YYYYMMDD date to Julian Day Number conversion function and viceversa
set "DateToJDN(YMD)=( a=(YMD), y=a/10000, a%%=10000, m=a/100, d=a%%100, a=(m-14)/12, (1461*(y+4800+a))/4+(367*(m-2-12*a))/12-(3*((y+4900+a)/100))/4+d-32075 )"
set "JDNtoDate(JDN)=( a=(JDN), l=a+68569,n=(4*l)/146097,l=l-(146097*n+3)/4,i=(4000*(l+1))/1461001,l=l-(1461*i)/4+31,j=(80*l)/2447,d=l-(2447*j)/80,l=j/11,m=j+2-(12*l),y=100*(n-49)+i+l,y*10000+m*100+d )"
echo/
echo %time% & for /F "tokens=1-4 delims=:." %%a in ("%time%") do set "startTime=%%a%%b%%c%%d"
set /P "birthDate=Enter your birthdate in YYYY/MM/DD format: "
echo %time% & for /F "tokens=1-4 delims=:." %%a in ("%time%") do set "endTime=%%a%%b%%c%%d"
set /A "elapsed=%TimeToCSec(HMSC):HMSC=endTime% - %TimeToCSec(HMSC):HMSC=startTime%"
echo/
echo You took %elapsed:~0,-2%.%elapsed:~-2% seconds to enter previous line
REM As usual, you must adjust the next line accordingly to your locale date format
for /F "tokens=1-3 delims=/" %%a in ("%date%") do set "today=%%c%%a%%b"
set /A "total=%DateToJDN(YMD):YMD=today% - %DateToJDN(YMD):YMD=!birthDate:/=!%, a=total*100, years=a/36525, a%%=36525, months=a/3060, days=(a%%3060)/100"
echo/
echo You are %total% days old (%years% years, %months% months and %days% days approx.)
Antonio