Dos Batch Math Library

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Dos Batch Math Library

#1 Post by einstein1969 » 08 Aug 2014 03:00

Hi to all,

on the main site lack the mathematical functions.

Since many user here, and elsewhere on the web, they have developed a lot, you can now reap the fruits of their sowing and planting new seeds.

I hope for the cooperation of all to make a point of reference.

I have not tried on StackOverflow/Stack... nor groups discussion or sites, for example, in German (or Russian or other) because they do not know the German nor the Russian enough. So if someone speaking German or Russian or else can do a search and also bring only the link where they talk about mathematical functions I will take the responsibility to keep this thread neat.

I had thought to create functions that could go well for both 32bit integers, 64bit integer (2*32), a fixed point math as simple as that defined by Antonio Acini, the float point defined by Penpen, I thought I'd define a new mathematical fixed point such as the one defined in the console games (for example see Nintendo DS).

I also decided to make versions that take advantage of the concept of macro.


List functions to implement:
  • Abs(x)
  • Sign(x)
  • Ceil(x)
  • Floor(x)
  • Cos(x)
  • Sin(x)
  • Tan(x)
  • Pow(x,y)
  • Log(x)
  • Ln(x)
  • Round(x)
  • Sqrt(x)
  • Hypot(x,y)
  • Min(x,y)
  • Max(x,y)
  • Cmp(x,y)

Reference and works: Performance tips:
Thanks to all
Francesco Poscetti aka einstein1969
Last edited by einstein1969 on 28 May 2023 02:55, edited 29 times in total.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#2 Post by einstein1969 » 08 Aug 2014 03:13

Example of implementations of abs(x):

Suppose that the x is 32bit signed INTEGER.

  1. Code: Select all

    Set /a X=-56789
    if %X% lss 0 set /a X=-X

  2. Code: Select all

    set /a "x=-56789,  x=(x>>31|1)*x"
...

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#3 Post by einstein1969 » 08 Aug 2014 03:15

  • Abs(x)

    Code: Select all

    set "Abs(x)=(((x)>>31|1)*(x))"
    set /A "x=-32, abs=%Abs(x)%"
    rem or
    set /A Abs=%Abs(x):x=-32%
    
    rem or (trick by dave)
    set /a "x=-32"
    set /a "x=%x:-=%"
    
  • Sign(x)

    Code: Select all

    set "Sign(x)=(x)>>31 | -(x)>>31 & 1" 
    :: or 
    set "Sign(x)=(x)>>31 | -(-(x)>>31)"
    ::
    set /A "x=-32, S=%Sign(x)%"
    set /A "S=%Sign(x):x=-32%"
    
  • Ceil(x)
  • Floor(x)
  • Cos(x)

    Code: Select all

    This is Sin(pi/2-x)
  • Sin(x)

    Code: Select all

    set "SIN(x)=(a=((x*31416/180)%%62832)+(((x*31416/180)%%62832)>>31&62832), b=(a-15708^a-47124)>>31,a=(-a&b)+(a&~b)+(31416&b)+(-62832&(47123-a>>31)),a-a*a/1875*a/320000+a*a/1875*a/15625*a/16000*a/2560000-a*a/1875*a/15360*a/15625*a/15625*a/16000*a/44800000)"
    set /A "sx=%SIN(x):x=45%"
  • Tan(x)

    Code: Select all

    This is Sin(x)/Cos(x)
  • Pow(x,y)
  • Log(x)
  • Ln(x)
  • Round(x)
  • Sqrt(N)

    Code: Select all

    Rem Without exception on negative input
    set "Sqrt(N)=( M=(N), x=M/(11*1024)+40, x=(M/x+x)>>1, x=(M/x+x)>>1, x=(M/x+x)>>1, x=(M/x+x)>>1, x=(M/x+x)>>1, x+=(M-x*x)>>31 )"
    set /A "N=36, sqrt=%Sqrt(N)%"
    rem or
    set /A "Sqrt=%Sqrt(N):N=36%"
    
    Rem With exception on negative input
    set "Sqrt(N)=( M=(N), x=M/(11*1024)+40, x=(M/x+x)>>1, x=(M/x+x)>>1, x=(M/x+x)>>1, x=(M/x+x)>>1, x=(M/x+x)>>1, x+=((M-x*x)>>31)/(1+((N)>>31)) )"
    set /A "Sqrt=%Sqrt(N):N=-32%"
    
  • Hypot(x,y)
  • Min(x,y)

    Code: Select all

    set /a "x=5, y=-6, min=y-((y-x)&((x-y)>>31))"
    echo !min!
  • Max(x,y)

    Code: Select all

    set /a "x=5, y=-6, max=(((((y-x)>>31)&1)*x)|((~(((y-x)>>31)&1)&1)*y))"
    echo !max!
    
    set /a "a=5, b=-6, max=((a-b)&((~(a-b))>>31))+b"
    echo !max!
    
    set /a "a=5, b=-6, max=a-(((a-b)>>31)&1)*(a-b)"
    echo !max!
    
    set /a "a=5, b=-6, max=((~((a-b)>>31))&a)|(((a-b)>>31)&b)"
    echo !max!
    
    set /a "a=5, b=-6, max=a^((a^b)&-((a-b)>>31&1))"
    echo !max!
    
    set /a "a=5, b=-6, max=(a+b+(((a-b)>>31|1)*(a-b)))/2"
    echo !max!
    
    rem this is shorter
    set /a "x=5, y=-6, max=x-((x-y)&((x-y)>>31))"
    echo !max!
    
  • Cmp(x,y)

    Code: Select all

    set "Cmp(x,y)=(x-y)>>31|((y-x)>>31&1)"
    set /A "x=3, y=5, C=%Cmp(x,y)%"
    
Last edited by einstein1969 on 19 Jun 2024 09:00, edited 11 times in total.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#4 Post by einstein1969 » 08 Aug 2014 03:31

example of implementation of sqrt(x):

input 32 bit integer output 32bit integer

by Antonio Acini:

Code: Select all

@echo off
setlocal EnableDelayedExpansion

set /A number=%1
set /A iter1=number/2+1

rem The maximum number of iterations to calculate sqrt of a 32 bits integer number is 20
set "sqrt="
for /L %%i in (1,1,20) do if not defined sqrt (
   set /A "iter2=number/iter1, iter1=(iter1+iter2)/2"
   if !iter2! geq !iter1! set /A "sqrt=(iter1+iter2)/2"
)
echo Sqrt(%number%) = %sqrt%
modified the first set to set /A (sugg. by dbenham)
modified the first iter1 adding + 1


backup of Judago STR_MATH.BAT v2.0:

Code: Select all

@ECHO Off
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
if "%~3"=="" (
    echo.
    echo. STR_MATH.BAT v2.0
    echo.
    echo.    Large number and floating point work around script for Addition,
    echo.    Subtraction, Division, Multiplaction, Modulus and exponents.
    echo.    Floating point and negative numbers supported.
    echo.
    echo.    Usage:
    echo.      STR_MATH.BAT {Number} {+-XM} {Number}
    echo.      STR_MATH.BAT {Number} {/Y} {Number} [n max decimal places]
    echo.
    echo.    Division and certain negative exponents default to a maximum
    echo.    of ten decimal places of output. An optional user defined max
    echo.    can be input as the fourth number when applicable.
    echo.
    echo.    No rounding is performed on output of division, the number
    echo.    is only truncated. For example a result of 0.1257 with three
    echo.    places will return 0.125 NOT 0.126.
    echo.
    echo.    Square brackets can be used on negative base exponents instead
    echo.    of parentheses if needed:
    echo.       STR_MATH [-5] Y 10
    echo.       STR_MATH -5 Y 10
    echo.
    echo.    Fractional exponents are not supported.
    echo.
    echo.   Results are not garanteed. Provided AS-IS.
    echo.
    echo.   Output Will be echo'd, a "for /f" loop can be used to
    echo.   capture the output. An example is as follows, be sure
    echo.   NOT to use the /A switch to SET if using a variable.
    echo.    
    echo.     FOR /F %%A IN ^(' STR_MATH.BAT 50.265 X 1.36 '^) DO SET RESULT=%%A
    echo.
    echo.   Variables must be expanded when passed to the script, like so:
    echo.
    echo.     FOR /F %%A IN ^(' STR_MATH.BAT %%number1%% / %%number2%% '^) DO SET RESULT=%%A
    echo.
    echo.   Judago  2011, 2015 ^& 2016.
    goto :eof
)
    



