# Math in NT batch files

## Introduction

Basic integer math functions using SET /A were first introduced in Windows NT 4.
With SET /A we can:

 Add: SET /A Result = 12 + 4 Subtract: SET /A Result = 23 - 7 Multiply: SET /A Result = 8 * 2 Integer divide: SET /A Result = 33 / 2 Modulo divide: (1, 2) SET /A "Result = 66 %% 25" Shift right: (2) SET /A "Result = 128 >> 3" Shift left: SET /A "Result = 1 << 4" Bitwise AND: SET /A "Result = 48 & 23" Bitwise OR: SET /A "Result = 16 | 16" Bitwise XOR: SET /A "Result = 31 ^ 15" Group: SET /A "Result = ( 24 << 1 ) & 23" We can combine operator and assignment: SET Result=8SET /A Result *= 2 which is a short notation for: SET Result=8SET /A Result = %Result% * 2 And we aren't limited to decimal numbers: Octal: SET /A Result = 020 Decimal: SET /A Result = 16 Hexadecimal: SET /A Result = 0x10 Or any combination: SET /A Result = 010 + 0x20 - 24 The result is always returned as decimal, though.

By the way, in all the examples above, the value of environment variable `Result` will be 16.

 Notes 1: Use double percent signs in batch files, or a single percent sign on the command line. 2: Always use doublequotes if the expression contains one or more "special characters" (`%`, `&`, `<`, `>`, `|`, `ˆ`, `(` or `)`) or if the variable name is omitted (which displays the result directly on screen).

Missing functionality like exponentiation and (square) root can be emulated ("core" code marked red):

• Exp.bat
```@ECHO OFF
:: Exponentiation (positive integers only)
:: Usage: Exp num pow
:: E.g. EXP 2 10 = 1024
IF     "%~2"=="" EXIT /B 1
IF NOT "%~3"=="" EXIT /B 1

SETLOCAL ENABLEDELAYEDEXPANSION
SET /A Num = %~1
SET /A Pow = %~2
IF NOT %~1 EQU %Num% (ENDLOCAL & EXIT /B 1)
IF NOT %~2 EQU %Pow% (ENDLOCAL & EXIT /B 1)
IF %Num% LSS 0 (ENDLOCAL & EXIT /B 1)
IF %Pow% LSS 0 (ENDLOCAL & EXIT /B 1)
IF %Pow% EQU 0 (ECHO 1& ENDLOCAL & EXIT /B 0)
IF %Num% EQU 0 (ECHO 0& ENDLOCAL & EXIT /B 0)
SET Result=1
FOR /L %%A IN (1,1,%Pow%) DO SET /A Result *= %Num%
ECHO %Result%
ENDLOCAL```
• Sqrt.bat
```@ECHO OFF
:: Square root (integer results only)
:: Usage:  Sqrt num
:: E.g. SQRT 64 = 8
IF     "%~1"=="" EXIT /B 1
IF NOT "%~2"=="" EXIT /B 1

SETLOCAL ENABLEDELAYEDEXPANSION
SET /A Num = %~1
IF %Num% NEQ %~1 (ENDLOCAL & EXIT /B 1)
IF %Num% LSS   0 (ENDLOCAL & EXIT /B 1)
IF %Num% LSS   2 (ECHO %Num%& ENDLOCAL & EXIT /B 0)
SET Error=0
SET Found=0
SET /A "UBound = ( %Num% + 1 ) / 2"
FOR /L %%A IN (0,1,%UBound%) DO (
IF !Error! EQU 0 (
IF !Found! EQU 0 (
FOR /F %%B IN ('SET /A %%A * %%A') DO (
IF %~1 LSS %%B (
SET Error=1
) ELSE (
IF %~1 EQU %%B (
ECHO.%%A
SET Found=1
)
)
)
)
)
)
ENDLOCAL & EXIT /B %Error%```

The square root functionality is severely limited, it can only return square roots of squared integers.

 Notes 3: To download the code, right-click on the link and choose "Save target as" or "Save link as" or whatever comes closest. 4: Hover your mouse pointer over the code to see it explained.

