Batch file multiply positive variables return a negative number - batch-file

I've been working on a Batch polygon area calculator and I got a problem.
I need to multiply 2 variables, but sometimes it return a negative number if the two positive variables are large.
Here's an example: 999999*999999 returns -729379967.
Code goes below:
REM Calc square area
:PolySqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Set /P "InputPolygonCalSqu=Enter one of the line's length in cm :"
Set /A SquArea=InputPolygonCalSqu * InputPolygonCalSqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Echo The area of this square is %SquArea% cm2.
Pause
Goto :PolygonCal
It seemed the command
Set /A SquArea="InputPolygonCalSqu * InputPolygonCalSqu
doesn't calculate properly.

As others already pointed out, a batch-file natively supports 32-bit signed integer arithmetics only.
The following code constitutes a work-around for multiplying non-negative numbers greater than the limit of 232 − 1 = 2147483647, using pure batch-file commands (let us call it multiply.bat):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define arguments here:
set "NUM1=%~1"
set "NUM2=%~2"
set "NUM3=%~3"
set "NUM4=%~4"
if defined NUM1 set "NUM1=%NUM1:"=""%
if defined NUM2 set "NUM2=%NUM2:"=""%
if defined NUM3 set "NUM3=%NUM3:"=%
call :VAL_ARGS NUM1 NUM2 NUM4 || exit /B 1
rem // Define constants here:
set /A "DIG=4" & set "PAD="
setlocal EnableDelayedExpansion
for /L %%J in (1,1,%DIG%) do set "PAD=!PAD!0"
endlocal & set "PAD=%PAD%"
rem // Determine string lengths:
call :STR_LEN LEN1 NUM1
call :STR_LEN LEN2 NUM2
set /A "LEN1=(LEN1-1)/DIG*DIG"
set /A "LEN2=(LEN2-1)/DIG*DIG"
set /A "LIM=LEN1+LEN2+DIG"
for /L %%I in (0,%DIG%,%LIM%) do set /A "RES[%%I]=0"
rem // Perform block-wise multiplication:
setlocal EnableDelayedExpansion
for /L %%J in (0,%DIG%,%LEN2%) do (
for /L %%I in (0,%DIG%,%LEN1%) do (
set /A "IDX=%%I+%%J"
if %%I EQU 0 (set "AUX1=-%DIG%") else (
set /A "AUX1=%DIG%+%%I" & set "AUX1=-!AUX1!,-%%I"
)
if %%J EQU 0 (set "AUX2=-%DIG%") else (
set /A "AUX2=%DIG%+%%J" & set "AUX2=-!AUX2!,-%%J"
)
for /F "tokens=1,2" %%M in ("!AUX1! !AUX2!") do (
set "AUX1=!NUM1:~%%M!" & set "AUX2=!NUM2:~%%N!"
)
call :NO_LEAD0 AUX1 !AUX1!
call :NO_LEAD0 AUX2 !AUX2!
set /A "RES[!IDX!]+=AUX1*AUX2"
set /A "NXT=IDX+DIG, DIT=DIG*2"
for /F "tokens=1,2,3" %%M in ("!IDX! !NXT! !DIT!") do (
set "AUX=!RES[%%M]:~-%%O,-%DIG%!"
set /A "RES[%%N]+=AUX"
set "RES[%%M]=!RES[%%M]:~-%DIG%!"
call :NO_LEAD0 RES[%%M] !RES[%%M]!
)
)
)
rem // Build resulting product:
set "RES=" & set "AUX="
for /L %%I in (0,%DIG%,%LIM%) do (
set /A "RES[%%I]+=AUX"
set /A "NXT=%%I+DIG"
for /L %%J in (!NXT!,%DIG%,!NXT!) do (
set "AUX=!RES[%%I]:~-%%J,-%DIG%!"
)
set "RES[%%I]=%PAD%!RES[%%I]!"
set "RES=!RES[%%I]:~-%DIG%!!RES!"
)
endlocal & set "RES=%RES%"
call :NO_LEAD0 RES %RES%
rem // Return resulting product:
echo(%RES%
if defined NUM3 (
endlocal
set "%NUM3%=%RES%"
) else (
endlocal
)
exit /B
:NO_LEAD0 rtn_var val_num
rem // Remove leading zeros from a number:
for /F "tokens=* delims=0" %%Z in ("%~2") do (
set "%~1=%%Z" & if not defined %~1 set "%~1=0"
)
exit /B 0
:STR_LEN rtn_length ref_string
rem // Retrieve length of string:
setlocal EnableDelayedExpansion
set "STR=!%~2!"
if not defined STR (set /A LEN=0) else (set /A LEN=1)
for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if defined STR (
set "INT=!STR:~%%L!"
if not "!INT!"=="" set /A LEN+=%%L & set "STR=!INT!"
)
)
endlocal & set "%~1=%LEN%"
exit /B 0
:VAL_ARGS ref_arg1 ref_arg2 ref_arg3
rem // Check arguments for validity:
if not defined %~1 >&2 echo ERROR: too few arguments given! & exit /B 1
if not defined %~2 >&2 echo ERROR: too few arguments given! & exit /B 1
if defined %~3 >&2 echo ERROR: too many arguments given! & exit /B 1
(call echo "%%%~1%%" | > nul findstr /R /C:"^\"[0-9][0-9]*\" $") || (
>&2 echo ERROR: argument 1 is not purely numeric! & exit /B 1
)
(call echo "%%%~2%%" | > nul findstr /R /C:"^\"[0-9][0-9]*\" $") || (
>&2 echo ERROR: argument 2 is not purely numeric! & exit /B 1
)
exit /B 0
To use it provide the two numbers to multiply as command line arguments; for instance:
multiply.bat 999999 999999
The resulting product is returned on the console:
999998000001
If you provide a third argument, the product is assigned to a variable with that name; for example:
multiply.bat 999999 999999 SquArea
This sets variable SquArea to the resulting value. The latter is also still returned on the console.
To silently assign the variable without any additional console output, redirect it to the nul device:
multiply.bat 999999 999999 SquArea > nul