set _INP_NUM1=
set _INP_NUM2=
set _INP_NEG1=
set _INP_NEG2=
set _INP_OP=
set _RET_NUM=
set _OUT_DP=
set _OUT_NEG=
set _INP_EXP_PAR=0
set _INP_EXP_DIV=0
set "_INP_NUM1=%~1"
set "_INP_NUM2=%~3"
set "_INP_OP=%~2"
if /i "%~2"=="*" (
    set _INP_OP=x
)
if /i "%~2"=="%%" (
    set _INP_OP=m
)
 
    
rem Default number of digits for division
set _DIV_DP=10



rem ***** Start Input validation *****
for %%z in ("/" "y") do (
    if /i "%~2"=="%%~z" if not "%~4"=="" (
         for /f "delims=0123456789 tokens=1*" %%a in ("A0%~4") do (
            if "%%b"=="" (
                for /f "tokens=1* delims=0" %%c in ("A0%~4") do (
                    if "%%d"=="" (
                        set _DIV_DP=0
                    ) else set _DIV_DP=%%d
                )
            ) else (
                1>&2 Echo ERROR: "%~4" not detected as a valid number of decimal places
                goto :eof
            )
        )
        shift /4
    )
)
      
if not "%~4"=="" (
    1>&2 echo ERROR: Too many arguments: "%~4"
    goto :eof
)
if not defined _INP_NUM1 (
    1>&2 echo ERROR: Invalid Input: "!_INP_NUM1!"
    goto :eof
)
if not defined _INP_NUM2 (
    1>&2 echo ERROR: Invalid Input: "!_INP_NUM2!"
    goto :eof
)
if not defined _INP_OP (
    1>&2 echo ERROR: Invalid Operator: "!_INP_OP!"
    goto :eof
)
if not "!_INP_OP:~1!"=="" (
    1>&2 echo ERROR: Invalid Operator: "!_INP_OP!"
    goto :eof
)


rem **** check for brackets on base exponent ****
if /i "!_INP_OP!"=="Y" (
    for /f "tokens=1,2* delims=[]" %%a in ("A!_INP_NUM1!A]") do (
        if "%%a%%c"=="AA]" (
            set _INP_EXP_PAR=1
            set _INP_NUM1=!_INP_NUM1:[=!
            set _INP_NUM1=!_INP_NUM1:]=!
        )
    )
)

rem ***** / ***** Start Negitive input ***** \ *****
for %%a in (1 2) do (
if "!_INP_NUM%%a:~0,1!"=="-" if not "!_INP_NUM%%a:~1!"=="" (
        set _INP_NUM%%a=!_INP_NUM%%a:~1!
        set _INP_NEG%%a=-
    ) else (
        1>&2 echo ERROR: Invalid input: "!_INP_NUM%%a!"
        goto :eof
    )
)

if not defined _INPUT_NEG1 set _INP_EXP_PAR=0

rem ***** \ ***** End Negitive input ***** / *****




rem ****** Bad characters ******
for /f "tokens=1* delims=0123456789." %%a in ("A0!_INP_NUM1!!_INP_NUM2!") do (
    if not "%%b"=="" (
        1>&2 echo ERROR: Invalid input: "!_INP_NUM1!" **OR** "!_INP_NUM2!"
        goto :eof
    )
)

rem ***** Fractional exponenets aren't supported ******
for /f "tokens=1,2* delims=." %%a in ("0.!_INP_NUM1!!_INP_NUM2!") do (
    if not "%%c"=="" (
        1>&2 echo ERROR: Fractional exponents are not supported
        goto :eof
    )
)

for %%a in (1 2) do (
    for /f "tokens=1-3 delims=." %%b in ("0!_INP_NUM%%a!0") do (
        if not "%%d"=="" (
            1>&2 echo ERROR: Invalid input: "!_INP_NUM%%a!"
            goto :eof
        )
    )
)
for /f "tokens=1* delims=/Xx+-mMYy" %%a in ("A+!_INP_OP!") do (
    if not "%%b"=="" (
        1>&2 echo ERROR: Invalid operator: "!_INP_OP!"
        goto :eof
    )
)

rem ***** End input validation *****

rem ***** Start Remove leading zero's / Return for zero sums  *****
for %%a in (1 2) do (
    for /f "tokens=1* delims=0" %%b in ("A0!_INP_NUM%%a!") do (
        if "%%c"=="" (
            if /i "!_INP_OP!"=="x" echo 0
            if "!_INP_OP!"=="/" (
                if %%a==2 (
                    1>&2 Echo ERROR: Divide by zero
                    goto :eof
                ) else echo 0
            ) else if /i "!_INP_OP!"=="m" (
                if %%a==2 (
                    1>&2 Echo ERROR: Divide by zero
                    goto :eof
                ) else echo 0
            ) else if "!_INP_OP!"=="-" (
                if %%a==1 (
                    if defined _INP_NEG2 (
                        echo !_INP_NUM2!
                    ) else echo -!_INP_NUM2!
                ) else (
                    echo !_INP_NEG1!!_INP_NUM1!
                )
            ) else if "!_INP_OP!"=="+" (
                if %%a==1 (
                    echo !_INP_NEG2!!_INP_NUM2!
                ) else echo !_INP_NEG1!!_INP_NUM1!
            ) else if /i "!_INP_OP!"=="y" (
                if "%%a"=="1" (
                    if "!_INP_NEG2!"=="-" (
                        1>&2 echo ERROR: Negative exponents of zero are undefined.
                    ) else (
                        echo 0
                    )
                ) else (
                    if "!_INP_EXP_PAR!"=="1" (
                        echo 1
                    ) else (
                        echo !_INP_NEG1!1
                    )
                )
            )
            goto :eof
        ) else set _INP_NUM%%a=%%c
    )
)
rem ***** End Remove leading zero's / Return for zero sums *****

rem ***** Start Floating point normalisation *****
for %%a in (1 2) do (
    if "!_INP_NUM%%a:~0,1!"=="." set _INP_NUM%%a=0!_INP_NUM%%a!
    if "!_INP_NUM%%a:~-1!"=="." set _INP_NUM%%a=!_INP_NUM%%a!0
    for /l %%b in (0 1 9) do set _INP_NUM%%a=!_INP_NUM%%a:%%b=%%b !
    for %%c in (!_INP_NUM%%a!) do set /a _INP_LEN%%a+=1
    set _INP_NUM%%a=!_INP_NUM%%a: =!
    if "!_INP_NUM%%a!"=="!_INP_NUM%%a:.=!" (
        set _INP_DP%%a=0
    ) else (
        for /l %%d in (!_INP_LEN%%a! -1 1) do (
            if not defined _INP_DP%%a if "!_INP_NUM%%a:~%%d,1!"=="." (
                set /a _INP_DP%%a=!_INP_LEN%%a! - %%d
            )
        )
    )
    set _INP_NUM%%a=!_INP_NUM%%a:.=!
)

if !_INP_DP1! gtr !_INP_DP2! (
    set /a _OUT_DP=_INP_DP1 - 1
) else set /a _OUT_DP=_INP_DP2 - 1
for /l %%a in (!_INP_DP1! 1 !_OUT_DP!) do set _INP_NUM1=!_INP_NUM1!0
for /l %%a in (!_INP_DP2! 1 !_OUT_DP!) do set _INP_NUM2=!_INP_NUM2!0
rem ***** End Floating point normalisation *****