## Limitations

There is a severe limitation in batch math: it can only handle 32-bit integers.

In Windows NT 4 and possibly 2000, the limitation is even worse: it can only handle unsigned 32-bit integers.

## Workarounds: 32-bit

Workarounds for the 32-bit limitation include:

1. dividing by 1000 (or any power of 10) by chopping off the last (3) digits
2. splitting up the numbers into separate decimal digits and perform all the math and carry logic "manually"
3. other scripting languages

Workaround #1 can be used to add up disk space, for example (relevant code marked red):

• "Chop" code example
```@ECHO OFF
:: Display the disk space in MB used by the subdirectories
:: of the directory represented by %1, plus a grand total
PUSHD "%~1"
SETLOCAL ENABLEDELAYEDEXPANSION
SET Total=0
SET Count=0
FOR /D %%A IN (*) DO (
SET DirSize=0
SET /A Count += 1
FOR /F "tokens=1,3" %%B IN ('DIR /A-D /-C /S "%%~A"') DO (
IF %%B NEQ 0 SET DirSize=%%C
)
IF !DirSize! GTR 0 SET DirSize=!DirSize:~0,-6!
IF "!DirSize!"=="" SET DirSize=0
SET /A Total += !DirSize!
SET DirSize=            !DirSize!
SET DirSize=!DirSize:~-12!
ECHO !DirSize! MB    %%~fA
)
ECHO.
SET /A Total = %Total% + %Count% / 2
SET Total=            %Total%
SET Total=%Total:~-12%
ECHO %Total% MB    Total
ENDLOCAL
POPD```
 Notes 3: To download the code, right-click on the link and choose "Save target as" (or whatever comes closest). 4: Hover your mouse pointer over the code to see it explained.

The trick is that each (big) number is treated as strings, then the rightmost 6 characters (digits) are chopped off, and only then the result is treated as a number.

This is a rather crude workaround, as it "rounds" all numbers before doing the math.
Adding half a MegaByte for each subdirectory (`%Count% / 2`) to `%Total%` does compensate for the truncations, though, so the grand total is more accurate than the individual numbers.
Note that the numbers don't represent "real" MegaBytes (1024 x 1024) buth rather Million Bytes (1000 x 1000).

Workaround #2 is perfectly demonstrated by Brian Williams' batch files:

