Search code examples
batch-filemultiplicationarea

Batch file multiply positive variables return a negative number


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.


Solution

  • As others already pointed out, a 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 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