rem ***** Start Negitive output checking *****
if /i "!_INP_OP!"=="x" (
    if "!_INP_NEG1!!_INP_NEG2!"=="-" set _OUT_NEG=-
) else if "!_INP_OP!"=="+" (
    if "!_INP_NEG1!!_INP_NEG2!"=="--" set _OUT_NEG=-
    if defined _INP_NEG2 if not defined _INP_NEG1 (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
            set _OUT_NEG=-
        )
        set _INP_OP=-
    )
    if defined _INP_NEG1 if not defined _INP_NEG2 (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
        ) else if "!_GTR_RES!"=="1" set _OUT_NEG=-
        set _INP_OP=-
    )
) else if "!_INP_OP!"=="-" (
    if "!_INP_NEG1!!_INP_NEG2!"=="" (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
            set _OUT_NEG=-
        )
    )
    if "!_INP_NEG1!!_INP_NEG2!"=="--" (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
        ) else if "!_GTR_RES!"=="1" set _OUT_NEG=-
    )
    if defined _INP_NEG2 if not defined _INP_NEG1 set _INP_OP=+
    if defined _INP_NEG1 if not defined _INP_NEG2 (
        set _OUT_NEG=-
        set _INP_OP=+
    )
) else if "!_INP_OP!"=="/" (
    if "!_INP_NEG1!!_INP_NEG2!"=="--" set _OUT_NEG=
    if "!_INP_NEG1!!_INP_NEG2!"=="-"  set _OUT_NEG=-
) else if /i "!_INP_OP!"=="M" (
    if defined _INP_NEG1 set _OUT_NEG=-
) else if /i "!_INP_OP!"=="Y" (
    if "!_INP_EXP_PAR!"=="0" (
        if "!_INP_NEG1!!_INP_NEG2!"=="--" (
            set _OUT_NEG=-
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG2!"=="-" (
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG1!"=="-" (
            set _OUT_NEG=-
        )
    ) else (
        if "!_INP_NEG1!!_INP_NEG2!"=="--" (
            for %%z in (1 3 5 7 9) do (
                if "!_INP_NUM2:~-1!"=="%%z" set _OUT_NEG=-
            )
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG2!"=="-" (
            set _OUT_NEG=-
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG1!"=="-" (
            for %%z in (1 3 5 7 9) do (
                if "!_INP_NUM2:~-1!"=="%%z" set _OUT_NEG=-
            )
        )
    )
)

rem ***** End Negitive output checking *****


Rem Main calling routines



if "!_INP_OP!"=="-" (
    call :subtract %_INP_NUM1% %_INP_NUM2%
) else if "!_INP_OP!"=="+" (
    call :add %_INP_NUM1% %_INP_NUM2%
) else if /i "!_INP_OP!"=="X" (
    set /a _OUT_DP=^(_OUT_DP * 2^) + 1
    call :multiply %_INP_NUM1% %_INP_NUM2%
) else if /i "!_INP_OP!"=="m" (
    call :divide %_INP_NUM1% %_INP_NUM2% 0 m
) else if "!_INP_OP!"=="/" (
    call :divide %_INP_NUM1% %_INP_NUM2% %_DIV_DP%
    set /a _OUT_DP=_DIV_DP - 1
) else if /i "!_INP_OP!"=="Y" (
    call :EXPONENT !_INP_NUM1! !_INP_NUM2!
    if "!_INP_EXP_DIV!"=="1" (
        call :DIVIDE 1 !_RET_NUM! !_DIV_DP!
        set /a _OUT_DP=_DIV_DP - 1
    )
) else (
    1>&2 echo ERROR: Unknown operator.
    goto :eof
)



rem finishing up.....

set _FIN_FLAG=
if not defined _OUT_DP (
    set _OUT_DP=0
) else set /a _OUT_DP+=1
if not "!_OUT_DP!"=="0" (
    for /l %%a in (1 1 !_OUT_DP!) do if "!_RET_NUM:~%_OUT_DP%!"=="" set _RET_NUM=0!_RET_NUM!
    set _RET_NUM=!_RET_NUM:~0^,-%_OUT_DP%!.!_RET_NUM:~-%_OUT_DP%!
    for /l %%a in (!_OUT_DP! -1 1) do (
        if "!_RET_NUM:~-1!"=="0" set _RET_NUM=!_RET_NUM:~0^,-1!
        if "!_RET_NUM:~-1!"=="." set _RET_NUM=!_RET_NUM:~0^,-1!
    )
)
for /f "tokens=1* delims=0" %%a in ("A0!_RET_NUM!") do (
    if "%%b"=="" (
        set _RET_NUM=0
    ) else set _RET_NUM=%%b
)
if "!_RET_NUM:~0,1!"=="." set _RET_NUM=0!_RET_NUM!
IF NOT DEFINED _RET_NUM SET _RET_NUM=0
echo %_OUT_NEG%%_RET_NUM%




goto :eof

rem ***********************************************************************
rem ****************************** Subroutines ****************************
rem ***********************************************************************

:SUBTRACT
set _SUB_NUM1=%~1
set _SUB_NUM2=%~2
for %%a in (CHA RES LEN1 LEN2 CAR TOT) do set _SUB_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _SUB_NUM%%a=!_SUB_NUM%%a:%%b=%%b !
    for %%c in (!_SUB_NUM%%a!) do set /a _SUB_LEN%%a+=1
    set _SUB_NUM%%a=!_SUB_NUM%%a: =!
)
if !_SUB_LEN1! gtr !_SUB_LEN2! (
    set /a _SUB_LEN=_SUB_LEN1 - 1
) else set /a _SUB_LEN=_SUB_LEN2 - 1
for /l %%b in (!_SUB_LEN1! 1 !_SUB_LEN!) do set _SUB_NUM1=0!_SUB_NUM1!
for /l %%b in (!_SUB_LEN2! 1 !_SUB_LEN!) do set _SUB_NUM2=0!_SUB_NUM2!
for /l %%a in (!_SUB_LEN! -1 0) do (
    set /a _SUB_RES=!_SUB_NUM1:~%%a,1! - !_SUB_NUM2:~%%a,1!
    if !_SUB_RES! lss 0 (
        set _SUB_TAKE=
        for /l %%b in (%%a -1 0) do if not defined _SUB_TAKE (
            if not "%%b"=="%%a" (
                if not "!_SUB_NUM1:~%%b,1!"=="0" (
                    set /a _SUB_CHA=!_SUB_NUM1:~%%b,1! - 1
                    set /a _SUB_TAKE=%%b + 1
                    for %%c in (!_SUB_TAKE!) do set _SUB_NUM1=!_SUB_NUM1:~0,%%b!!_SUB_CHA!!_SUB_NUM1:~%%c!
                    set /a _SUB_RES=1!_SUB_NUM1:~%%a,1! - !_SUB_NUM2:~%%a,1!

                ) else (
                    set /a _SUB_CHA=%%b + 1
                    for %%c in (!_SUB_CHA!) do set _SUB_NUM1=!_SUB_NUM1:~0,%%b!9!_SUB_NUM1:~%%c!
                )
            )
        )
    )
    set _SUB_TOT=!_SUB_RES!!_SUB_TOT!
)
for /f "tokens=1* delims=0" %%a in ("A0!_SUB_TOT!") do set _SUB_TOT=%%b
if not defined _SUB_TOT set _SUB_TOT=0
set _RET_NUM=%_SUB_TOT%
goto :eof


:ADD
set _ADD_NUM1=%~1
set _ADD_NUM2=%~2
for %%a in (LEN1 LEN2 CAR TOT) do set _ADD_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _ADD_NUM%%a=!_ADD_NUM%%a:%%b=%%b !
    for %%c in (!_ADD_NUM%%a!) do set /a _ADD_LEN%%a+=1
    set _ADD_NUM%%a=!_ADD_NUM%%a: =!
)
if !_ADD_LEN1! gtr !_ADD_LEN2! (
    set /a _ADD_LEN=_ADD_LEN1 - 1
) else set /a _ADD_LEN=_ADD_LEN2 - 1
for /l %%b in (!_ADD_LEN1! 1 !_ADD_LEN!) do set _ADD_NUM1=0!_ADD_NUM1!
for /l %%b in (!_ADD_LEN2! 1 !_ADD_LEN!) do set _ADD_NUM2=0!_ADD_NUM2!
for /l %%a in (!_ADD_LEN! -1 0) do (
    set /a _ADD_CAR=_ADD_CAR + !_ADD_NUM1:~%%a,1! + !_ADD_NUM2:~%%a,1!
    set _ADD_TOT=!_ADD_CAR:~-1!!_ADD_TOT!
    set _ADD_CAR=!_ADD_CAR:~0,-1!
    if not defined _ADD_CAR set _ADD_CAR=0
)
if !_ADD_CAR! gtr 0 set _ADD_TOT=!_ADD_CAR!!_ADD_TOT!
set _RET_NUM=%_ADD_TOT%
goto :eof