```@ECHO OFF
SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION

SET _FIRST=%1
SET _SECOND=%2

ENDLOCAL
ENDLOCAL
GOTO :END

::SYNTAX: CALL ADD _VAR1 _VAR2 _VAR3
::VER 1.2
::hieyeque1@gmail.com - drop me a note telling me
::if this has helped you.  Sometimes I don't know if anyone uses my stuff
::Its free for the world to use.
::I retain the rights to it though, you may not copyright this
::to prevent others from using it, you may however copyright works
::as a whole that use this code.
::Just don't try to stop others from using this through some legal means.
:: _VAR1 = VARIABLE FIRST NUMBER TO ADD
:: _VAR2 = VARIABLE SECOND NUMBER TO ADD
:: _VAR3 = VARIABLE NUMBER RETURNED
SET _RESULT=
SETLOCAL
SET _NUM1=!%1!
SET _NUM2=!%2!
IF NOT DEFINED _NUM1 SET _NUM1=0
IF NOT DEFINED _NUM2 SET _NUM2=0
FOR /L %%A IN (1,1,2) DO (
FOR /L %%B IN (0,1,9) DO (
SET _NUM%%A=!_NUM%%A:%%B=%%B !
)
)
FOR %%A IN (%_NUM1%) DO SET /A _NUM1CNT+=1 & SET _!_NUM1CNT!_NUM1=%%A
FOR %%A IN (%_NUM2%) DO SET /A _NUM2CNT+=1 & SET _!_NUM2CNT!_NUM2=%%A
SET _NUM1=%_NUM1: =%
SET _NUM2=%_NUM2: =%
SET /A _DIGITS=%_NUM1CNT% + %_NUM2CNT%
IF %_DIGITS% LEQ 8 (
SET /A _RESULT=%_NUM1% + %_NUM2%
)
IF DEFINED _RESULT (ENDLOCAL & SET %3=%_RESULT%& GOTO :EOF)
IF %_NUM1CNT% GEQ %_NUM2CNT% (SET _MAXOPS=%_NUM1CNT%) ELSE (SET _MAXOPS=%_NUM2CNT%)
SET /A _MAXOPS=%_MAXOPS% - 1
IF %_NUM1CNT% GTR %_NUM2CNT% (
SET /A _ZEROS=%_NUM1CNT% - %_NUM2CNT%
FOR /L %%A IN (1,1,!_ZEROS!) DO SET _ZERO=!_ZERO!0
SET _NUM2=!_ZERO!!_NUM2!
)

IF %_NUM2CNT% GTR %_NUM1CNT% (
SET /A _ZEROS=%_NUM2CNT% - %_NUM1CNT%
FOR /L %%A IN (1,1,!_ZEROS!) DO SET _ZERO=!_ZERO!0
SET _NUM1=!_ZERO!!_NUM1!
)

FOR /L %%A IN (!_MAXOPS!,-1,0) DO (
SET /A _TMP=!_NUM1:~%%A,1! + !_NUM2:~%%A,1! !_PLUS! !_CO!
SET _CO=
SET _PLUS=
IF !_TMP! GTR 9 SET _CO=!_TMP:~0,1!& SET _TMP=!_TMP:~-1!& SET _PLUS=+
SET _RETURN=!_TMP!!_RETURN!
SET _TMP=
)
IF DEFINED _CO SET _RETURN=%_CO%%_RETURN%
SET _RESULT=%_RETURN%
IF DEFINED _RESULT (ENDLOCAL&SET %3=%_RESULT%)
GOTO :EOF

:END
```
• IsLarger.cmd
```@ECHO OFF
SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION
SETLOCAL
SET _FIRST=%1
SET _SECOND=%2
GOTO :END

:ISLARGER
::DETERMINE IF FIRST VAR IS LARGER THAN SECOND
::NUMBERS OF UP TO ~4000 DIGITS CAN BE COMPARED, NO MORE THAN ~8100 DIGITS COMBINED
::SYNTAX: CALL ISLARGER _VAR1 _VAR2 _VAR3
::hieyeque1@gmail.com - drop me a note telling me
::if this has helped you.  Sometimes I don't know if anyone uses my stuff
::Its free for the world to use.
::I retain the rights to it though, you may not copyright this
::to prevent others from using it, you may however copyright works
::as a whole that use this code.
::Just don't try to stop others from using this through some legal means.
:: _VAR1 = VARIABLE AGAINST WHICH WE SHALL COMPARE
:: _VAR2 = VARIABLE TO BE COMPARED
:: _VAR3 = VARIABLE WITH TRUE/FALSE RETURNED
SET _NUM1=!%1!
SET _NUM2=!%2!
SET _RESULT=%3
FOR /L %%A IN (1,1,2) DO (
FOR /L %%B IN (0,1,9) DO (
SET _NUM%%A=!_NUM%%A:%%B=%%B !
)
)
FOR %%A IN (!_NUM1!) DO SET /A _NUM1CNT+=1 & SET _!_NUM1CNT!_NUM1=%%A
FOR %%A IN (!_NUM2!) DO SET /A _NUM2CNT+=1 & SET _!_NUM2CNT!_NUM2=%%A
IF !_NUM1CNT! NEQ !_NUM2CNT! (
IF !_NUM1CNT! GTR !_NUM2CNT! (
SET !_RESULT!=TRUE
GOTO :EOF
) ELSE (
SET !_RESULT!=FALSE
GOTO :EOF
)
)
FOR /L %%A IN (1,1,!_NUM1CNT!) DO (
IF !_%%A_NUM1! NEQ !_%%A_NUM2! (
IF !_%%A_NUM1! GTR !_%%A_NUM2! (
SET !_RESULT!=TRUE
GOTO :EOF
) ELSE (
SET !_RESULT!=FALSE
GOTO :EOF
)
)
)
SET !_RESULT!=EQUAL
GOTO :EOF

:END

```
• Multiply.cmd
```@ECHO OFF
SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION
SETLOCAL
SET _FIRST=%1
SET _SECOND=%2
GOTO :END

:MULTIPLY
::MULTIPLY NUMBERS LARGER THAN 32-BITS
::SYNTAX: CALL MULTIPLY _VAR1 _VAR2 _VAR3
::VER 1.2 - Fixed ending carry over bug - Thanks Justin
::hieyeque1@gmail.com - drop me a note telling me
::if this has helped you.  Sometimes I don't know if anyone uses my stuff
::Its free for the world to use.
::I retain the rights to it though, you may not copyright this
::to prevent others from using it, you may however copyright works
::as a whole that use this code.
::Just don't try to stop others from using this through some legal means.
:: _VAR1 = VARIABLE AGAINST WHICH WE SHALL COMPARE
:: _VAR2 = VARIABLE TO BE COMPARED
:: _VAR3 = VARIABLE WITH TRUE/FALSE RETURNED
SET _NUM1=!%1!
SET _NUM2=!%2!
SET _RESULT=%3
FOR /L %%A IN (1,1,2) DO (
FOR /L %%B IN (0,1,9) DO (
SET _NUM%%A=!_NUM%%A:%%B=%%B !
)
)
FOR %%A IN (!_NUM1!) DO SET /A _NUM1CNT+=1 & SET _!_NUM1CNT!_NUM1=%%A
FOR %%A IN (!_NUM2!) DO SET /A _NUM2CNT+=1 & SET _!_NUM2CNT!_NUM2=%%A
IF !_NUM1CNT! EQU 1 IF !_NUM2CNT! EQU 1 (
SET /A !_RESULT!=!_NUM1! * !_NUM2!
GOTO :EOF
)
FOR /L %%B IN (!_NUM2CNT!,-1,1) DO (
FOR /L %%A IN (!_NUM1CNT!,-1,1) DO (
SET /A _TMP=!_%%B_NUM2! * !_%%A_NUM1! !_PLUS! !_CO!
SET _CO=
SET _PLUS=
IF !_TMP! GTR 9 SET _CO=!_TMP:~0,1!& SET _TMP=!_TMP:~-1!& SET _PLUS=+
SET _NUM3_%%B=!_NUM3_%%B!!_SPC!!_TMP!
SET _SPC=
SET _TMP=
)
IF DEFINED _CO SET _NUM3_%%B=!_NUM3_%%B! !_CO!& SET _CO=& SET _PLUS=
SET _NUM3_%%B=!_ZERO!!_NUM3_%%B!
SET _ZERO=0!_SPC1!!_ZERO!
SET _SPC1=
FOR %%A IN (!_NUM3_%%B!) DO (
SET /A _CNT+=1
FOR %%C IN (!_CNT!) DO SET _NUM4_%%C=!_NUM4_%%C!%%A+
)
SET _CNT=
)
FOR /F %%A IN ('SET _NUM4') DO SET /A _COLCNT+=1
FOR /L %%A IN (1,1,!_COLCNT!) DO SET /A _NUM5_%%A=!_NUM4_%%A:~0,-1!
FOR /L %%A IN (1,1,!_COLCNT!) DO (
IF DEFINED _CO SET /A _NUM5_%%A=!_NUM5_%%A! + !_CO!
SET _CO=
IF !_NUM5_%%A! GTR 9 (
SET _CO=!_NUM5_%%A:~0,-1!
SET _NUM6=!_NUM5_%%A:~-1!!_NUM6!
) ELSE (
SET _NUM6=!_NUM5_%%A!!_NUM6!
)
SET !_RESULT!=!_CO!!_NUM6!
)
GOTO :EOF

:END```
 Notes 3: To download the code, right-click on the link and choose "Save target as" (or whatever comes closest).

