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=8 SET /A Result *= 2 

which is a short notation for:  
SET Result=8 SET /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):
@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
@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, rightclick 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. 
There is a severe limitation in batch math: it can only handle 32bit integers.
In Windows NT 4 and possibly 2000, the limitation is even worse: it can only handle unsigned 32bit integers.
Workarounds for the 32bit limitation include:
Workaround #1 can be used to add up disk space, for example (relevant code marked red):
@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 /AD /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, rightclick 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 CALL :ADD _FIRST _SECOND _ANSWER ECHO !_ANSWER! ENDLOCAL ENDLOCAL GOTO :END :ADD ::ADD NUMBERS LARGER THAN 32BITS ::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. ::CopyRight Brian Williams 8/29/2009 :: _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
@ECHO OFF SETLOCAL ENABLEEXTENSIONS SETLOCAL ENABLEDELAYEDEXPANSION SETLOCAL SET _FIRST=%1 SET _SECOND=%2 CALL :ISLARGER _FIRST _SECOND _ANSWER ECHO !_ANSWER! 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. ::CopyRight Brian Williams 5/18/2009 :: _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
@ECHO OFF SETLOCAL ENABLEEXTENSIONS SETLOCAL ENABLEDELAYEDEXPANSION SETLOCAL SET _FIRST=%1 SET _SECOND=%2 CALL :MULTIPLY _FIRST _SECOND _ANSWER ECHO !_ANSWER! GOTO :END :MULTIPLY ::MULTIPLY NUMBERS LARGER THAN 32BITS ::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. ::CopyRight Brian Williams 5/18/2009 :: _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, rightclick on the link and choose "Save target as" (or whatever comes closest). 
Perfect, but quite complex.
Workaround #3, other scripting languages, is selfexplanatory.
I have often used temporary scripts, created "onthefly" 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 PowerShell scripts are often restricted for security reasons.
Temporary VBScript or JSCript scripts need to be saved in temporary files before they can be "executed".
PowerShell code doesn't need to be saved to a script file first, as Kevin Ridenhour, L SFC RET, taught me:
@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 /AD /C /S "%%~A"') DO ( IF %%B NEQ 0 SET DirSize=%%C ) IF !DirSize! GTR 0 ( FOR /F %%B IN ('powershell C "!Total! + !DirSize!"') DO SET Total=%%B FOR /F "delims=" %%B IN ('powershell C "\""{0,12:F2}\"" f ( !DirSize! / 1MB )"') DO SET DirSize=%%B ) ECHO.!DirSize! MB %%~fA ) ECHO. FOR /F "delims=" %%A IN ('powershell C "\""{0,12:F2}\"" f ( %Total% / 1MB )"') DO SET Total=%%A ECHO.%Total% MB Total ENDLOCAL POPD
Notes  3:  To download the code, rightclick on the link and choose "Save target as" (or whatever comes closest). 
4:  Hover your mouse pointer over the code to see it explained. 
Well, what I meant to demonstrate is that you can use PowerShell oneliners 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 a very important argument to choose for batch files with embedded PowerShell commands: 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.
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 /A Whole = %Result% / 100 SET /A "Fraction = %Result% %% 100" SET Result=%Whole%.%Fraction%
This may break on the 32bit limit, though.
In general, for floating point math I would recommend using other scripting languages.
page last modified: 20220222