:MULTIPLY
set _MUL_NUM1=%1
set _MUL_NUM2=%2
for %%a in (CAR TOT) do set _MUL_%%a=0
for %%a in (X10 REV1 REV2) do set _MUL_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _MUL_NUM%%a=!_MUL_NUM%%a:%%b=%%b !
    for %%c in (!_MUL_NUM%%a!) do set _MUL_REV%%a=%%c !_MUL_REV%%a!
)
for %%a in (!_MUL_REV1!) do (
    set _MUL_ROW=!_MUL_X10!
    for %%b in (!_MUL_REV2!) do (
        set /a _MUL_CAR=^(%%a * %%b^) + _MUL_CAR
        set _MUL_ROW=!_MUL_CAR:~-1!!_MUL_ROW!
        set _MUL_CAR=!_MUL_CAR:~0,-1!
        if not defined _MUL_CAR set _MUL_CAR=0
    )
    for /f "tokens=1* delims=0" %%c in ("A0!_MUL_CAR!!_MUL_ROW!") do (
        if not "%%d"=="" (
            call :ADD %%d !_MUL_TOT!
            set _MUL_TOT=!_RET_NUM!
        )
    )
    set _MUL_CAR=
    set _MUL_X10=!_MUL_X10!0
)
set _RET_NUM=%_MUL_TOT%
goto :eof

:DIVIDE
for %%a in (LEN1 LEN2 X10 PAD) do set _DIV_%%a=
if /i not "%4"=="m" (
    for /l %%a in (1 1 %3) do set _DIV_PAD=0!_DIV_PAD!
)
set _DIV_NUM1=%1!_DIV_PAD!
set _DIV_NUM2=%2
set _DIV_TOT=0
set _DIV_PRC=1
for %%a in (1 2) do (
    for /f "tokens=1* delims=0" %%d in ("A0!_DIV_NUM%%a!") do set _DIV_NUM%%a=%%e
    for /l %%b in (0 1 9) do set _DIV_NUM%%a=!_DIV_NUM%%a:%%b=%%b !
    for %%c in (!_DIV_NUM%%a!) do set /a _DIV_LEN%%a+=1
    set _DIV_NUM%%a=!_DIV_NUM%%a: =!
)
set /a _DIV_LEN1-=1
for /l %%a in (!_DIV_LEN2! 1 !_DIV_LEN1!) do set _DIV_X10=0!_DIV_X10!

:__DIVINL
    call :ISGREATER %_DIV_NUM1% %_DIV_NUM2%%_DIV_X10%
    if %_GTR_RES% leq 2 (
        call :SUBTRACT %_DIV_NUM1% %_DIV_NUM2%%_DIV_X10%
        set _DIV_NUM1=!_RET_NUM!
        call :ADD %_DIV_TOT% 1%_DIV_X10%
        set _DIV_TOT=!_RET_NUM!
    ) else if defined _DIV_X10 (
        set _DIV_X10=!_DIV_X10:~1!
    ) else set _DIV_PRC=0
    if "!_DIV_PRC!"=="1" goto :__DIVINL
if /i "%4"=="m" set _DIV_TOT=!_DIV_NUM1!
set _RET_NUM=%_DIV_TOT%
Goto :eof

:EXPONENT
set _EXP_SRL=
set _EXP_SQU=%2
set _EXP_TOT=%1
set _EXP_PRC=1
:__EXINL
    call :DIVIDE !_EXP_SQU!0 2 0
    set _EXP_SQU=%_RET_NUM%
    if "%_EXP_SQU:~-1%"=="5" (
        set _EXP_SRL=%_EXP_SRL% %_EXP_TOT%
    )
    set _EXP_SQU=%_EXP_SQU:~0,-1%
    if "%_EXP_SQU%"=="" ( 
        set _EXP_PRC=0
    ) else (
        call :MULTIPLY %_EXP_TOT% %_EXP_TOT%
        set _EXP_TOT=!_RET_NUM!
        if "%_EXP_SQU%"=="3" (
            set _EXP_SRL=!_EXP_SRL! !_EXP_TOT!
            set _EXP_SQU=2
        )
        if "!_EXP_SQU!"=="2" (
            call :MULTIPLY !_EXP_TOT! !_EXP_TOT!
            set _EXP_TOT=!_RET_NUM!
            for %%z in (!_EXP_SRL!) do (
                call :MULTIPLY !_EXP_TOT! %%z
                set _EXP_TOT=!_RET_NUM!
            )
            set _EXP_PRC=0
        )
        if "!_EXP_SQU!"=="1" (
            set _EXP_PRC=0
        )
    )
    if "!_EXP_PRC!"=="1" goto __EXINL
set _RET_NUM=!_EXP_TOT!
goto :eof


:ISGREATER
set _GTR_RES=
for /f "tokens=1* delims=0" %%a in ("A0%~1") do (
    if "%%b"=="" (
        set _GTR_NUM1=0
    ) else set _GTR_NUM1=%%b
)
for /f "tokens=1* delims=0" %%a in ("A0%~2") do (
    if "%%b"=="" (
        set _GTR_NUM2=0
    ) else set _GTR_NUM2=%%b
)
for %%a in (lEN1 lEN2) do set _GTR_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _GTR_NUM%%a=!_GTR_NUM%%a:%%b=%%b !
    for %%c in (!_GTR_NUM%%a!) do set /a _GTR_lEN%%a+=1
    set _GTR_NUM%%a=!_GTR_NUM%%a: =!
)
if !_GTR_lEN1! gtr !_GTR_lEN2! (
    set _GTR_RES=1
) else if !_GTR_lEN2! gtr !_GTR_lEN1! (
    set _GTR_RES=3
) else (
    set /a _GTR_lEN1-=1
    for /l %%a in (0 1 !_GTR_lEN1!) do (
        if not defined _GTR_RES (
            if !_GTR_NUM1:~%%a^,1! gtr !_GTR_NUM2:~%%a^,1! set _GTR_RES=1
            if !_GTR_NUM2:~%%a^,1! gtr !_GTR_NUM1:~%%a^,1! set _GTR_RES=3
        )
    )
)
if not defined _GTR_RES set _GTR_RES=2
goto :eof
judago 2.1:

Code: Select all

@ECHO Off
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
if "%~3"=="" (
    echo.
    echo. STR_MATH.BAT v2.1
    echo.
    echo.    Large number and floating point work around script for Addition,
    echo.    Subtraction, Division, Multiplaction, Modulus and exponents.
    echo.    Floating point and negative numbers supported.
    echo.
    echo.    Usage:
    echo.      STR_MATH.BAT {Number} {+-XM} {Number}
    echo.      STR_MATH.BAT {Number} {/Y} {Number} [n max decimal places]
    echo.
    echo.    Division and certain negative exponents default to a maximum
    echo.    of ten decimal places of output. An optional user defined max
    echo.    can be input as the fourth number when applicable.
    echo.
    echo.    No rounding is performed on output of division, the number
    echo.    is only truncated. For example a result of 0.1257 with three
    echo.    places will return 0.125 NOT 0.126.
    echo.
    echo.    Square brackets can be used on negative base exponents instead
    echo.    of parentheses if needed:
    echo.       STR_MATH [-5] Y 10
    echo.       STR_MATH -5 Y 10
    echo.
    echo.    Fractional exponents are not supported.
    echo.
    echo.   Results are not garanteed. Provided AS-IS.
    echo.
    echo.   Output Will be echo'd, a "for /f" loop can be used to
    echo.   capture the output. An example is as follows, be sure
    echo.   NOT to use the /A switch to SET if using a variable.
    echo.    
    echo.     FOR /F %%A IN ^(' STR_MATH.BAT 50.265 X 1.36 '^) DO SET RESULT=%%A
    echo.
    echo.   Variables must be expanded when passed to the script, like so:
    echo.
    echo.     FOR /F %%A IN ^(' STR_MATH.BAT %%number1%% / %%number2%% '^) DO SET RESULT=%%A
    echo.
    echo.   Judago  2011, 2015 ^& 2016.
    goto :eof
)
    