Perfect, but quite complex.

Workaround #3, other scripting languages, is self-explanatory.

I have often used temporary scripts, created "on-the-fly" by the batch file. Don't forget to clean up the "mess" afterwards.

The temporary script could be in any language supported on the computer. The most commonly supported scripting languages nowadays are VBScript and JScript. PowerShell comes close, but is not installed on every Windows computer yet.

Temporary VBScript or JSCript scripts need to be saved in temporary files before they can be "executed".

PowerShell scripts don't need to be saved to files first, as Kevin Ridenhour, L SFC RET, taught me:

• "Embedded PowerShell" code example
```@ECHO OFF
:: Display the disk space in MB used by the subdirectories
:: of the directory represented by %1, plus a grand total
PUSHD "%~1"
SETLOCAL ENABLEDELAYEDEXPANSION
SET Total=0
FOR /D %%A IN (*) DO (
SET DirSize=0
FOR /F "tokens=1,3" %%B IN ('DIR /A-D /-C /S "%%~A"') DO (
IF %%B NEQ 0 SET DirSize=%%C
)
IF !DirSize! GTR 0 (
FOR /F %%B IN ('powershell !Total! + !DirSize!') DO SET Total=%%B
FOR /F %%B IN ('powershell !DirSize! / 1048576') DO SET DirSize=%%B
FOR /F "tokens=1* delims=,." %%B IN ("!DirSize!") DO (
SET Whole=%%B
SET Fraction=%%C
SET DirSize=!Whole!.!Fraction:~0,2!
)
)
SET DirSize=            !DirSize!
SET DirSize=!DirSize:~-12!
ECHO !DirSize! MB    %%~fA
)
FOR /F %%A IN ('powershell %Total% / 1048576') DO SET Total=%%A
FOR /F "tokens=1* delims=,." %%B IN ("!Total!") DO (
SET Whole=%%B
SET Fraction=%%C
SET Total=!Whole!.!Fraction:~0,2!
)
ECHO.
SET Total=            %Total%
SET Total=%Total:~-12%
ECHO.%Total% MB    Total
ENDLOCAL
POPD```
 Notes 3: To download the code, right-click on the link and choose "Save target as" (or whatever comes closest). 4: Hover your mouse pointer over the code to see it explained.