Batch uses 32bit integers for storing numbers. This gives them a maximum size of 2^31 - 1 = 2,147,483,647.
999,999 * 999,999 = 999,998,000,001 which is larger than 2,147,483,647 therefore it "wraps" and starts from negatives.
This is a limitation of batch although there are some workarounds.

This might be useful
Can batch files not process large numbers?
The largest number batch can take is 2^31 - 1 = 2,147,483,647.
So again, it is starting back from negative and giving you that answer..

I noticed using pure batch, it would be somewhat to very difficult to implement operations that support the numbers beyond the 32-bit integer limit. So instead, I limited the numbers, so they won't overflow when multiplied.
REM Calc Square Area
:PolySqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Set /P "InputPolygonCalSqu=Enter one of the line's length in cm [Less then 40000] :"
If %InputPolygonCalSqu% GTR 40000 Goto :PolySqu
Set /A SquArea="InputPolygonCalSqu * InputPolygonCalSqu
Cls
Echo Polygon Area Calculator
For /L %%P In (1,1,57) Do Echo.
Echo The area of this square is %SquArea% cm2.
Pause
Goto :PolygonCal

Related

how can i get disk space with decimal point in gb tb and mb [duplicate]

This question already has answers here:
Using wmic to get drives space information
(3 answers)
Closed 6 years ago.
I am trying to get the disk space in if it is MB GB or TB with 2 decimal part.
It should give the output with decimals
Here is my code:
WMIC LOGICALDISK GET Name,Size,FreeSpace | find /i "C:"
This is the slightly modified script from my answer to another question, which constitutes a pure batch-file solution: Get size of a directory in 'MB' using batch file.
Basically, all computations are done with numbers padded by two zeros to the right, which is the same as multiplying by a hundred, and the decimal separator is inserted next to the second digit from the right just for displaying. The drive to get the free space of needs to be given as a command line argument, like C: for example.
Here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define constants here:
set /A DIVISOR=1024 & rem (1024 Bytes = 1 KiB, 1024 KiB = 1 MiB,...)
set "ROUNDUP=" & rem (set to non-empty value to round up results)
set /A DECIMALS=2 & rem (define number of decimal places here)
set "PAD=" & for /L %%N in (1,1,%DECIMALS%) do call set "PAD=%%PAD%%0"
rem Get free space size of drive given as command line argument:
for /F "tokens=2 delims== " %%B in ('
wmic LogicalDisk WHERE 'DeviceID^="%~1"' GET FreeSpace /VALUE
') do for /F %%A in ("%%B") do set "BYTES=%%A"
if not defined BYTES set "BYTES=0"
rem Display result in Bytes and divide it to get KiB, MiB, etc.:
call :DIVIDE %BYTES%%PAD% 1 RESULT
call :DISPLAY %RESULT% %DECIMALS% STRING
echo( Bytes: %STRING%
call :DIVIDE %RESULT% %DIVISOR% RESULT REST
if defined ROUNDUP if 0%REST% GTR 0 set /A RESULT+=1
call :DISPLAY %RESULT% %DECIMALS% STRING
echo(KiBytes: %STRING%
call :DIVIDE %RESULT% %DIVISOR% RESULT REST
if defined ROUNDUP if 0%REST% GTR 0 set /A RESULT+=1
call :DISPLAY %RESULT% %DECIMALS% STRING
echo(MiBytes: %STRING%
call :DIVIDE %RESULT% %DIVISOR% RESULT REST
if defined ROUNDUP if 0%REST% GTR 0 set /A RESULT+=1
call :DISPLAY %RESULT% %DECIMALS% STRING
echo(GiBytes: %STRING%
call :DIVIDE %RESULT% %DIVISOR% RESULT REST
if defined ROUNDUP if 0%REST% GTR 0 set /A RESULT+=1
call :DISPLAY %RESULT% %DECIMALS% STRING
echo(TiBytes: %STRING%
endlocal
exit /B
:DIVIDE val_dividend val_divisor [ref_result] [ref_remainder]
rem Divide a huge number exceeding the 32-bit limitation
rem by a 32-bit number in the range from 1 to 1000000000;
rem the result might also exceed the 32-bit limitation.
setlocal EnableDelayedExpansion
set "DIVIDEND=%~1"
set "DIVISOR=%~2"
set "QUOTIENT=%~3"
set "REMAINDER=%~4"
rem Check whether dividend and divisor are given:
if not defined DIVIDEND (
>&2 echo(Too few arguments, dividend missing^^!
exit /B 2
) else if not defined DIVISOR (
>&2 echo(Too few arguments, divisor missing^^!
exit /B 2
)
rem Check whether dividend is purely numeric:
for /F "tokens=* delims=0123456789" %%N in ("!DIVIDEND!") do (
if not "%%N"=="" (
>&2 echo(Dividend must be numeric^^!
exit /B 2
)
)
rem Convert divisor to numeric value without leading zeros:
for /F "tokens=* delims=0" %%N in ("%DIVISOR%") do set "DIVISOR=%%N"
set /A DIVISOR+=0
rem Check divisor against its range:
if %DIVISOR% LEQ 0 (
>&2 echo(Divisor value must be positive^^!
exit /B 1
) else if %DIVISOR% GTR 1000000000 (
>&2 echo(Divisor value exceeds its limit^^!
exit /B 1
)
set "COLL=" & set "NEXT=" & set /A INDEX=0
rem Do a division digit by digit as one would do it on paper:
:LOOP
if "!DIVIDEND:~%INDEX%,1!"=="" goto :CONT
set "NEXT=!NEXT!!DIVIDEND:~%INDEX%,1!"
rem Remove trailing zeros as such denote octal numbers:
for /F "tokens=* delims=0" %%N in ("!NEXT!") do set "NEXT=%%N"
set /A NEXT+=0
set /A PART=NEXT/DIVISOR
set "COLL=!COLL!!PART!"
set /A NEXT-=PART*DIVISOR
set /A INDEX+=1
goto :LOOP
:CONT
rem Remove trailing zeros as such denote octal numbers:
for /F "tokens=* delims=0" %%N in ("%COLL%") do set "COLL=%%N"
if not defined COLL set "COLL=0"
rem Set return variables or display result if none are given:
if defined QUOTIENT (
if defined REMAINDER (
endlocal
set "%REMAINDER%=%NEXT%"
) else (
endlocal
)
set "%QUOTIENT%=%COLL%"
) else (
endlocal
echo(%COLL%
)
exit /B
:DISPLAY val_number val_decimals [ref_result]
rem Prepare fractional number for being displayed.
setlocal EnableDelayedExpansion
set "NUM=%~1"
set "DEC=%~2"
set "SEP=."
set "RET=%~3"
set "PAD=" & for /L %%N in (1,1,%DEC%) do set "PAD=!PAD!0"
set "NUM=%PAD%%NUM%"
set "NUM=!NUM:~,-%DEC%!%SEP%!NUM:~-%DEC%!"
for /F "tokens=* delims=0" %%N in ("%NUM%") do set "NUM=%%N"
if "%NUM:~,1%"=="%SEP%" set "NUM=0%NUM%"
if defined RET (
endlocal
set "%RET%=%NUM%"
) else (
endlocal
echo(%NUM%
)
endlocal
exit /B
Here is a sample output (supposing the script is saved as drive-size.bat):
C:\TEMP>drive-size.bat C:
Bytes: 82752724992.00
KiBytes: 80813208.00
MiBytes: 78919.14
GiBytes: 77.06
TiBytes: 0.07

Get size of a directory in 'MB' using batch file

I want to get the size of directory say C:\Temp in MB using batch file. I don't need sizes of child directories or files, but the size of directory itself.
I found an answer at How to list all folder with size via batch file
but it gives me size in bytes and that of sub folders. So my question is :
How to get the size of directory itself in MB ?
You can do it with a hybrid script [Batch/Vbscript] like this one :
#echo off
set Folder="C:\temp"
echo The size of %Folder% is
Call :GetSize %Folder%
pause
:GetSize
(
echo wscript.echo GetSize("%~1"^)
echo Function GetSize(MyFolder^)
echo Set fso = CreateObject("Scripting.FileSystemObject"^)
echo Set objFolder= fso.GetFolder(MyFolder^)
echo GetSize = FormatSize(objFolder.Size^)
echo End Function
echo '*******************************************************************
echo 'Function to format a number into typical size scales
echo Function FormatSize(iSize^)
echo aLabel = Array("bytes", "KB", "MB", "GB", "TB"^)
echo For i = 0 to 4
echo If iSize ^> 1024 Then
echo iSize = iSize / 1024
echo Else
echo Exit For
echo End If
echo Next
echo FormatSize = Round(iSize,2^) ^& " " ^& aLabel(i^)
echo End Function
echo '*******************************************************************
)>%tmp%\Size.vbs
Cscript /NoLogo %tmp%\Size.vbs
Del %tmp%\Size.vbs
Exit /b
Edit : on 30/03/2016 #12:11
And this a nice trick
the Liviu's hack for embedding vbscode in batch without temp files
I just discovered from npocmaka thanks to him
#echo off
Set Folder="c:\temp"
#cScript.EXE //noLogo "%~f0?.WSF" %Folder% //job:info %~nx0%*
pause
#exit /b 0
<job id="info">
<script language="VBScript">
wscript.echo GetSize(WScript.Arguments(0))
Function GetSize(MyFolder)
Set fso = CreateObject("Scripting.FileSystemObject")
Set objFolder= fso.GetFolder(MyFolder)
GetSize = FormatSize(objFolder.Size)
End Function
'*******************************************************************
'Function to format a number into typical size scales
Function FormatSize(iSize)
aLabel = Array("bytes", "KB", "MB", "GB", "TB")
For i = 0 to 4
If iSize > 1024 Then
iSize = iSize / 1024
Else
Exit For
End If
Next
FormatSize = Round(iSize,2) & " " & aLabel(i)
End Function
'*******************************************************************
</script>
</job>
#echo off
::robocopy "%~1" %TEMP% /S /L /BYTES /XJ /NFL /NDL /NJH
for /f "tokens=2 delims=: " %%a in ('
robocopy "%~1" %TEMP% /S /L /BYTES /XJ /NFL /NDL /NJH ^| find "Bytes"
') do set "bytes=%%a"
::1048576
set "beginJS=mshta "javascript:close(new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1).Write("
set "endJS=));""
for /f %%N in (
'%beginJS% %bytes%/1048576 %endJS%'
) do set mb=%%N
echo mb=%mb%
It takes one argument - the folder which size you want to calculate
Here is a pure batch file solution (refer to the remarks in the code for some brief explanations):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define constants here:
set /A DIVISOR=1024 & rem (1024 Bytes = 1 KBytes, 1024 KBytes = 1 MByte,...)
set "ROUNDUP=#" & rem (set to non-empty value to round up results)
rem Get size of directory given as command line argument:
for /F "tokens=2 delims=: " %%B in ('
robocopy "%~1" "%~1" "*.*" /L /S /XJ /BYTES /NP /NFL /NDL /NJH ^| ^
find /I "Bytes"
') do set "BYTES=%%B"
if not defined BYTES set "BYTES=0"
rem Display result in Bytes and divide it to get KBytes, MBytes, etc.:
call :DIVIDE %BYTES% 1 RESULT
echo( Bytes: %RESULT%
call :DIVIDE %RESULT% %DIVISOR% RESULT REST
if defined ROUNDUP if 0%REST% GTR 0 set /A RESULT+=1
echo(KBytes: %RESULT%
call :DIVIDE %RESULT% %DIVISOR% RESULT REST
if defined ROUNDUP if 0%REST% GTR 0 set /A RESULT+=1
echo(MBytes: %RESULT%
call :DIVIDE %RESULT% %DIVISOR% RESULT REST
if defined ROUNDUP if 0%REST% GTR 0 set /A RESULT+=1
echo(GBytes: %RESULT%
endlocal
exit /B
:DIVIDE val_dividend val_divisor [ref_result] [ref_remainder]
rem Divide a huge number exceeding the 32-bit limitation
rem by a 32-bit number in the range from 1 to 1000000000;
rem the result might also exceed the 32-bit limitation.
setlocal EnableDelayedExpansion
set "DIVIDEND=%~1"
set "DIVISOR=%~2"
set "QUOTIENT=%~3"
set "REMAINDER=%~4"
rem Check whether dividend and divisor are given:
if not defined DIVIDEND (
>&2 echo(Too few arguments, dividend missing^^!
exit /B 2
) else if not defined DIVISOR (
>&2 echo(Too few arguments, divisor missing^^!
exit /B 2
)
rem Check whether dividend is purely numeric:
for /F "tokens=* delims=0123456789" %%N in ("!DIVIDEND!") do (
if not "%%N"=="" (
>&2 echo(Dividend must be numeric^^!
exit /B 2
)
)
rem Convert divisor to numeric value without leading zeros:
for /F "tokens=* delims=0" %%N in ("%DIVISOR%") do set "DIVISOR=%%N"
set /A DIVISOR+=0
rem Check divisor against its range:
if %DIVISOR% LEQ 0 (
>&2 echo(Divisor value must be positive^^!
exit /B 1
) else if %DIVISOR% GTR 1000000000 (
>&2 echo(Divisor value exceeds its limit^^!
exit /B 1
)
set "COLL=" & set "NEXT=" & set /A INDEX=0
rem Do a division digit by digit as one would do it on paper:
:LOOP
if "!DIVIDEND:~%INDEX%,1!"=="" goto :CONT
set "NEXT=!NEXT!!DIVIDEND:~%INDEX%,1!"
rem Remove trailing zeros as such denote octal numbers:
for /F "tokens=* delims=0" %%N in ("!NEXT!") do set "NEXT=%%N"
set /A NEXT+=0
set /A PART=NEXT/DIVISOR
set "COLL=!COLL!!PART!"
set /A NEXT-=PART*DIVISOR
set /A INDEX+=1
goto :LOOP
:CONT
rem Remove trailing zeros as such denote octal numbers:
for /F "tokens=* delims=0" %%N in ("%COLL%") do set "COLL=%%N"
if not defined COLL set "COLL=0"
rem Set return variables or display result if none are given:
if defined QUOTIENT (
if defined REMAINDER (
endlocal
set "%REMAINDER%=%NEXT%"
) else (
endlocal
)
set "%QUOTIENT%=%COLL%"
) else (
endlocal
echo(%COLL%
)
exit /B
Basically, the size of the contents of the directory given as command line argument is gathered by robocopy, similar to npocmaka's approach; the resulting size in Bytes is stored in variable BYTES.
Afterwards, several subroutine calls are done, each to divide the result by 1024 in order to get the size in KBytes, then MBytes and GBytes.
The values resulting from the divisions are incremented by 1 if there is a remainder in order to round up the sizes (similar to Windows Explorer, which for example displays 1 KB for very small files). You can disable this feature by setting variable ROUNDUP to an empty string, which leads to rounding down.
Division Approach
Since the set /A command (and its division operator /) can handle (signed) 32-bit integers only, the subroutine :DIVIDE performs the actual division like one would do it on paper. The dividend (that is the number to be divided) is treated as a string, so the 32-bit range can be exceeded; the divisor (that is the number to divide by) however must not exceed it; rather it must be in the range from 1 to 1000000000. The loop at :LOOP consisting of goto :LOOP and a conditional goto :CONT constitutes a while-loop-like structure that iterates through all the decimal figures of the dividend and terminates after the last one has been reached.
The subroutine expects at least two arguments -- the values of the dividend and the divisor. It accepts even two more -- the name of a variable to hold the quotient (that is the division result) and another one to hold the remainder. If no optional arguments are given, the quotient is displayed on the console.
if you need exact values, go for another option. But if you need a rough value only (cut, not rounded, not 1024 as divisor but 1000 only):
#echo off
for /f "tokens=2 delims=," %%a in ('dir *^|findstr /e "Bytes"') do (
for /f "delims= " %%i in ("%%a") do set size=%%i
)
echo size in Bytes: %size%
echo that's about %size:~0,-4% KB
echo or about %size:~0,-8% MB
echo or about %size:~0,-12% GB
Advantage: this is string manipulation only, no math included, so it even works outside 32bit Integer (which would be limited to sizes up to about 2GB).

Using an if statement inside a for loop

I am trying to make a batch file to solve the first Project Euler problem, http://projecteuler.net/problem=1, but I need an if statement inside my loop to check if n modulo 3 or 5 is 0. And the sum has suddenly stopped working.
My code:
echo off
set sum=0
for /l %%n in (1,1,999) do (
set a/ sum+=%%n *(only add if n%%3 == 0 or n%%5 == 0)*
)
echo %sum%
pause
Here is a very efficient solution, though it is a bit obfuscated:
#echo off
setlocal
set /a sum=0
for /l %%N in (1 1 999) do set /a "sum+=%%N/!((%%N%%5)*(%%N%%3))" 2>nul
echo %sum%
The expression (%%N%%5)*(%%N%%3) yields zero if %%N is divisible by 3 or 5, or non-zero if it is not divisible by either. The ! takes the inverse logical value, so 0 becomes 1, and non-zero becomes 0. Dividing %%N by that expression yields either %%N or a division by zero error. So simply add that entire expression to the sum, and redirect error messages to nul.
Final result - only numbers divisible by 3 or 5 are added :-)
#ECHO OFF
SETLOCAL
set /A sum=0
for /l %%n in (1,1,999) do (
CALL :modulo %%n
IF DEFINED addme set /a sum+=%%n
REM CALL echo %%n %%sum%% %%addme%%
)
echo %sum%
GOTO :EOF
:modulo
:: set addme if %1 %% 3 == 0 or %1 %% 5 == 0
SET /a addme = %1 %% 3
IF %addme%==0 GOTO :EOF
SET /a addme = %1 %% 5
IF %addme%==0 GOTO :EOF
SET "addme="
GOTO :eof
Simply pass each value to the :modulo routine in turn and set a flag value to (clear or not)
OR
#ECHO OFF
SETLOCAL enabledelayedexpansion
set /A sum=0
for /l %%n in (1,1,999) do (
SET /a mod=%%n %% 3
IF NOT !mod!==0 SET /a mod=%%n %% 5
IF !mod!== 0 set /a sum+=%%n
rem CALL echo %%n %%sum%%
)
echo %sum%
GOTO :EOF
which does the same thing using delayedexpansion.
And the sum has suddenly stopped working.
I think your sum stopped working because your set needs to have the slash in front of the 'a', and not behind it, like this:
SET /A sum+=%%n
Also, there isn't an OR operator in DOS Batch, so you'll need to use a nested IF for that. This worked for me:
echo off
SETLOCAL ENABLEDELAYEDEXPANSION
set sum=0
for /l %%n in (1,1,999) do (
SET /A mod3=%%n%%3
SET /A mod5=%%n%%5
IF !mod3!==0 (
SET /A sum+=%%n
) ELSE (
IF !mod5!==0 (
SET /A sum+=%%n
)
)
)
echo %sum%
ENDLOCAL
If you need more help, check out Rob van der Woude's Scripting pages. Specifically, here is a link to his page on performing mathematical operations in DOS batch files.

How to find large factorials using a batch script

#echo off
if %1.==. ( echo Missing parameter! Try passing the number as a parameter like 'factorial 10' without the quotes.
goto end )
setlocal enabledelayedexpansion
set /a count=0
set /a temp=0
set /a digits=1
set /a array1=1
for /L %%i IN (2,1,%1) do (
set /a temp=0
for /L %%j IN ( 1,1,!digits! ) do (
set /a temp=!temp!+!array%%j!*%%i
set /a array%%j=!temp!%%10
set /a temp=!temp!/10 )
set /a index=!digits!+1
for /L %%v IN (!index!,1,!index! ) do (
if !temp! NEQ 0 (
set /a array!index!=!temp!%%10
set /a temp/=10
set /a index+=1 ))
set /a digits=!index!-1)
for /l %%v IN ( !digits!,-1,1 ) do set array=!array!!array%%v!
echo !array!
echo Total # of decimal digits = !digits!
:end
pause
This is what i have gotten so far. It is quite stable under 10! but once i reach 15 or 20 it starts missing out a few digits.
My edit....
#echo off
if %1.==. ( echo Missing parameter! Try passing the number as a parameter like 'factorial 10' without the quotes.
goto end )
setlocal enabledelayedexpansion
set /a count=0
set /a temp=0
set /a digits=1
set /a array1=1
for /L %%i IN (2,1,%1) do (
set /a temp=0
for /L %%j IN ( 1,1,!digits! ) do (
set /a temp=!temp!+!array%%j!*%%i
set /a array%%j=!temp!%%10
set /a temp=!temp!/10 )
for /l %%v IN ( 1,1,30 ) do (
if !temp! neq 0 (
set /a digits+=1
set /a array!digits!=!temp!%%10
set /a temp=!temp!/10
)))
for /l %%v IN ( !digits!,-1,1 ) do set array=!array!!array%%v!
echo !array!
echo Total # of decimal digits = !digits!
:end
pause
Now i am forcing the the last inner loop to run 30 times eventhough temp would have been zero way before that. Is there any way to write a snippet that would be analogous to the following c code
while (temp)
{/code goes here/}
This execute only as long as temp is non zero.
Is there any partyicular limit to how big a variable can be in a batch?
I know it is a pain * to debug computational programs in a batch, I'm just trying to code this in all programming languages i know, Just so i could compare their speeds. Can somebody Please point out what i'm doing wrong here.
edit........22/12/13
#echo off
if %1.==. ( echo Missing parameter! Try passing the number as a parameter like 'factorial 10' without the quotes.
goto end )
setlocal enabledelayedexpansion
set /a count=0
set /a tempo=0
set /a digits=1
set /a array1=1
for /f "tokens=1-4 delims=:.," %%a IN ("%time%") do (
set /a "start=(((%%a*60)+1%%b %% 100)*60+1%%c %%100)*100+1%%d %% 100"
)
for /L %%i IN (2,1,%1) do (
set /a tempo=0
for /L %%j IN ( 1,1,!digits! ) do (
set /a tempo=!tempo!+!array%%j!*%%i
set /a array%%j=!tempo!%%10000
set /a tempo=!tempo!/10000 )
for /l %%v IN (1,1,2) do (
if !tempo! neq 0 (
set /a digits+=1
set /a array!digits!=tempo%%10000
set /a tempo=tempo/10000
))
)
for /l %%v IN ( !digits!,-1,1 ) do set array=!array!!array%%v!
echo !array!
echo Total # of decimal digits = !digits!
for /f "tokens=1-4 delims=:.," %%a in ("%time%") do (
set /a "end=(((%%a*60)+1%%b %% 100)*60+1%%c %%100)*100+1%%d %% 100"
)
set /a elapsedcs=end-start
set /a elapseds=elapsedcs/100
set /a elapsedcs=elapsedcs%%100
echo %elapseds%.%elapsedcs% seconds
:end
rem label should never be the last statement
Is this what you meant aacini?
In order to achieve fast arithmetic operations on Big numbers in Batch, you must split the Bignum in groups of digits that can be managed via the 32-bits operations of set /A command. The maximum 32-bits signed integer is 2147483647, so the largest group of digits that can be multiplied this way is 4, because 5 digits (99999 x 99999) exceed the maximum number. Addition and multiplication must be achieved from right to left translating a "carry" to the next group to the left. Subtraction and division must be achieved from left to right translating a "borrow" to the next group to the right. The Batch file below use this method to succesively multiply a Bignum by a 4 digits factor, so it can calculate up to 9999! as long as all variables that contain the groups of 4 digits fits in the 64 MB size limit of the environment (look for "65,536KB maximum size" under "Setting environment variables"). The result is directly output to the screen in order to avoid the 8192 digits limit of one Batch variable.
EDIT: I slightly modified the program in order to run faster and get the number of digits in the result.
#echo off
if "%1" equ "" (
echo Missing parameter! Try passing the number as a parameter like 'factorial 10' without the quotes.
goto end
)
setlocal EnableDelayedExpansion
rem Calculate the factorial
set /A g1=1, groups=1
for /L %%n in (2,1,%1) do (
set carry=0
for /L %%g in (1,1,!groups!) do (
set /A group=g%%g*%%n+carry, g%%g=group%%10000, carry=group/10000
)
if !carry! neq 0 (
set /A groups+=1
set g!groups!=!carry!
)
)
rem Show the factorial
set /P "=!g%groups%!" < NUL
set /A groupsM1=groups-1
for /L %%g in (%groupsM1%,-1,1) do (
set group=000!g%%g!
set /P "=!group:~-4!" < NUL
)
echo/
rem Get the number of digits
set digits=0
for /L %%i in (0,1,3) do if "!g%groups%:~%%i,1!" neq "" set /A digits+=1
set /A digits+=4*groupsM1
echo Total # of decimal digits = %digits%
:end
pause
I had to rereread the code to see what it is doing. Nice.
You have to face three limits on your code.
1 - In the inner %%j and %%v loop, where buffer is used to multiply the current value in %%i, you face the limit indicated by Magoo. You can not operate with set /a with values greater than 2^31. As the values in the array are limited to 0-9, this means that this limit will not let you calculate factorials of numbers greater than 214748364 (aprox)
2 - There is a limit in the size of a environment variable. It can not hold more than 32767 characters. As you are concatenating the digits to output to console (the next limit is related), this limits you to factorials of numbers below 9273 (aprox).
3 - There is a limit in the length of the lines cmd can handle. It is 8191 characters. This does not limit your calc, but you can not use the method of concatenating in a variable to represent the number. If the method is not changed, this limits you to factorials of numbers below 2727 (aprox).
Batch is limited to signed-32-bit integers.
BTW - don't use temp or tmp as a user-variable. It is set by the system as a pointer to a directory where temporary files are stored.

Prime numbers in batch

I've been working on a little project using batch files and I've ran into a problem. As far as I'm aware there's no way to run a check to see if a certain variable is a prime number, if I'm wrong would anyone please inform me of how to do so, otherwise, can anyone think of a workaround I could use (like checking if a number is equal to a number on a list of prime numbers on a txt file or whatever).
Thanks ^^
(Also it's worth noting I'm not very knowledgeable with batch files so please excuse any idiocy I may present..)
If you have a text file of prime numbers, 1 per line (obviously up through some limit), then the solution is trivial - just use FINDSTR.
Assuming you have a NUMBER variable containing a number, then
>nul findstr /x %NUMBER% "primes.txt" && (
REM prime actions go here
echo %NUMBER% is prime
) || (
REM not prime actions go here
echo %NUMBER% is NOT prime
)
UPDATE
Here is a native batch script that can test any valid integer supported by batch (signed 32 bit ints) to see if it is prime. Performance is much better than I thought possible.
::testPrime Number
::
:: Computes whether Number is a prime or not.
:: The result is printed to stdout.
::
:: ERRORLEVEL is also set to indicate the result:
:: 0 = Prime
:: 1 = Not Prime
:: 2 = Error
::
:: Number = Any valid integral expression supported by SET /A
::
#echo off
if "%~1"=="test" (
setlocal enableDelayedExpansion
for /l %%N in (3 2 0x7fffffff) do (
set /a "test1=num %% %%N, test2=%%N*%%N"
if !test1! equ 0 exit 1
if !test2! gtr !num! exit 0
)
)
setlocal disableDelayedExpansion
2>nul set /a "num=%~1" || (
>&2 echo invalid number: %1
exit /b 2
)
if %num% leq 1 (
echo %num% is NOT prime
exit /b 1
)
if %num% leq 3 (
echo %num% is prime
exit /b 0
)
2>nul set /a "1/(num %% 2)" || (
echo %num% is NOT prime
exit /b 1
)
(
cmd /c "%~f0" test
) && (
echo %num% is prime
exit /b 0
) || (
echo %num% is NOT prime
exit /b 1
)
exit /b
The test is actually split into 2 parts, the 2nd of which is actually run in a new CMD instance. The 2nd part actually appears at the top of the script. This is done for performance reasons. It is the only way I can break out of a FOR /L loop immediately without terminating the batch script.
You can integrate your code with the script easily enough. For example:
#echo off
::----------------------------------------------------
:: This 2nd part of :testPrime must be at top of script
::
if "%~1"=="test" (
setlocal enableDelayedExpansion
for /l %%N in (3 2 0x7fffffff) do (
set /a "test1=num %% %%N, test2=%%N*%%N"
if !test1! equ 0 exit 1
if !test2! gtr !num! exit 0
)
)
:: End of 2nd part of :testPrime
::-----------------------------------------------------
:: Your code goes here
:: I'll just call the test with some representative values
::
setlocal disableDelayedExpansion
for %%N in (
1 2 3 4 100001 100003 5000009 5000011 0x7fffffff-2 0x7fffffff
) do >nul call :testPrime %%N && (
rem prime number actions go here
echo %%N is prime!
) || (
rem non-prime number actions go here
echo Not prime (%%N^)
)
exit /b
::----------------------------------------------------
:: Here is the 1st part of :testPrime
::
:testPrime
2>nul set /a "num=%~1" || (
>&2 echo invalid number: %1
exit /b 2
)
if %num% leq 1 (
echo %num% is NOT prime
exit /b 1
)
if %num% leq 3 (
echo %num% is prime
exit /b 0
)
2>nul set /a "1/(num %% 2)" || (
echo %num% is NOT prime
exit /b 1
)
(
cmd /c "%~f0" test
) && (
echo %num% is prime
exit /b 0
) || (
echo %num% is NOT prime
exit /b 1
)
exit /b
The output for the above looks like this:
Not prime (1)
2 is prime!
3 is prime!
Not prime (4)
Not prime (100001)
100003 is prime!
Not prime (5000009)
5000011 is prime!
Not prime (0x7fffffff-2)
0x7fffffff is prime!
Finally, just for yucks, I wrote a variation that lists the next prime >= or <= a given number.
::nextPrime [/less] Num
::
:: List the minimum prime number >= Num
::
:: The /L option lists the maximum prime number <= Num
::
:: The ERRORLEVEL is set to the found prime number
::
:: Num = Any valid integral expression supported by SET /A
::
#echo off
setlocal enableDelayedExpansion
if "%~1"=="test" (
for /l %%N in (3 2 0x7fffffff) do (
set /a "test1=%2 %% %%N, test2=%%N*%%N"
if !test1! equ 0 exit 1
if !test2! gtr %2 exit 0
)
)
if "%~1"=="prev" (
if !num! lss 2 exit 0
set /a "test=num%%2"
if !test! equ 0 set /a num-=1
for /l %%N in (!num! -2 2) do cmd /c "%~f0" test %%N && exit %%N
exit 0
)
if "%~1"=="next" (
if !num! lss 2 exit 2
set /a "test=!num!%%2"
if !test! equ 0 set /a num+=1
for /l %%N in (!num! 2 0x7fffffff) do cmd /c "%~f0" test %%N && exit %%N
exit 0
)
set "cmd=next"
if /i "%~1" equ "/L" (
set "cmd=prev"
shift /1
)
2>nul set /a "num=%~1" || exit /b 0
cmd /c "%~f0" %cmd% || echo !errorlevel!
And here is a demonstration of usage with output:
D:\test>nextPrime 10000000
10000019
D:\test>nextPrime /l 10000000
9999991
All of those scripts seem awfully (and unnecessarily) large to me.
An easier way to do this is using... I believe the term I'm looking for is either modulo or modulus expressions (I think modulo is the plural or modulus).
#echo off & setlocal enabledelayedexpansion
:a
cls
set /p num=Type a number to be checked:
cls
set num2=%num%-1
if %num% leq 2 goto yes
for /l %%i in (2,1,%num2%) do (
set rem=%num% %% %%i
if %rem% neq 0 goto no
)
:yes
echo %num% is a prime number.
pause
goto a
:no
echo %num% is not a prime number.
pause
goto a
Basically, it gets a user-defined variable and checks, when divided by a number, if the remainder (rem) is 0 or not.
This way is a little slow, but the shortest code. You can make it a bit shorter by putting another if statement before the for loop that checks if the number has a remainder when divided by two.
Hope it helps.
Another prime lister, this one does'nt use a file and can reach 64,000,000 if you have patience. Keeps a list of prime divisors in an environment variable. If i had a batch integer square root routine i could make it faster.
#echo off
::batch prime list up to 64M by Antoni Gual
:: does not use files!!
setlocal enabledelayedexpansion
set bitmap=
set n=Y
set /a test=3,npri=3
echo 1th prime is 2 & echo 2th prime is 3
:nextpri
set /a test+=2,index=0,div=3
if %test% LSS 8000 set bitmap=%bitmap%%n%
if %test% gtr 64000000 exit /b
:nextest
if "!bitmap:~%index%,1!"=="N" goto nextdiv
set /a resi=!test!%%!div!
if %resi% equ 0 set n=N& goto nextpri
:nextdiv
set /a index+=1, div+=2
set /a div2=div*div
if %div2% gtr %test% (
set n=Y
echo %npri%th prime is %test%
set /a npri+=1
goto nextpri)
goto nextest
The following script does not perform a primality test. Rather, it generates prime numbers up to a specific bound (the hard-coded 1000 in this particular case). You could generate the list once and then use it in your script(s):
#echo off
echo 2
echo 3
echo 2 > primenos.txt
echo 3 >> primenos.txt
set current=3
:numbercalc
set tim=3
set /a max=%current%/5
:try
set /a t=%current%/%tim%
set /a u=%t%*%tim%
if %u% EQU %current% goto noprime
set /a tim+=2
if %tim% GTR %max% goto endtry
goto try
:endtry
echo %current%
echo %current% >> primenos.txt
:noprime
set /a current+=2
if %current% GTR 1000 goto end
goto numbercalc
:end
pause
Taken from here...
Assuming you have a text file with prime numbers (each number in a single line) you could do it like this:
#echo off
if "%1"=="" (echo Syntax: %~nx0 number & exit /b 2)
for /F "tokens=*" %%p in (primes.txt) do (
if %%p EQU %1 (
echo %1 is prime!
exit /b 0
)
)
echo %1 is not prime!
exit /b 1
Example call: isprime.cmd 2 would give you 2 is prime!.
Here is what I made. It finds all prime numbers up to 214748 but it is accurate and fast and outputs the results to a file called "pn.txt" and the current number to "cn.txt" (this was to let me know what its limit was):
(#echo off)&((set n=3)&((set tn=%n%)&((set d=2)&((set d2=)&((set m=100)&((echo.prime-numbers>pn.txt)&((echo. >>pn.txt)&((echo.1>>pn.txt)&((echo.2>>pn.txt)&((echo.1)&((echo.2)&(goto a))))))))))))
:a
(echo.%n%cn.txt)&((set tn=%n%)&(set tn=%n:~-1%))
(if %tn%==2 ((set /a n=%n%+1)&((set d=2%d2%)&(goto a))))&((if %tn%==4 ((set /a n=%n%+1)&((set d=2%d2%)&(goto a))))&((if %tn%==6 ((set /a n=%n%+1)&((set d=2%d2%)&(gotoa))))&((if %tn%==8 ((set /a n=%n%+1)&((set d=2%d2%)&(goto a))))&((if %tn%==0 ((set /a n=%n%+1)&((set d=2%d2%)&(goto a))))&(goto b)))))
:b
((set /a tn=%d%*%d%)&(if /i %tn% equ %n% ((set /a n=%n%+1)&((set d=2%d2%)&(goto a)))))&((if /i %tn% gtr %n% ((set tn=%n%)&((set l=%d%)&((if /i %n% equ 10000 (set d2=0))&((if /i %n% equ 100000 set (d2=00))&((set d=2%d2%)&((set m=100%d2%)&(goto d)))))))&((set /a d=%d%+1)&(goto b))
:d
(title verifing %n% at %d%)&(set tn=%n%)
set /a tn=%tn%*%m%
set /a tn=%tn%/%d%
set tn=%tn:~-2%
(if /i %tn% equ 00%d2% ((set /a n=%n%+1)&((set d=2%d2%)&(goto a))))&(((set /a tn=%n%-1)&(if %d%==%tn% ((echo.%n%)&((echo.%n%>pn.txt)&(((set /a n=%n%+1)&((set d=2%d2%)&(goto a)))))))&(if %d%==%l% ((echo.%n%)&((echo.%n%>pn.txt)&((set /a n=%n%+1)&((set d=2%d2%)&(goto a)))))))&((set /a d=%d%+1)&(goto d)))
#echo off
::PRIMES
set multiple2=1
set add=1
set counter=1
color f0
set /p range=Computer primes 0-?:
set /a limit=(range/2)+1
set ut=3
mkdir prime0-%range%
cd prime0-%range%
echo >>2
:opipe
echo >>%ut%
set /a ut=ut+2
if %ut% GEQ %range% goto next
goto opipe
:next
set /a multiple2=multiple2+2
if %multiple2% GEQ %limit% goto end
set /a add=add+2
set /a multiple=multiple2
:process
set /a multiple=multiple+add
del %multiple%
if %multiple% GEQ %range% goto next
goto process
:end
CD ..
echo 2 >>ALLprime0-%range%.txt
:offx
set /a counter=counter+2
if exist prime0-%range%\%counter% echo %counter% >>ALLprime0-%range%.txt
if %counter% GEQ %range% goto down
goto offx
:down
echo Computation Succesful
pause
exit
::RMDIR /S /Q prime0-%range%
Please excuse me for beating a dead horse. Here is a radically differnt approach to generating primes in pure batch that overperforms everything else I have found .
It's based in Keeping a list of multiples of previous found primes around the window we're checking. The numbers not found in the list are primes. For keeping the list I use environment variables of the form #composite=next_increment .It uses a single loop, much faster and degrading slower than the nested double loop used in other algorithms. Unforunately a FOR and an auxiliar subrputine are required to overcome the impossiblilty of having repeated keys in environment, as sometimes multiples of different primes clash.
BTW the idea comes from an exerciste in Knuth's TAOCP Vol 3 page 617.
:: prime table using rotating multiples stored in environment
#echo off
setlocal enabledelayedexpansion
mode con cols=90
set #25=-10
set /a num=7,inc1=4,cnt=0,inc2=0,num1=0, maxprime=10000
set lin= 0:
call:line 2 & call:line 3 & call:line 5
:nextnum
if defined #%num% (
for %%i in (!#%num%!) do (
if %%i lss 0 (set /a num1=%num%-%%i,"inc2=-%%i<<1") else (set /a num1=%num%+%%i,"inc2=-(%%i>>1)")
call :aux !num1! !inc2!
)
set #%num%=
) else (
call :line %num%
set /a num1= num * num
if %inc1% equ 4 (set/a "inc2=num<<2") else (set /a "inc2=-(num<<1)")
if !num1! leq %maxprime% set #!num1!=!inc2!
)
set /a num+=inc1, inc1=6-inc1
if %num% lss %maxprime% goto nextnum
echo %lin%
pause & goto:eof
:aux
if %1 leq %maxprime% set #%1=%2 !#%1!
goto:eof
:line formats output in 10 right aligned columns
set num2= %1
set lin=%lin%%num2:~-8%
set /a cnt+=1,res1=(cnt%%10)
if %res1% neq 0 goto:eof
echo %lin%
set cnt1= %cnt%
set lin=%cnt1:~-5%:
goto:eof
#echo off
:top
set /p number=number:
set /a check=1
:work
set /a check=%check%+1
set /a result=%number%/%check%
set /a abc=%result%+1
if %abc% LSS %check% goto end
SET /a modulo=%number% %% %check%
IF %modulo%==0 (goto factor
) ELSE (goto notfactor
)
:factor
echo %check%
set /a other= %number%/%check%
echo %other%
echo.
goto work
:notfactor
goto work
:end
echo If you see 2 numbers this its a prime otherwise its composit
echo.
echo i am too lazy to code it further and have already spend way too much time trying to make this
pause
cls
goto top
this will also tell the prime factors if any.

Resources