set _INP_NUM1=
set _INP_NUM2=
set _INP_NEG1=
set _INP_NEG2=
set _INP_OP=
set _RET_NUM=
set _OUT_DP=
set _OUT_NEG=
set _INP_EXP_PAR=0
set _INP_EXP_DIV=0
set "_INP_NUM1=%~1"
set "_INP_NUM2=%~3"
set "_INP_OP=%~2"
if /i "%~2"=="*" (
    set _INP_OP=x
)
if /i "%~2"=="%%" (
    set _INP_OP=m
)
 
    
rem Default number of digits for division
set _DIV_DP=10



rem ***** Start Input validation *****
for %%z in ("/" "y") do (
    if /i "%~2"=="%%~z" if not "%~4"=="" (
         for /f "delims=0123456789 tokens=1*" %%a in ("A0%~4") do (
            if "%%b"=="" (
                for /f "tokens=1* delims=0" %%c in ("A0%~4") do (
                    if "%%d"=="" (
                        set _DIV_DP=0
                    ) else set _DIV_DP=%%d
                )
            ) else (
                1>&2 Echo ERROR: "%~4" not detected as a valid number of decimal places
                goto :eof
            )
        )
        shift /4
    )
)
      
if not "%~4"=="" (
    1>&2 echo ERROR: Too many arguments: "%~4"
    goto :eof
)
if not defined _INP_NUM1 (
    1>&2 echo ERROR: Invalid Input: "!_INP_NUM1!"
    goto :eof
)
if not defined _INP_NUM2 (
    1>&2 echo ERROR: Invalid Input: "!_INP_NUM2!"
    goto :eof
)
if not defined _INP_OP (
    1>&2 echo ERROR: Invalid Operator: "!_INP_OP!"
    goto :eof
)
if not "!_INP_OP:~1!"=="" (
    1>&2 echo ERROR: Invalid Operator: "!_INP_OP!"
    goto :eof
)


rem **** check for brackets on base exponent ****
if /i "!_INP_OP!"=="Y" (
    for /f "tokens=1,2* delims=[]" %%a in ("A!_INP_NUM1!A]") do (
        if "%%a%%c"=="AA]" (
            set _INP_EXP_PAR=1
            set _INP_NUM1=!_INP_NUM1:[=!
            set _INP_NUM1=!_INP_NUM1:]=!
        )
    )
)

rem ***** / ***** Start Negitive input ***** \ *****
for %%a in (1 2) do (
if "!_INP_NUM%%a:~0,1!"=="-" if not "!_INP_NUM%%a:~1!"=="" (
        set _INP_NUM%%a=!_INP_NUM%%a:~1!
        set _INP_NEG%%a=-
    ) else (
        1>&2 echo ERROR: Invalid input: "!_INP_NUM%%a!"
        goto :eof
    )
)

if not defined _INPUT_NEG1 set _INP_EXP_PAR=0

rem ***** \ ***** End Negitive input ***** / *****




rem ****** Bad characters ******
for /f "tokens=1* delims=0123456789." %%a in ("A0!_INP_NUM1!!_INP_NUM2!") do (
    if not "%%b"=="" (
        1>&2 echo ERROR: Invalid input: "!_INP_NUM1!" **OR** "!_INP_NUM2!"
        goto :eof
    )
)

rem ***** Fractional exponenets aren't supported ******
if /i "!_INP_OP!"=="y" (
    for /f "tokens=1,2* delims=." %%a in ("0.!_INP_NUM1!!_INP_NUM2!") do (
        if not "%%c"=="" (
            1>&2 echo ERROR: Fractional exponents are not supported
            goto :eof
        )
    )
)

for %%a in (1 2) do (
    for /f "tokens=1-3 delims=." %%b in ("0!_INP_NUM%%a!0") do (
        if not "%%d"=="" (
            1>&2 echo ERROR: Invalid input: "!_INP_NUM%%a!"
            goto :eof
        )
    )
)
for /f "tokens=1* delims=/Xx+-mMYy" %%a in ("A+!_INP_OP!") do (
    if not "%%b"=="" (
        1>&2 echo ERROR: Invalid operator: "!_INP_OP!"
        goto :eof
    )
)

rem ***** End input validation *****

rem ***** Start Remove leading zero's / Return for zero sums  *****
for %%a in (1 2) do (
    for /f "tokens=1* delims=0" %%b in ("A0!_INP_NUM%%a!") do (
        if "%%c"=="" (
            if /i "!_INP_OP!"=="x" echo 0
            if "!_INP_OP!"=="/" (
                if %%a==2 (
                    1>&2 Echo ERROR: Divide by zero
                    goto :eof
                ) else echo 0
            ) else if /i "!_INP_OP!"=="m" (
                if %%a==2 (
                    1>&2 Echo ERROR: Divide by zero
                    goto :eof
                ) else echo 0
            ) else if "!_INP_OP!"=="-" (
                if %%a==1 (
                    if defined _INP_NEG2 (
                        echo !_INP_NUM2!
                    ) else echo -!_INP_NUM2!
                ) else (
                    echo !_INP_NEG1!!_INP_NUM1!
                )
            ) else if "!_INP_OP!"=="+" (
                if %%a==1 (
                    echo !_INP_NEG2!!_INP_NUM2!
                ) else echo !_INP_NEG1!!_INP_NUM1!
            ) else if /i "!_INP_OP!"=="y" (
                if "%%a"=="1" (
                    if "!_INP_NEG2!"=="-" (
                        1>&2 echo ERROR: Negative exponents of zero are undefined.
                    ) else (
                        echo 0
                    )
                ) else (
                    if "!_INP_EXP_PAR!"=="1" (
                        echo 1
                    ) else (
                        echo !_INP_NEG1!1
                    )
                )
            )
            goto :eof
        ) else set _INP_NUM%%a=%%c
    )
)
rem ***** End Remove leading zero's / Return for zero sums *****

rem ***** Start Floating point normalisation *****
for %%a in (1 2) do (
    if "!_INP_NUM%%a:~0,1!"=="." set _INP_NUM%%a=0!_INP_NUM%%a!
    if "!_INP_NUM%%a:~-1!"=="." set _INP_NUM%%a=!_INP_NUM%%a!0
    for /l %%b in (0 1 9) do set _INP_NUM%%a=!_INP_NUM%%a:%%b=%%b !
    for %%c in (!_INP_NUM%%a!) do set /a _INP_LEN%%a+=1
    set _INP_NUM%%a=!_INP_NUM%%a: =!
    if "!_INP_NUM%%a!"=="!_INP_NUM%%a:.=!" (
        set _INP_DP%%a=0
    ) else (
        for /l %%d in (!_INP_LEN%%a! -1 1) do (
            if not defined _INP_DP%%a if "!_INP_NUM%%a:~%%d,1!"=="." (
                set /a _INP_DP%%a=!_INP_LEN%%a! - %%d
            )
        )
    )
    set _INP_NUM%%a=!_INP_NUM%%a:.=!
)

if !_INP_DP1! gtr !_INP_DP2! (
    set /a _OUT_DP=_INP_DP1 - 1
) else set /a _OUT_DP=_INP_DP2 - 1
for /l %%a in (!_INP_DP1! 1 !_OUT_DP!) do set _INP_NUM1=!_INP_NUM1!0
for /l %%a in (!_INP_DP2! 1 !_OUT_DP!) do set _INP_NUM2=!_INP_NUM2!0
rem ***** End Floating point normalisation *****