The code has become rather complex, mainly because I ran into 2 problems using PowerShell like this:

1. `"{0:N2}" -f ( !DirSize! / 1048576 )` should divide `DirSize` by 1048576 (1MB) and display the result with 2 decimals; it does on the command line, but not in my batch files
2. If the system uses a comma for the decimal delimiter (my Dutch system does), `FOR` loops will interpret it as an additional delimiter in the list (why can't humanity standardize something as simple as a decimal delimiter?)

Well, what I meant to demonstrate is that you can use PowerShell one-liners in batch files without the need for (temporary) script files.

But why not just create a PowerShell script instead?

Good question.
For this particular example, it would probably be a better choice indeed.

But there may be valid arguments to choose for batch files with embedded PowerShell commands:

• Batch files usually are much faster than PowerShell
• PowerShell scripts ususally require code signing, whereas command line use does not

An alternative to PowerShell are PHP based batch files, but they require PHP to be installed.
PHP is extremely good at math.

## Workarounds: integers

There are no real workarounds that allow floating point math, except using other scripting languages.
The only exception may be if you have a limited and fixed number of decimals (e.g. 2), then you can just multiply everything by 100.
To display a decimal delimiter in the end results, concatenate the ineger divide by 100, followed by the decimal delimiter, followed by the modulo divide by 100:

```SET Whole = Result / 100
SET "Fraction = Result %% 100"
SET Result=%Whole%.%Fraction%```

This may break on the 32-bit limit, though.

In general, for floating point math I would recommend using other scripting languages.