rem ***** Start Negitive output checking *****
if /i "!_INP_OP!"=="x" (
    if "!_INP_NEG1!!_INP_NEG2!"=="-" set _OUT_NEG=-
) else if "!_INP_OP!"=="+" (
    if "!_INP_NEG1!!_INP_NEG2!"=="--" set _OUT_NEG=-
    if defined _INP_NEG2 if not defined _INP_NEG1 (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
            set _OUT_NEG=-
        )
        set _INP_OP=-
    )
    if defined _INP_NEG1 if not defined _INP_NEG2 (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
        ) else if "!_GTR_RES!"=="1" set _OUT_NEG=-
        set _INP_OP=-
    )
) else if "!_INP_OP!"=="-" (
    if "!_INP_NEG1!!_INP_NEG2!"=="" (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
            set _OUT_NEG=-
        )
    )
    if "!_INP_NEG1!!_INP_NEG2!"=="--" (
        call :isgreater !_INP_NUM1! !_INP_NUM2!
        if "!_GTR_RES!"=="3" (
            set _INP_NUM1=%_INP_NUM2%
            set _INP_NUM2=%_INP_NUM1%
        ) else if "!_GTR_RES!"=="1" set _OUT_NEG=-
    )
    if defined _INP_NEG2 if not defined _INP_NEG1 set _INP_OP=+
    if defined _INP_NEG1 if not defined _INP_NEG2 (
        set _OUT_NEG=-
        set _INP_OP=+
    )
) else if "!_INP_OP!"=="/" (
    if "!_INP_NEG1!!_INP_NEG2!"=="--" set _OUT_NEG=
    if "!_INP_NEG1!!_INP_NEG2!"=="-"  set _OUT_NEG=-
) else if /i "!_INP_OP!"=="M" (
    if defined _INP_NEG1 set _OUT_NEG=-
) else if /i "!_INP_OP!"=="Y" (
    if "!_INP_EXP_PAR!"=="0" (
        if "!_INP_NEG1!!_INP_NEG2!"=="--" (
            set _OUT_NEG=-
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG2!"=="-" (
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG1!"=="-" (
            set _OUT_NEG=-
        )
    ) else (
        if "!_INP_NEG1!!_INP_NEG2!"=="--" (
            for %%z in (1 3 5 7 9) do (
                if "!_INP_NUM2:~-1!"=="%%z" set _OUT_NEG=-
            )
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG2!"=="-" (
            set _OUT_NEG=-
            set _INP_EXP_DIV=1
        ) else if "!_INP_NEG1!"=="-" (
            for %%z in (1 3 5 7 9) do (
                if "!_INP_NUM2:~-1!"=="%%z" set _OUT_NEG=-
            )
        )
    )
)

rem ***** End Negitive output checking *****


Rem Main calling routines



if "!_INP_OP!"=="-" (
    call :subtract %_INP_NUM1% %_INP_NUM2%
) else if "!_INP_OP!"=="+" (
    call :add %_INP_NUM1% %_INP_NUM2%
) else if /i "!_INP_OP!"=="X" (
    set /a _OUT_DP=^(_OUT_DP * 2^) + 1
    call :multiply %_INP_NUM1% %_INP_NUM2%
) else if /i "!_INP_OP!"=="m" (
    call :divide %_INP_NUM1% %_INP_NUM2% 0 m
) else if "!_INP_OP!"=="/" (
    call :divide %_INP_NUM1% %_INP_NUM2% %_DIV_DP%
    set /a _OUT_DP=_DIV_DP - 1
) else if /i "!_INP_OP!"=="Y" (
    call :EXPONENT !_INP_NUM1! !_INP_NUM2!
    if "!_INP_EXP_DIV!"=="1" (
        call :DIVIDE 1 !_RET_NUM! !_DIV_DP!
        set /a _OUT_DP=_DIV_DP - 1
    )
) else (
    1>&2 echo ERROR: Unknown operator.
    goto :eof
)



rem finishing up.....


if not defined _OUT_DP (
    set _OUT_DP=0
) else set /a _OUT_DP+=1
if not "!_OUT_DP!"=="0" (
    for /l %%a in (1 1 !_OUT_DP!) do if "!_RET_NUM:~%_OUT_DP%!"=="" set _RET_NUM=0!_RET_NUM!
    set _RET_NUM=!_RET_NUM:~0^,-%_OUT_DP%!.!_RET_NUM:~-%_OUT_DP%!
    for /l %%a in (!_OUT_DP! -1 1) do (
        if "!_RET_NUM:~-1!"=="0" set _RET_NUM=!_RET_NUM:~0^,-1!
        if "!_RET_NUM:~-1!"=="." set _RET_NUM=!_RET_NUM:~0^,-1!
    )
)
for /f "tokens=1* delims=0" %%a in ("A0!_RET_NUM!") do (
    if "%%b"=="" (
        set _RET_NUM=0
    ) else set _RET_NUM=%%b
)
if "!_RET_NUM:~0,1!"=="." set _RET_NUM=0!_RET_NUM!
IF NOT DEFINED _RET_NUM SET _RET_NUM=0
echo %_OUT_NEG%%_RET_NUM%




goto :eof

rem ***********************************************************************
rem ****************************** Subroutines ****************************
rem ***********************************************************************

:SUBTRACT
set _SUB_NUM1=%~1
set _SUB_NUM2=%~2
for %%a in (CHA RES LEN1 LEN2 CAR TOT) do set _SUB_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _SUB_NUM%%a=!_SUB_NUM%%a:%%b=%%b !
    for %%c in (!_SUB_NUM%%a!) do set /a _SUB_LEN%%a+=1
    set _SUB_NUM%%a=!_SUB_NUM%%a: =!
)
if !_SUB_LEN1! gtr !_SUB_LEN2! (
    set /a _SUB_LEN=_SUB_LEN1 - 1
) else set /a _SUB_LEN=_SUB_LEN2 - 1
for /l %%b in (!_SUB_LEN1! 1 !_SUB_LEN!) do set _SUB_NUM1=0!_SUB_NUM1!
for /l %%b in (!_SUB_LEN2! 1 !_SUB_LEN!) do set _SUB_NUM2=0!_SUB_NUM2!
for /l %%a in (!_SUB_LEN! -1 0) do (
    set /a _SUB_RES=!_SUB_NUM1:~%%a,1! - !_SUB_NUM2:~%%a,1!
    if !_SUB_RES! lss 0 (
        set _SUB_TAKE=
        for /l %%b in (%%a -1 0) do if not defined _SUB_TAKE (
            if not "%%b"=="%%a" (
                if not "!_SUB_NUM1:~%%b,1!"=="0" (
                    set /a _SUB_CHA=!_SUB_NUM1:~%%b,1! - 1
                    set /a _SUB_TAKE=%%b + 1
                    for %%c in (!_SUB_TAKE!) do set _SUB_NUM1=!_SUB_NUM1:~0,%%b!!_SUB_CHA!!_SUB_NUM1:~%%c!
                    set /a _SUB_RES=1!_SUB_NUM1:~%%a,1! - !_SUB_NUM2:~%%a,1!

                ) else (
                    set /a _SUB_CHA=%%b + 1
                    for %%c in (!_SUB_CHA!) do set _SUB_NUM1=!_SUB_NUM1:~0,%%b!9!_SUB_NUM1:~%%c!
                )
            )
        )
    )
    set _SUB_TOT=!_SUB_RES!!_SUB_TOT!
)
for /f "tokens=1* delims=0" %%a in ("A0!_SUB_TOT!") do set _SUB_TOT=%%b
if not defined _SUB_TOT set _SUB_TOT=0
set _RET_NUM=%_SUB_TOT%
goto :eof


:ADD
set _ADD_NUM1=%~1
set _ADD_NUM2=%~2
for %%a in (LEN1 LEN2 CAR TOT) do set _ADD_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _ADD_NUM%%a=!_ADD_NUM%%a:%%b=%%b !
    for %%c in (!_ADD_NUM%%a!) do set /a _ADD_LEN%%a+=1
    set _ADD_NUM%%a=!_ADD_NUM%%a: =!
)
if !_ADD_LEN1! gtr !_ADD_LEN2! (
    set /a _ADD_LEN=_ADD_LEN1 - 1
) else set /a _ADD_LEN=_ADD_LEN2 - 1
for /l %%b in (!_ADD_LEN1! 1 !_ADD_LEN!) do set _ADD_NUM1=0!_ADD_NUM1!
for /l %%b in (!_ADD_LEN2! 1 !_ADD_LEN!) do set _ADD_NUM2=0!_ADD_NUM2!
for /l %%a in (!_ADD_LEN! -1 0) do (
    set /a _ADD_CAR=_ADD_CAR + !_ADD_NUM1:~%%a,1! + !_ADD_NUM2:~%%a,1!
    set _ADD_TOT=!_ADD_CAR:~-1!!_ADD_TOT!
    set _ADD_CAR=!_ADD_CAR:~0,-1!
    if not defined _ADD_CAR set _ADD_CAR=0
)
if !_ADD_CAR! gtr 0 set _ADD_TOT=!_ADD_CAR!!_ADD_TOT!
set _RET_NUM=%_ADD_TOT%
goto :eof


:MULTIPLY
set _MUL_NUM1=%1
set _MUL_NUM2=%2
for %%a in (CAR TOT) do set _MUL_%%a=0
for %%a in (X10 REV1 REV2) do set _MUL_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _MUL_NUM%%a=!_MUL_NUM%%a:%%b=%%b !
    for %%c in (!_MUL_NUM%%a!) do set _MUL_REV%%a=%%c !_MUL_REV%%a!
)
for %%a in (!_MUL_REV1!) do (
    set _MUL_ROW=!_MUL_X10!
    for %%b in (!_MUL_REV2!) do (
        set /a _MUL_CAR=^(%%a * %%b^) + _MUL_CAR
        set _MUL_ROW=!_MUL_CAR:~-1!!_MUL_ROW!
        set _MUL_CAR=!_MUL_CAR:~0,-1!
        if not defined _MUL_CAR set _MUL_CAR=0
    )
    for /f "tokens=1* delims=0" %%c in ("A0!_MUL_CAR!!_MUL_ROW!") do (
        if not "%%d"=="" (
            call :ADD %%d !_MUL_TOT!
            set _MUL_TOT=!_RET_NUM!
        )
    )
    set _MUL_CAR=
    set _MUL_X10=!_MUL_X10!0
)
set _RET_NUM=%_MUL_TOT%
goto :eof

:DIVIDE
for %%a in (LEN1 LEN2 X10 PAD) do set _DIV_%%a=
if /i not "%4"=="m" (
    for /l %%a in (1 1 %3) do set _DIV_PAD=0!_DIV_PAD!
)
set _DIV_NUM1=%1!_DIV_PAD!
set _DIV_NUM2=%2
set _DIV_TOT=0
set _DIV_PRC=1
for %%a in (1 2) do (
    for /f "tokens=1* delims=0" %%d in ("A0!_DIV_NUM%%a!") do set _DIV_NUM%%a=%%e
    for /l %%b in (0 1 9) do set _DIV_NUM%%a=!_DIV_NUM%%a:%%b=%%b !
    for %%c in (!_DIV_NUM%%a!) do set /a _DIV_LEN%%a+=1
    set _DIV_NUM%%a=!_DIV_NUM%%a: =!
)
set /a _DIV_LEN1-=1
for /l %%a in (!_DIV_LEN2! 1 !_DIV_LEN1!) do set _DIV_X10=0!_DIV_X10!

:__DIVINL
    call :ISGREATER %_DIV_NUM1% %_DIV_NUM2%%_DIV_X10%
    if %_GTR_RES% leq 2 (
        call :SUBTRACT %_DIV_NUM1% %_DIV_NUM2%%_DIV_X10%
        set _DIV_NUM1=!_RET_NUM!
        call :ADD %_DIV_TOT% 1%_DIV_X10%
        set _DIV_TOT=!_RET_NUM!
    ) else if defined _DIV_X10 (
        set _DIV_X10=!_DIV_X10:~1!
    ) else set _DIV_PRC=0
    if "!_DIV_PRC!"=="1" goto :__DIVINL
if /i "%4"=="m" set _DIV_TOT=!_DIV_NUM1!
set _RET_NUM=%_DIV_TOT%
Goto :eof

:EXPONENT
set _EXP_SRL=
set _EXP_SQU=%2
set _EXP_TOT=%1
set _EXP_PRC=1
:__EXINL
    call :DIVIDE !_EXP_SQU!0 2 0
    set _EXP_SQU=%_RET_NUM%
    if "%_EXP_SQU:~-1%"=="5" (
        set _EXP_SRL=%_EXP_SRL% %_EXP_TOT%
    )
    set _EXP_SQU=%_EXP_SQU:~0,-1%
    if "%_EXP_SQU%"=="" ( 
        set _EXP_PRC=0
    ) else (
        call :MULTIPLY %_EXP_TOT% %_EXP_TOT%
        set _EXP_TOT=!_RET_NUM!
        if "%_EXP_SQU%"=="3" (
            set _EXP_SRL=!_EXP_SRL! !_EXP_TOT!
            set _EXP_SQU=2
        )
        if "!_EXP_SQU!"=="2" (
            call :MULTIPLY !_EXP_TOT! !_EXP_TOT!
            set _EXP_TOT=!_RET_NUM!
            for %%z in (!_EXP_SRL!) do (
                call :MULTIPLY !_EXP_TOT! %%z
                set _EXP_TOT=!_RET_NUM!
            )
            set _EXP_PRC=0
        )
        if "!_EXP_SQU!"=="1" (
            set _EXP_PRC=0
        )
    )
    if "!_EXP_PRC!"=="1" goto __EXINL
set _RET_NUM=!_EXP_TOT!
goto :eof


:ISGREATER
set _GTR_RES=
for /f "tokens=1* delims=0" %%a in ("A0%~1") do (
    if "%%b"=="" (
        set _GTR_NUM1=0
    ) else set _GTR_NUM1=%%b
)
for /f "tokens=1* delims=0" %%a in ("A0%~2") do (
    if "%%b"=="" (
        set _GTR_NUM2=0
    ) else set _GTR_NUM2=%%b
)
for %%a in (lEN1 lEN2) do set _GTR_%%a=
for %%a in (1 2) do (
    for /l %%b in (0 1 9) do set _GTR_NUM%%a=!_GTR_NUM%%a:%%b=%%b !
    for %%c in (!_GTR_NUM%%a!) do set /a _GTR_lEN%%a+=1
    set _GTR_NUM%%a=!_GTR_NUM%%a: =!
)
if !_GTR_lEN1! gtr !_GTR_lEN2! (
    set _GTR_RES=1
) else if !_GTR_lEN2! gtr !_GTR_lEN1! (
    set _GTR_RES=3
) else (
    set /a _GTR_lEN1-=1
    for /l %%a in (0 1 !_GTR_lEN1!) do (
        if not defined _GTR_RES (
            if !_GTR_NUM1:~%%a^,1! gtr !_GTR_NUM2:~%%a^,1! set _GTR_RES=1
            if !_GTR_NUM2:~%%a^,1! gtr !_GTR_NUM1:~%%a^,1! set _GTR_RES=3
        )
    )
)
if not defined _GTR_RES set _GTR_RES=2
goto :eof
Last edited by einstein1969 on 27 May 2023 14:00, edited 6 times in total.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Dos Batch Math Library

#5 Post by dbenham » 09 Aug 2014 14:28

For SQRT.BAT, modify initial assignment to use SET /A

Code: Select all

set /a number=%1

Then you can enter hex notation for a number, or compute the integral approximation of the square root of a mathematical expression. It is also a good way to test the result.

Code: Select all

C:\test>sqrt 23456*23456
Sqrt(550183936) = 23456

C:\test>


Dave Benham

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#6 Post by einstein1969 » 09 Aug 2014 19:40

Thanks Dave.

For completeness. In the algos there is (X+Y)/2. It's the average.

This is a average without overflow:

Code: Select all

  set /a "M=(X&Y)+(X^Y)/2"


In the sqrt algos is not necessary, I think, but for other calculations:

examples:

Code: Select all

>set /a "m=(2100000001+2000000001)/2"
-97483647


Code: Select all

>set /a "m=(2100000001&2000000001)+(2100000001^2000000001)/2"
2050000001


Francesco Poscetti
Last edited by einstein1969 on 09 Aug 2014 21:13, edited 1 time in total.

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Dos Batch Math Library

#7 Post by Aacini » 09 Aug 2014 20:00

@einstein1969:

This is very interesting! Could you explain why the "(X&Y)+(X^Y)/2" give correct results with large numbers, but "(X+Y)/2" does not, perhaps?

In assembler, when you want to multiply two numbers followed by a division (for example: X*Y/Z, common operation to change scale) you may multiply X*Y, that give a result of double the size of X and Y, and then directly divide this double-size partial result by Z. If the scaling is correct, the final result is correct even if the partial result of X*Y would produce an overflow in the base size of X and Y.

Is there any way to apply your trick above to an X*Y/Z operation in a SET /A command?

Antonio

EDIT: PS - You may find some interesting stuff on mathematical functions in my SET/A macro post.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#8 Post by einstein1969 » 09 Aug 2014 21:46

I have added a decimal part (5 digit) at the Aacini's square root, using the standard algorithm of square root. (rif . Arithmetic Extraction of Square Roots)

Code: Select all

@echo off
setlocal EnableDelayedExpansion

set /A number=%1
set /A iter1=number/2

rem The maximum number of iterations to calculate sqrt of a 32 bits integer number is 20
set "sqrt="
for /L %%i in (1,1,20) do if not defined sqrt (
   set /A "iter2=number/iter1, iter1=(iter1+iter2)/2"
   if !iter2! geq !iter1! set /A "sqrt=(iter1+iter2)/2"
)
rem echo Sqrt(%number%) = %sqrt%

rem decimal part
set count=0
set /a sqr=sqrt*sqrt

if !sqr! gtr !number! set /a sqrt-=1, sqr=sqrt*sqrt

set digit=%sqrt%.

:dec
(   set /a count+=1
   if !count! GTR 5 goto next
   set /a number=(number-sqr^)*100, div=sqrt*2
   for /l %%i in (9,-1,0) do (
      set /a sqr=!div!%%i*%%i
      if !sqr! leq !number! (
         set sqrt=!sqrt!%%i
         goto dec
      )
   )
)   
:next   
   set sqrt=%sqrt:~-5%
   if %number% neq 0 set digit=%digit%%sqrt%
   echo Square Root of %1 is %digit%


exit /b 0

EDIT:Above code has errors

examples

Code: Select all

>sqrt.bat 2
Sqrt(2) = 1
Square Root of 2 is 1.41421


EDIT: look at SQRT.BAT for enhanced version by dbenham
Last edited by einstein1969 on 11 Aug 2014 01:30, edited 6 times in total.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Dos Batch Math Library

#9 Post by dbenham » 09 Aug 2014 22:02

Impressive. But SQRT 3 original code gives 2, and decimal version gives 2.2.

Dave Benham
Last edited by dbenham on 10 Aug 2014 03:37, edited 1 time in total.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#10 Post by einstein1969 » 09 Aug 2014 22:54

dbenham wrote:Impressive. But SQRT 2 original code gives 2, and decimal version gives 2.2.

Dave Benham


:?:

What? what do you mean?

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Dos Batch Math Library

#11 Post by dbenham » 10 Aug 2014 03:38

:oops: Stoopid typos :!:

That was supposed to be a 3. I edited my post.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#12 Post by einstein1969 » 10 Aug 2014 03:58

I can't understand why... I have added a "stupid" workaround and seem work.

Code: Select all

if !sqr! gtr !number! set /a sqrt-=1, sqr=sqrt*sqrt


update the previus post.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#13 Post by einstein1969 » 10 Aug 2014 04:13

Aacini wrote:@einstein1969:

This is very interesting! Could you explain why the "(X&Y)+(X^Y)/2" give correct results with large numbers, but "(X+Y)/2" does not, perhaps?

In assembler, when you want to multiply two numbers followed by a division (for example: X*Y/Z, common operation to change scale) you may multiply X*Y, that give a result of double the size of X and Y, and then directly divide this double-size partial result by Z. If the scaling is correct, the final result is correct even if the partial result of X*Y would produce an overflow in the base size of X and Y.

Is there any way to apply your trick above to an X*Y/Z operation in a SET /A command?

Antonio

EDIT: PS - You may find some interesting stuff on mathematical functions in my SET/A macro post.


Thanks Antonio for the link. I see a lot of stuff! If possibile I integrate but your work is very advanced! :shock:

For the question. The + go in overflow. The & and ^ no. This proprierty is discoverer by Dietz.

For the MUL i think that this is not applicable. You can use KARATSUBA. I'll post an example soon.

Francesco

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Dos Batch Math Library

#14 Post by dbenham » 10 Aug 2014 19:23

Your code sometimes raises errors, and the last digit is not rounded properly, and sometimes is just plain wrong.

I refined your SQRT function to compute an accurately rounded value with as much precision as possible without resorting to numbers that exceed signed 32 bit values. I also eliminated any backward GOTO for improved performance. The function properly handles negative, 0, and 1 values.

I thought I could add 5 and divide by 10 to get a properly rounded number, but the last digit was sometimes off by 1. Via some spot checking at both extremes (small numbers and big numbers), I determined that adding 50 and dividing by 100 seems to give a reliably accurate rounded value.

I tested 10001 small inputs from 0 to 1000, as well as 1000 large inputs from 2147483647 to 2146483647, incrementing by 1000.

I have not looked at Aacini's linked code to see if there are better ways to do this.

Code: Select all

@echo off

:SQRT  InExpr  [OutVar]
::
:: Compute the square root of expression InExpr
:: and store the result in variable OutVar.
:: Write the result to stdout if OutVar not specified.
::
:: InExpr may be a valid SET /A mathematical expression.
::
:: The number is rounded accurately, and the number of
:: digits varies depending on the value of InExpr.
::
:: If the result is exact, then it will not have any
:: decimal digits.
::
setlocal EnableDelayedExpansion

:: Initialize and handle special cases
set /A "number=%~1, iter1=number/2+1" || exit /b
if !number! lss 0 >&2 echo ERROR: Imaginary number&exit /b 1
if !number! leq 1 set /a sqrt=number&goto return

:: Compute integral portion
set "sqrt="
for /L %%i in (1,1,20) do if not defined sqrt (
  set /A "iter2=number/iter1, iter1=(iter1+iter2)/2"
  if !iter2! geq !iter1! set /A "sqrt=(iter1+iter2)/2, sqr=sqrt*sqrt, count=0"
)
if !sqr! equ !number! goto return

:: Compute decimal portion
for /l %%N in (-1 1 6) do (
  set /a "number=(number-sqr)*100, div=sqrt*2"
  if !number! lss 0 goto finish
  set "next="
  for /l %%i in (9,-1,0) do if not defined next (
    set /a sqr=!div!%%i*%%i 2>nul || goto finish
    if !sqr! geq 0 if !sqr! leq !number! set "next=%%i"
  )
  set "sqrt=!sqrt!!next!"
  set "count=%%N"
)
:finish
set /a sqrt=(sqrt+50)/100
set sqrt=!sqrt:~0,-%count%!.!sqrt:~-%count%!

:return
endlocal & if "%~2" equ "" (echo %sqrt%) else set "%~2=%sqrt%"
exit /b 0


Dave Benham
Last edited by dbenham on 11 Aug 2014 07:02, edited 2 times in total.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Dos Batch Math Library

#15 Post by einstein1969 » 11 Aug 2014 00:58

I have found the error in the previus method. I think this method uses this algos.

The first term is

Code: Select all

iter1=number/2+1
and not

Code: Select all

iter1=number/2
. I have probed e seem work without my previous "stupid" patch.

@Dave
You can correct previus post?

PS: Very good work!

Post Reply