%MM% v0.2a - a full-featured WinNT math macro

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
CirothUngol
Posts: 46
Joined: 13 Sep 2017 18:37

%MM% v0.2a - a full-featured WinNT math macro

#1 Post by CirothUngol » 08 Aug 2018 06:54

Hiya guys! Been awhile since I dropped by, but I just wanted to share my latest little goodie... and hopefully find a few interested testers. ^_^

%MM% is a math macro that contains all of the functionality of my previous large, slow, and over-bloated Math.cmd utility, except for functions and decimal rounding. Instead, I was able to add unary ops (~ ' -), bitwise ops (| ^ &), logical ops (||,|&,&&), compound assignments (+= -= *= /= @= $= |= ^= &= <<= >>=) and conditional/ternary if (?:). Although it's naturally much smaller I still had to struggle to keep it under the maximum size (it currently has about 350 characters to spare). It's also noticeably faster (although I've yet to do a comparison) not only because of the lack of CALLs, but also because all math is performed on groups of eight digits instead of just one. Please give it a try and post any operational or computational issues you may find.

Code: Select all

:: %MM% is a WinNT batch macro that performs mathematical and relational
:: operations on large integers and decimals. It will accept either numerals
:: or variables as operands, supports the parsing of multiple complex in-line
:: expressions, and provides the following operators in order of precedence:
::         |( ) Grouping
:: Highest | '  LogicalNot | ~  BitwiseNot  | -  Negative
::         | $  PowerOf
::         | *  Multiply   | /  Division    | @  Modulo
::         | +  Addition   | -  Subtraction
::         | << LeftShift  | >> RightShift
::         |<=> 3-way Comparison
::         | <  LessThan   | >  GreaterThan | <= LessOrEqual | >= GreaterOrEqual
::         | ## IsEqualTo  | <> NotEqualTo
::         | &  BitwiseAnd > ^  BitwiseXor  > |  BitwiseOr
::         | && LogicalAnd > |& LogicalXor  > || LogicalOr
::         | ?: TernaryIf
:: Lowest  | = += -= *= /= @= $= |= ^= &= <<= >>= Equals/Compound Assignment
::         | ;, Expression Separators
:: 
:: Relation & Logical ops return both value and ERRORLEVEL of 1=True, 0=False.
:: 3-way Comparison operator returns 1 if n1>n2, 0 if n1=n2, or -1 if n1<n2.
:: TernaryIf(?:) = boolean ? returnIfBoolean<>0 : returnIfBoolean==0.
:: Bitwise ops are passed to SET/A, which allows signed 32-bit integers only.
:: Modulo(@) is integer only. PowerOf($) exponent is integer and positive only.
:: Variables may contain 0-9, A-z, []_ only, and first letter can't be 0-9.
:: If a variable's value is undefined or non-numerical, it's treated as 0.
:: To display result use "echo#=" in the expression, where #=num of linefeeds.
:: The result of each expression is always returned in the variable %MM_%.
:: IF ERRORLEVEL 1 IF %MM_%==0 then an error has occurred.
:: 
:: Constants: set these prior to invoking the macro, default if undefined.
:: SET $M#= # of asterisks, tildes, and equal-signs to scan for, default is 16.
::          if insufficient macro will fail without warning, ERRORLEVEL=MM_=0.
:: SET $MD= the maximum number of decimals to return, 2 if undefined.
::          macro is most efficient when $MD+2 is a multiple of 8.
:: SET $MM= expression to execute if %MM% is invoked without parameters. Line
::          input is limited to ~350 characters, use this to input up to ~8000.
:: 
::

@ECHO OFF
SET $M#=
SET $MD=
SET $MM=

:: setup macro
(SET \n=^^^
%= This defines an escaped Line Feed - DO NOT ALTER =%
)

SET MM=FOR %%# IN (1 2)DO IF %%#==2 (%=                      'v0.2a 2018/08/18 =%%\n%
%= =%FOR %%A IN (D G N T U U1 U2 X Y)DO SET $%%A=%=          '$ is used as prefix on all first-tier variables as these can't be passed into the macro, clear intitial values =%%\n%
%= =%SET $Y=4096 2048 1024 512 256 128 64 32 16 8 4 2 1%=    'binary regression for sizeOf/trailingZeros functions =%%\n%
%= =%SET $Z=0000000000000000%=                               'create constants, 9-digit controls, 4096 zeros, operator-precedence table =%%\n%
%= =%FOR %%A IN (1 2 3 4)DO SET $%%A=%%A00000000^&SET $Z=!$Z!!$Z!!$Z!!$Z!%\n%
%= =%FOR %%A IN ("( ," ": )" "T # +# -# \# /# @# $# |# {# &# <<# >>#" "||" "|&" "&&" "|" { "&" "## <>" "< > ># <#" "<#>" "<< >>" "+ -" "\ / @" $ "' } `")DO SET/A $T+=1^&SET "$O!$T!= %%~A "%\n%
%= =%IF "!$MD!"=="" SET $MD=2%=                              '$MD/maxDecimal=maximum #of decimals to return =%%\n%
%= =%IF "!$M#!"=="" SET $M#=16%=                             '$M#=maximum# of asterisks/equal-signs/tildes allowed in input, else macro will fail without warning and MM_=ERRORLEVEL=0, be sure to set this value high enough for your usage =%%\n%
%= =%IF "!$P: =!"==";" SET $P=;!$MM!%=                       'if no input (or only spaces), copy parameters from variable $MM =%%\n%
%= =%SET "$P=!$P:^={!"%=                                     'replace ^ carets(problem char#1) with { left-brace =%%\n%
%= =%SET $P=!$P:^"= !%=                                      'remove double-quotes =%%\n%
%= =%SET $P=!$P:\=/!%=                                       'assume \ backslash is / forwardslash(division), \ backslash is then used for * multiplication =%%\n%
%= =%SET $P=!$P:,=;!%=                                       'assume all expression separators are ; semi-colons, commas are then used as ? ternary-open =%%\n%
%= =%SET $P=!$P:?= , ! %=                                    'replace ? question-marks(problem char#2) with , commas =%%\n%
%= =%FOR %%A IN (+ - / @ $ # : ' { "&" "|" "<" ">" "(" ")" ";")DO SET "$P=!$P:%%~A= %%~A !"%\n%
%= =%FOR /L %%A IN (0,1,!$M#!)DO (%=                         'separate all other operators from operands =%%\n%
%=    =%FOR /F "tokens=1* delims=*" %%B IN ("!$P!")DO IF "%%C" NEQ "" SET "$P=%%B \ %%C"%= 'replace * asterisks(problem char#3) with \ backslash =%%\n%
%=    =%FOR /F "tokens=1* delims==" %%B IN ("!$P!")DO IF "%%C" NEQ "" SET "$P=%%B # %%C"%= 'replace = equal-signs(problem char#4) with # hashmark =%%\n%
%=    =%FOR /F "tokens=1* delims=~" %%B IN ("!$P!")DO IF "%%C" NEQ "" SET "$P=%%B } %%C"%= 'replace ~ tilde(problem char#5) with } right-brace =%%\n%
%=    =%SET $P=!$P:  = !)%=                                  'reduce double-spaces, replace numeric negation(-) with ` and remove unary(+), reassemble multi-char operators =%%\n%
%= =%FOR %%A IN (+ - \ / @ $ # : ' { } "&" "|" ">" "<" "(" ";" ",")DO SET "$P=!$P: %%~A - = %%~A ` !"^&SET "$P=!$P: %%~A + = %%~A !"%\n%
%= =%FOR %%A IN ("< <=<<" "> >=>>" "< >=<>" "< #=<#" "# >=#>" "> #=>#" "# #=##" "+ #=+#" "- #=-#" "\ #=\#" "/ #=/#" "@ #=@#" "$ #=$#" "| #=|#" "& #=&#" "{ #={#" "& &=&&" "| &=|&" "| |=||" "{ {={" "{ {={")DO SET "$P=!$P:%%~A!"%\n%
%= =%SET $R=MM_=%=                                           'initialize $R=return-variable-queue and feed expressions one-at-a-time into loop =%%\n%
%= =%FOR %%: IN (^"!$P: ;=^" ^"!^")DO IF %%: NEQ "" IF DEFINED $R (SET "$E=(%%~: + 0 )"%=  'begin shunting-yard(ish) parser =%%\n%
%=    =%FOR %%A IN (C Q S T V)DO SET $%%A=%=                                               'small cheat by seeding expression with '( exp + 0 )' to greatly simplify parser =%%\n%
%=    =%FOR %%A IN (^"!$E: =^" ^"!^")DO IF %%A NEQ "" IF DEFINED $R (SET/A $O=$K=0%=       'feed ops one-at-a-time into loop =%%\n%
%=       =%FOR /F %%B IN ("!$V!")DO SET $K=%%B%=                                           '$K=peek at value on top of $V=precedence-value-of-operator stack =%%\n%
%=       =%FOR /L %%B IN (1,1,17)DO IF "!$O%%B!" NEQ "!$O%%B: %%~A =!" SET $O=%%B%=        'find $O=operator-precedence of current op =%%\n%
%=       =%IF !$O!==0 (SET $Q=%%~A !$Q!%=                                                  'if no match, then it's an operand, place on top of queue =%%\n%
%=       =%)ELSE IF !$O!==3 (IF !$K! GTR 3 SET $R=^&ECHO Assignment error.^>^&2%=          'only ternary, assignments, and parenthesis are allowed below assignments =%%\n%
%=       =%)ELSE IF !$O! NEQ 1 IF !$O! LSS 16 IF !$O! LEQ !$K! (SET $T=%=                  'if not openParenthesis/openTernary(lowest) nor powerOf/unary(highest) but LEQ top of stack then process stack =%%\n%
%=          =%IF "!$S!"=="!$S:(=!" SET $R=^&ECHO Missing open parenthesis.^>^&2%=          'there should always be open-parenthesis in the stack =%%\n%
%=          =%FOR %%B IN (^"!$S: =^" ^"!^")DO IF %%B NEQ "" IF DEFINED $R (%=              'feed operators from stack one-at-a-time into loop =%%\n%
%=             =%IF !$O! LEQ !$K! (SET/A $C+=1%=                                           '$C=counter for next-queued-variable,if current operator-precedence LEQ top of stack process next operator =%%\n%
%=                =%FOR /F "tokens=1-3*" %%C IN ("!$Q!")DO (%=                             'pop operands from the top of queue =%%\n%
%=                   =%IF !$K!==17 (SET $A=%%C %%C^&SET $B=%%D %%E %%F^&IF "%%C"=="" SET $R=%=   'if unary operator =%%\n%
%=                   =%)ELSE IF %%~B==T (SET $A=%%D %%C %%E^&SET $B=%%F^&IF "%%E"=="" SET $R=%=  'if ternary operator =%%\n%
%=                   =%)ELSE SET $A=%%D %%C^&SET $B=%%E %%F^&IF "%%D"=="" SET $R=%=              'if binary operator =%%\n%
%= Start Math        =%IF DEFINED $R (SETLOCAL%=                                           'if operands are good,SETLOCAL and start math sequence =%%\n%
%= Capture Operands     =%FOR %%I IN (!$A!)DO FOR /F "tokens=* delims=-0123456789." %%J IN ("%%I")DO (SET/A $D+=1%= '$D=counter for operands =%%\n%
%=                         =%IF "%%J"=="" (SET $N!$D!=%%I)ELSE SET $N!$D!=!%%I!)%=         'determine if number or variable,capture values =%%\n%
%=                      =%FOR %%I IN (g n n1 n2 n3 u u1 u2 v)DO SET %%I=!$%%I!%=           'now it's OK to use variables other than $, set/clear new values =%%\n%
%=                      =%SET/A i=1,a=f=f1=q=s11=s12=s21=s22=t=t1=t2=t3=w=0%=              'clear start values =%%\n%
%=                      =%SET "p=%%~B"%=                                                   'capture current operator-symbol =%%\n%
%= Discover Operands    =%FOR /L %%I IN (1,1,!$D!)DO (IF "!n%%I:~0,1!"=="-" SET n%%I=!n%%I:~1!^&SET u%%I=-%=                 'separate unary from value =%%\n%
%=                         =%FOR /F "tokens=* delims=0123456789." %%J IN ("!n%%I!")DO IF "%%J" NEQ "" SET n%%I=!n%%I:%%J=!%= 'remove non-numerical portion of value =%%\n%
%=                         =%FOR /F "tokens=1-2 delims=." %%J IN ("0!n%%I!")DO FOR /F "tokens=* delims=0" %%L IN ("%%J")DO SET o%%I1=%%L^&SET o%%I2=%%K%\n%
%=                         =%IF "!o%%I1!"=="" SET o%%I1=0%=                                'capture integer+decimal portions of value, remove leading zeros, check for zero =%%\n%
%=                         =%SET o%%I=!o%%I1!!o%%I2!%=                                     'set o1/o2/o3=assembled non-padded values =%%\n%
%=                         =%FOR %%J IN (1 2)DO (SET t=!o%%I%%J!0%=                        'find length of integer/decimal portion of each value =%%\n%
%=                            =%FOR %%K IN (!$Y!)DO IF "!t:~%%K,1!" NEQ "" SET/A s%%I%%J+=%%K^&SET t=!t:~%%K!)%\n%
%=                         =%SET/A s%%I=s%%I1+s%%I2)%=                                     'set total length(s=size) of each value, then set padded values(m1=maxIntLength,m2=maxDecLength,n1/n2=padded-values) =%%\n%
%=                      =%FOR %%I IN (1 2)DO SET/A"m%%I=(s1%%I+7)/8*8"^&IF !s2%%I! GTR !s1%%I! SET/A"m%%I=(s2%%I+7)/8*8"%\n%
%=                      =%FOR %%I IN (1 2)DO FOR /F "tokens=1*" %%J IN ("!m1! !m2!")DO SET n%%I=!$Z!!o%%I1!^&SET t=!o%%I2!!$Z!^&SET n%%I=!n%%I:~-%%J!!t:~0,%%K!%\n%
%=                      =%IF "!n1!" GTR "!n2!" (SET q=1)ELSE IF "!n1!" LSS "!n2!" SET q=-1%= 'determine q=3-way compare=if abs(n1) GTR abs(n2) =%%\n%
%= Compound Assignment  =%FOR %%I IN (+ - \ / @ $ { "|" "&" "<<" ">>")DO IF %%B=="%%~I#" SET y=%%D^&SET "p=%%~I"%= 'check for compound assignments, if found, correct op and set y=return variable =%%\n%
%=                      =%SET/A d=m2,h=m1+m2,z=$MD+2%=                                     'd=decimalPlace=max padded fractional length,h=maxLength of padded int + fraction values =%%\n%
%=                      =%IF !p!==+ IF "!u2!" NEQ "!u1!" SET u2=!u1!^&SET p=-%\n%
%=                      =%IF !p!==- IF "!u2!" NEQ "!u1!" SET u2=!u1!^&SET p=+%=            'only add/subtract values with matching unaries =%%\n%
%= (`)Negative          =%IF !p!==` SET n=!n1!^&IF "!u1!"=="" SET u=-%=                    'negative - numeric negation =%%\n%
%= (#)Assignment        =%IF !p!==# SET y=%%D^&SET u=!u2!^&SET n=!n2!%=                    'assignment - set y=return variable=operand2,u=unary of answer=same as value2,n=final answer=same as value2 =%%\n%
%= (T)Ternary If        =%IF !p!==T SET u=!u1!^&SET n=!n1!^&IF !o3! EQU 0 SET u=!u2!^&SET n=!n2!%= 'ternary - set u=n=value1, if third-value=0, set u=n=value2 =%%\n%
%= (+)Addition          =%IF !p!==+ (SET u=!u1!%=                                          'addition - group values by 8 digits, add values, collect carry, assemble n=answer =%%\n%
%=                         =%FOR /L %%I IN (8,8,!h!)DO SET/A t=1!n1:~-%%I,8!+1!n2:~-%%I,8!+w,w=t/$3^&SET n=!t:~1!!n!%\n%
%=                         =%SET n=!w!!n!)%=                                               'collect carry =%%\n%
%= (-)Subtraction       =%IF !p!==- (%=                                                    'subtraction - only subtract lesser from greater, if n1>=n2 set u=u1=unary of value1, else swap n1 with n2 and set u=negative if u1=positive, then same procedure as addition without the carry =%%\n%
%=                         =%IF !q! GEQ 0 (SET u=!u1!)ELSE SET t=!n1!^&SET n1=!n2!^&SET n2=!t!^&IF "!u1!"=="" SET u=-%\n%
%=                         =%IF !q! NEQ 0 FOR /L %%I IN (8,8,!h!)DO SET/A t=3!n1:~-%%I,8!-1!n2:~-%%I,8!+w,w=t/$2-1^&SET n=!t:~1!!n!)%\n%
%=                      =%IF "!n!"=="" SET d=0%=                 'above operations require d=m2, all others require d=0, if n="" then no operations have been performed =%%\n%
%= (@)Modulo            =%IF !p!==@ SET p=/^&SET f1=1%=          'modulo - set op=division and f1/MODflag=1 =%%\n%
%= (/)Division          =%IF !p!==/ (SET/A s11+=s22,s12-=s22%=   'division+modulo - set s11/size1_int+=size2_dec,s12/size1_dec-=size2_dec =%%\n%
%=                         =%IF !s12! LSS 0 SET s12=0%=          'if size1_dec<0 set to 0 =%%\n%
%=                         =%SET/A s1=s11+s12,j=s11+z%=          's1/length_of_value1=size1_int+size1_dec,j/#of divisions=size1+$maxDecimal =%%\n%
%=                         =%SET w=!$Z!%=                        'w=remainder=$zeros =%%\n%
%=                         =%SET o1=0!o1!!$Z!%=                  'o1/non-padded value1=lead0~o1~$zeros =%%\n%
%=                         =%IF !f1!==1 SET j=0%=                'above will move decimalPlace and set j=#of divisions, if f1/MODflag=1 set j=0 because integer only =%%\n%
%=                         =%IF !j! LSS !s1! SET/A j=s1%=        'if j LSS s1/size1=length of value1 then j=size1 ie. no fraction/integer only =%%\n%
%=                         =%IF "!u1!" NEQ "!u2!" SET u=-%=      'if unaries don't match answer is negative =%%\n%
%=                         =%FOR %%I IN (!h!)DO SET n2=!$Z!!o2!^&SET n2=!n2:~-%%I!%=       'set n2/divisor=corrected/padded value2 =%%\n%
%=                         =%IF !q!==0 (SET n=1)ELSE IF !n2! EQU 0 (SET g=X^&ECHO Divide by zero error.^>^&2%\n%
%=                         =%)ELSE IF !o1! NEQ 0 FOR /L %%I IN (1,1,!j!)DO (SET t=0%=      'reset t/answer digit=0 and d/decimalPlace+=1 if GTR lengthOfDividend =%%\n%
%=                            =%IF %%I GTR !s11! SET/A d+=1%=                              'add next digit from dividend to remainder and crop to maxSize for comparison with divisor =%%\n%
%=                            =%FOR %%J IN (!h!)DO SET w=!w!!o1:~%%I,1!^&SET w=!w:~-%%J!%\n%
%=                            =%IF "!w!" GEQ "!n2!" FOR /L %%J IN (1,1,9)DO IF "!w!" GEQ "!n2!" (SET/A t+=1,t2=0%\n%
%=                               =%SET t3=!w!%=                                            'if w/remainder GTR n2/divisor then w-=n2 until it's not, t=answer digit=#of subtractions until w LSS n2 =%%\n%
%=                               =%SET w=%\n%
%=                               =%FOR /L %%K IN (8,8,!h!)DO SET/A t1=3!t3:~-%%K,8!-1!n2:~-%%K,8!+t2,t2=t1/$2-1^&SET w=!t1:~1!!w!)%\n%
%=                            =%SET n=!n!!t!)%=                                            'add t=answer digit to n/quotient/final answer =%%\n%
%=                         =%IF !f1!==1 SET u=!u1!^&SET n=!w!)%=                           'if f1/MODflag=1 set u=unary of dividend,n/answer=w/integer remainder =%%\n%
%= ($)Power Of          =%IF !p!==$ (SET p=\^&SET u2=%=                                    'powerOf - set op=multiply, check for integer/0/1 exponent =%%\n%
%=                         =%IF !s22! NEQ 0 SET o2=0^&SET g=X^&ECHO Non-integer exponent.^>^&2%\n%
%=                         =%IF !o1! LEQ 1 SET o2=0%=                                      'i=o2=0 will effectively cancel multiplication routine =%%\n%
%=                         =%SET/A i=o2,a=i/3+1,t=i%%2,n=o2=s2=1%=                         'set i=exponent,a=#of loop iterations,t=even/odd exponent,answer=non-padded value2=length of value2=1 =%%\n%
%=                         =%IF !t!==0 SET u1=)%=                                          'if exponent is even clear unary of value1, as answer will be positive =%%\n%
%= (\)Multiplication    =%IF !p!==\ (IF "!u1!" NEQ "!u2!" SET u=-%=                        'multiplication+powerOf - if unaries don't match answer is negative =%%\n%
%=                         =%FOR /L %%H IN (0,1,!a!)DO IF !i! NEQ 0 (SET/A t2=i%%2,i/=2%=  'if exponent NEQ 0 capture odd/even and halve exponent =%%\n%
%=                            =%IF !t2!==1 (SET n=%=                                       'n/answer=NULL =%%\n%
%=                               =%SET n2=0000000!o2!%=                                    'n2=0000000non-padded value2 =%%\n%
%=                               =%SET/A"d=s12+s22,h=(s2+7)/8*8"%=                         'set decimalPlace=size1_dec+size2_dec,maxSize=largest group of 8 =%%\n%
%=                               =%FOR /L %%I IN (1,1,!h!)DO SET _%%I=0%=                  'clear carry columns =%%\n%
%=                               =%FOR /L %%I IN (1,1,!s1!)DO (SET/A w=t1=0,c=%%I%=        'multiply each digit of o1 by n2 grouped by 8 =%%\n%
%=                                  =%FOR /L %%J IN (8,8,!h!)DO SET/A t=!o1:~-%%I,1!,t=t*1!n2:~-%%J,8!+w-t*$1,w=t/$1,_!c!+=t%%$1,c+=8%\n%
%=                                  =%SET _!c!=!w!)%=                                      'collect carry into the next column =%%\n%
%=                               =%FOR /L %%I IN (1,1,!c!)DO SET/A _%%I+=t1,t1=_%%I/10^&SET n=!_%%I:~-1!!n!%\n%
%=                               =%SET n=!t1!!n!)%=                                        'add all columns together, attach remainder to product =%%\n%
%=                            =%IF !i! NEQ 0 (SET n1=%=                                    'if exponent NEQ 0 square n1 by setting n2=n1 and repeating as above saving product in n1 =%%\n%
%=                               =%SET n2=0000000!o1!%\n%
%=                               =%SET/A"s12*=2,s22=d,h=(s1+7)/8*8"%\n%
%=                               =%FOR /L %%I IN (1,1,!h!)DO SET _%%I=0%\n%
%=                               =%FOR /L %%I IN (1,1,!s1!)DO (SET/A w=t1=0,c=%%I%\n%
%=                                  =%FOR /L %%J IN (8,8,!h!)DO SET/A t=!o1:~-%%I,1!,t=t*1!n2:~-%%J,8!+w-t*$1,w=t/$1,_!c!+=t%%$1,c+=8%\n%
%=                                  =%SET _!c!=!w!)%\n%
%=                               =%FOR /L %%I IN (1,1,!c!)DO SET/A _%%I+=t1,t1=_%%I/10^&SET n1=!_%%I:~-1!!n1!%\n%
%=                               =%SET n1=!t1!!n1!^&SET n2=!n!%=                           'n1=new square,n2=current answer, remove leading zeros =%%\n%
%=                               =%FOR %%I IN (1 2)DO FOR /F "tokens=* delims=0" %%J IN ("!n%%I!")DO (SET o%%I=%%J%\n%
%=                                  =%SET/A s%%I=0,t=s%%I2-z%=                             'clear size,set t=size#_dec - $maxDecimal =%%\n%
%=                                  =%IF !t! GTR 0 FOR %%K IN (!t!)DO SET/A s%%I2=z^&SET o%%I=!o%%I:~0,-%%K!%\n%
%=                                  =%SET t=!o%%I!0%=                                      'crop values to $maxDecimal,determine new length =%%\n%
%=                                  =%FOR %%K IN (!$Y!)DO IF "!t:~%%K,1!" NEQ "" SET/A s%%I+=%%K^&SET t=!t:~%%K!))))%\n%
%= Relational Ops       =%IF DEFINED u1 (IF DEFINED u2 (SET/A q*=-1)ELSE SET q=-1)ELSE IF DEFINED u2 SET q=1%= 'q=abs(n1)>abs(n2), correct using unaries u1+u2 =%%\n%
%=                      =%IF !p!==## IF !q!==0 SET f=1%=                       'relational operators =%%\n%
%=                      =%IF %%B==">" IF !q! GTR 0 SET f=1%=                   'f=flag=only set to 1(true) by logic and relational operators =%%\n%
%=                      =%IF %%B=="<" IF !q! LSS 0 SET f=1%\n%
%=                      =%IF %%B==">#" IF !q! GEQ 0 SET f=1%\n%
%=                      =%IF %%B=="<#" IF !q! LEQ 0 SET f=1%\n%
%=                      =%IF %%B=="<>" IF !q! NEQ 0 SET f=1%\n%
%=                      =%IF %%B=="<#>" SET/A n=q%\n%
%= Logical Ops          =%IF !p!==' IF !o1! EQU 0 SET f=1%=                    'logical operators - not =%%\n%
%=                      =%IF %%B=="||" IF !o1!!o2! NEQ 0 SET f=1%=             'or =%%\n%
%=                      =%IF %%B=="&&" IF !o1! NEQ 0 IF !o2! NEQ 0 SET f=1%=   'and =%%\n%
%=                      =%IF %%B=="|&" IF !o1! NEQ 0 (IF !o2! EQU 0 SET f=1)ELSE IF !o2! NEQ 0 SET f=1%= 'xor =%%\n%
%=                      =%IF !f!==1 SET n=1^&SET g= 00%=                       'if flag1=1 set n=answer=true=1 and ErrLvl=1 =%%\n%
%= Bitwise Ops          =%SET o1=!u1!!o1!^&SET o2=!u2!!o2!%=                   'bitwise operators =%%\n%
%=                      =%IF !p!==} SET/A n=~o1%=                              'passed directly to SET/A using variables as operands, so it never errors? =%%\n%
%=                      =%IF !p!=={ SET/A"n=o1^o2"%=                           'supports 32-bit signed integers only =%%\n%
%=                      =%IF %%B=="|" SET/A"n=o1|o2"%=                         'values above  2147483647 are treated as  2147483647 =%%\n%
%=                      =%IF %%B=="&" SET/A"n=o1&o2"%=                         'values below -2147483648 are treated as -2147483648 =%%\n%
%=                      =%IF %%B=="<<" SET/A"n=o1<<o2"%\n%
%=                      =%IF %%B==">>" SET/A"n=o1>>o2"%\n%
%=                      =%SET/A z-=2%\n%
%= Finalize Value       =%IF !d! NEQ 0 (FOR %%I IN (!d!)DO FOR %%J IN (!z!)DO SET n=!n:~0,-%%I!.!n:~-%%I,%%J!%\n%
%=                         =%FOR %%I IN (!$Y!)DO IF "!n:~-%%I!"=="!$Z:~-%%I!" SET n=!n:~0,-%%I!%= 'remove trailing zeros =%%\n%
%=                         =%IF "!n:~-1!"=="." SET n=!n:~0,-1!)%=                          'if d GTR 0 answer is decimal, place decimal in n cropping to $maxDecimal, if last character is decimal remove it =%%\n%
%=                      =%FOR /F "tokens=* delims=0" %%I IN ("!n!")DO SET n=%%I%=          'remove leading zeros =%%\n%
%=                      =%IF "!n!"=="" SET n=0%=                                           'if n=NULL set to 0 =%%\n%
%=                      =%IF !n! NEQ 0 SET n=!u!!n!%=                                      'if n/answer=non-zero attach unary =%%\n%
%=                      =%IF "!g!" NEQ "X" IF /I "!y:~0,4!"=="ECHO" (^<NUL SET/P=!n!%=     'if no error check for ECHO command =%%\n%
%=                         =%FOR /L %%J IN (1,1,!y:~4!)DO ECHO.%\n%
%=                         =%SET y=)%=                                         'return values from 2nd-tier SETLOCAL =%%\n%
%= Return Value         =%FOR /F "tokens=1-3 delims=;" %%I IN (^""!y!";"!n!";"!g!"^")DO (ENDLOCAL%\n%
%=                         =%IF %%I NEQ "" SET %%~I=%%~J^&SET $R=!$R!" "%%~I=%%~J%=        'if return variable present SET value and add to return-varible-queue =%%\n%
%=                         =%SET $_!$C!=%%~J%=                                             'SET value of current queued variable =%%\n%
%=                         =%SET $X=%%~K)%=                                                'SET exitcode =%%\n%
%= Resume Parser        =%IF "!$X!"=="X" SET $R=%=                             'if exitcode=X then ERROR, clear $R as halt-flag for FOR loops =%%\n%
%=                      =%SET $Q=$_!$C! !$B!%=                                 'push newly computed value on top of queue =%%\n%
%=                   =%)ELSE ECHO Missing operand.^>^&2)%=                     'otherwise operand not found =%%\n%
%=                =%FOR /F "tokens=2*" %%C IN ("!$V!")DO SET $K=%%C^&SET $V=%%C %%D%= 'pop and peek at the value stack =%%\n%
%=             =%)ELSE SET "$T=!$T! %%~B")%=                                   'rebuild remainder of symbol stack =%%\n%
%=          =%SET $S=!$T!)%=                                                   'reset stack to remainder of stack =%%\n%
%=       =%IF !$O!==2 (FOR /F "tokens=1*" %%C IN ("!$V!")DO SET $V=%%D%=       'if current op is close parenthesis/ternary =%%\n%
%=          =%FOR /F "tokens=1*" %%C IN ("!$S!")DO (SET "$S=%%D"%=             'pop both stacks, check for ternary(push new operator onto stacks) or type mismatch(error if found) =%%\n%
%=             =%IF "%%C"=="," (IF %%A==":" (SET $S=T !$S!^&SET $V=3 !$V!)ELSE SET $R=^&ECHO Missing ternary close.^>^&2)ELSE IF %%A==":" SET $R=^&ECHO Missing ternary open.^>^&2)%\n%
%=       =%)ELSE IF !$O! GTR 0 SET "$S=%%~A !$S!"^&SET $V=!$O! !$V!)%=         'else if current op is not an operand, push to top of stacks =%%\n%
%=    =%IF DEFINED $R (SET $S= !$S!%=                                          'if no errors check for empty stack and single item in operand queue =%%\n%
%=       =%IF "!$S: =!" NEQ "" (SET $R=^&ECHO Missing close parenthesis.^>^&2%='if symbol stack is not empty =%%\n%
%=       =%)ELSE FOR /F "tokens=1*" %%A IN ("!$Q!")DO (%\n%
%=          =%IF "%%~B" NEQ "" SET $R=^&ECHO Missing operator.^>^&2%=          'if more than one item in queue =%%\n%
%=          =%SET MM_=!%%~A!)))%=                                              'set MM_=final value for expression =%%\n%
%= =%IF "!$R!"=="" SET $R=MM_=^&SET MM_=0^&SET $X= 00%=                        'if error set ErrLvl=1 and MM_=0 =%%\n%
%= =%FOR /F "tokens=1-3 delims=;" %%A IN (^""!$R!";"!MM_!";"COLOR!$X!"^")DO (ENDLOCAL%= 'exit macro and return values =%%\n%
%=    =%FOR %%: IN (%%A)DO SET %%:%=                                           'process return-variable-queue =%%\n%
%=    =%SET MM_=%%~B%=                                                         'set MM_=final value of last expression =%%\n%
%=    =%%%~C)%=                                                                'set ERRORLEVEL=0/1 =%%\n%
)ELSE SETLOCAL EnableDelayedExpansion^&SET $P= ;%= [returnVar[+,-,*,/,@,$,|,^,&,<<,>>]= [ ...]] [boolean ?] ['~-]operand1 [+,-,*,/,@,$,<,>,<#,>#,##,<>,<#>,|,^,&,<<,>>,||,|&,&& operand2] [;exp2 [,exp3 [ ...up to ~300 characters]]] =%

:: Parser Variables:
::  $#= 9-digit control numbers used by +-*/ to stack 8-digits per iteration using SET/A
::  $A= operands that have been pulled from the queue for the current operation
::  $B= remainder of the operand queue
::  $C= variable counter for newly-generated operands, as in SET "$_!$C!=new value"
::  $D= variable counter for the currently-captured operands
::  $E= current expression being parsed, padded with "($E+0)" to simplify the parser
::  $K= precedence value of operator on top of stack
:: $N#= value of each currently-captured operand
::  $O= precedence value of current operator
:: $O#= operator symbols numbered by precedence, used to identify the current operator
::  $P= input parameters, replaced with contents of $MM if empty (or only spaces)
::  $Q= current operand queue
::  $R= return variable queue, list of double-quote separated "variable=value" pairs, also used as a flag to halt macro
::  $S= current operator stack, symbols
::  $T= temp variable
::  $V= current operator stack, precedence values
::  $X= exit code, empty=0, 00=1, X=error
::  $Y= binary regression from 4096 to 1 used to find string-length and remove trailing zeros
::  $Z= 4096 zeros used to pad values to equal lengths, needed for compare, add, and subtract
:: 
:: Math Variables:
::   a= # of iterations to perform in multiplication/powerOf, starting at 0
::   c= column counter used during multiplication, steps by 8
::   d= decimal position for the final return value
::   f= boolean flags, f=logical/relational op is True, f1=Modulo in Division
::   g= exit code for 2nd-tier SETLOCAL, undefined=0, space00=1, X=error
::   h= maximum size of zero-padded values, always a multiple of 8, used to group SET/A operations together (8x fewer iterations)
::   i= powerOf exponent, initialize i=1 to allow multiplication
::   j= # of divisions to perform, based on position of decimal and $MD=maxDecimal to return
::  m#= maximum size of zero-padded values, always a multiple of 8, 1=integer portion, 2=decimal portion
::   n= final value for the current operation
::  n#= zero-padded values for the current operands, decimal removed
::  o#= non-padded values for the current operands, decimal removed
:: o##= non-padded values, 2nd # is 1=integer portion, 2=decimal portion
::   p= current operand symbol, used to select routine
::   q= 3-way compare of absolute values of n1<=>n2, for subtraction and relational
::  s#= size (length) of the current operands
:: s##= size (length), 2nd # is 1=integer portion, 2=decimal portion
::   t= temp variables, t,t1,t2,t3
::   u= operand unaries (only -), u=current result, u#=current operands
::   w= also a temp variable, changed to a single letter to save ~70 characters
::   y= name of user return variable
::   z= $MD+2 adjusted maxDecimal. Used to increase accuracy of truncated decimal operations, better approximates rounding.
::

:mm_help
TITLE Press ENTER to skip...
SET base=1
SET "EKO=<NUL SET/P="
FOR %%# IN ($MD last root guess cnt xx)DO SET %%#=
FOR /F "usebackq tokens=* delims=:" %%# IN ("%~f0")DO IF "%%#"=="" (SET base=)ELSE IF DEFINED base ECHO. %%#
ECHO example: NthRoot convergence (((root-1)*guess)+(base/guess$(root-1)))/root
SET /P "base=enter a base to find the root of (positive decimal): "
IF DEFINED base SET /P "root=enter the NthRoot to find (positive integer): "
IF DEFINED root SET /P "$MD=enter the accuracy (# of decimal places): "
IF DEFINED $MD SET /P "guess=enter your best guess (positive decimal): "
IF NOT DEFINED guess SET $MD=& ECHO. & ECHO okay, maybe try some expressions? max decimal is 8. & GOTO :mm_h2
ECHO. & ECHO Finding %root%th root of %base% to %$MD% decimals starting at %guess%
ECHO guess=(((%root%-1)*%guess%)+(%base%/%guess%$(%root%-1)))/%root%
TITLE Finding %root%th root of %base% to %$MD% decimals starting at %guess%

:mm_h1
SET /A cnt+=1
SET last=%guess%
%EKO%guess#%cnt%=
%MM% "echo1=guess=(((root-1)*guess)+(base/guess$(root-1)))/root,guess<>last"
IF ERRORLEVEL 1 IF %MM_% NEQ 0 ( GOTO :mm_h1 )ELSE ECHO Error : Abort & GOTO :mm_h2
ECHO. & ECHO checking:
%EKO%guess$%root%=
%MM% cnt-=1,echo1=guess$root
ECHO That's %$MD% decimals of accuracy in %cnt% trys.
ECHO.
ECHO now, try some expressions. max decimal is %$MD%.

:mm_h2
TITLE Press ENTER to exit...
SET xx=
SET/P xx=
IF NOT DEFINED xx (IF "!!"=="" ENDLOCAL) & TITLE & EXIT /B 0
%MM% %xx%
IF ERRORLEVEL 1 ( IF %MM_%==0 ( ECHO Error : MM_=%MM_%
	)ELSE ECHO True : MM_=%MM_%
)ELSE ECHO False : MM_=%MM_%
GOTO :mm_h2
If you run it as-is, it will display the help, run a sample Nth-Root convergence formula, and allow you to enter sample expressions for testing. I've only tested it on my Win7 x64 machine, I don't have access to any others.

Edit 2018/08/08: I had totally forgotten the no-space method of inserting comments in macros (saw it while poking around the forum) so I've updated the post with a heavily commented/verbosely-cluttered version. No version# change as I only added comments and didn't alter a single character of code. After repeatedly handing it many difficult and increasingly unnecessarily herculean tasks, I currently have it finding the 999,999th root of 999,999 to 32 decimal accuracy starting at 1.005... sure it's been running for nearly 3 days now, but I've no doubt that it'll finally make it! ^_^

Edit 2018/08/09: Cleaned up v0.1 again and re-posted. Changed %# to %= for comments, replaced tabs with %= =%, and removed the DelayedExpansion duality. No code has changed, it just looks different. Also, I chose not to employ DB's fix for the endless-loop effect because I like to know when something's broken, it's better if I actually fix it. ^_^
Incidentally, that 999,999th root is on guess#517 and counting... any day now.

Edit 2018/08/17: I've just posted v0.2 with (hopefully) some improvements (v0.1 can conveniently be found archived on the post below this one). All ops from SET/A are now supported with bitwise ops being passed directly to SET/A which treats values above 2147483647 as 2147483647 and below -2147483648 as -2147483648. Due to the size of the macro, expressions are limited to ~350 characters, so I've also added the ability to save longer expressions to a variable (%$MM%) which can be passed into the macro by invoking it without any parameters. I haven't tested it too extensively, so I'm expecting to correct at least a glitch or two (if not more). Please post if you try it and encounter any issues.

Edir 2018/08/18: Reading through the comments I had forgotten about the endless-loop issue in the demo. I've corrected that by extending the maxDecimal by +2 during process and reducing back down before final truncation (+1 would probably be sufficient, but perhaps +2 will better approximate rounding). I also took the opportunity to minimize the size as much as I could by reducing all possible variables to single characters, added a few comments and posted v0.2a
Last edited by CirothUngol on 19 Aug 2018 11:27, edited 9 times in total.

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

Re: %MM% - a full-featured WinNT math macro

#2 Post by dbenham » 08 Aug 2018 20:45

Impressive :!: :D

I've reformatted your MM definition to have consistent indents (2 spaces per level) that properly reflect the code structure, and there is now more than 100 bytes to spare when defining with delayed expansion.

The trick to saving space is to expand an impossible variable name with the correct length to achieve the desired indent in the source code, without actually using up memory in the result.

I don't think it makes a difference, but I also simplified the definition of \n

Lastly, your nth root example code does not always converge. For example, base=10, root=10, accuracy=3, guess=2 ends up in an endless loop alternating between 1.258 and 1.259.

I modified the code a bit to also track the previous last guess, in addition to the last guess. The code now breaks out of the loop if the current guess matches either the last or prevLast value. I'm not sure it actually gives the best answer, but at least it breaks out of the loop.

Code: Select all

:: %MM% is a WinNT batch macro that performs mathematical and comparison
:: operations on large integers and decimals. It will accept either numerals
:: or variables as operands, supports the parsing of multiple complex in-line
:: expressions, and provides the following operators in order of precedence:
::         |( ) Grouping (often requires double-quotes)
:: Highest | $ PowerOf (exponent is positive and integer only)
::         | * Multiply | / Divide | @ Modulo (@ is integer only)
::         | + Addition | - Subtraction
::         | <=> Raw Comparison (returns 1 if n1>n2, 0 if n1=n2, -1 if n1<n2)
::         | < LessThan | > GreaterThan | <= LessOrEqual | >= GreaterOrEqual
::         | ## EqualTo | <> NotEqualTo (<> always requires double-quotes)
::         | = += -= *= /= @= $= Equals (variable assignment)
:: Lowest  | ?: TernaryIf (boolean ? returnIfBoolean<>0 : returnIfBoolean==0)
::         | ;, Expression Separators
:: Comparisons will return both value and ERRORLEVEL of 1=True, 0=False.
:: If a variable's value is undefined or non-numerical, it's treated as 0.
:: To display result use "echo#=" in the expression, where #=num of linefeeds.
:: The result of each expression is always returned in the variable %MM_%.
:: IF ERRORLEVEL 1 IF %MM_%==0 then an error has occurred.
:: Constants: set these prior to invoking the macro, default if undefined.
:: SET $M#= number of asterisks and equals to scan for, default is 16.
:: SET $MD= the maximum number of decimals to return, default is 8.
::
::
@ECHO OFF
setlocal enableDelayedExpansion
SET $M#=
SET $MD=

:: setup macro constants
(set \n=^^^
%= This defines an escaped Line Feed - DO NOT ALTER =%
)

SET "b=!" & SET "q=!"
IF "!!"=="" SET "b=^^^^^^^!" & SET "q=^^^!"

SET MM=FOR %%# IN (1 2) DO IF %%#==2 (REM v0.1 2018/08/06%\n%
%=  =%SETLOCAL EnableExtensions EnableDelayedExpansion%\n%
%=  =%FOR %%A IN (D E0 N0 P U0 U1 U2 V0 X) DO SET $%%A=%\n%
%=  =%SET $Y=4096 2048 1024 512 256 128 64 32 16 8 4 2 1%\n%
%=  =%SET $Z=0000000000000000%\n%
%=  =%FOR %%A IN (1 2 3 4) DO SET $%%A=%%A00000000^&SET $Z=%b%$Z%b%%b%$Z%b%%b%$Z%b%%b%$Z%b%%\n%
%=  =%FOR %%A IN ("( ," ": )" T "# +# -# \# /# @# $#" "## <>" "< > ># <# <#>" "+ -" "\ / @" $) DO SET/A $P+=1^&SET "$O%q%$P%q%= %%~A "%\n%
%=  =%IF "%q%$MD%q%"=="" SET $MD=8%\n%
%=  =%IF "%q%$M#%q%"=="" SET $M#=16%\n%
%=  =%SET $T=%b%$T:^"= %b%%\n%
%=  =%SET $T=%b%$T:\=/%b%%\n%
%=  =%SET $T=%b%$T:,=;%b%%\n%
%=  =%SET $T=%b%$T:?= , %b% %\n%
%=  =%FOR %%A IN (+ - / @ $ # : ">" "<" "(" ")" ";") DO SET "$T=%q%$T:%%~A= %%~A %q%"%\n%
%=  =%FOR /L %%A IN (0,1,%b%$M#%b%) DO (%\n%
%=    =%FOR /F "tokens=1* delims=*" %%B IN ("%q%$T%q%") DO IF "%%C" NEQ "" SET "$T=%%B \ %%C"%\n%
%=    =%FOR /F "tokens=1* delims==" %%B IN ("%q%$T%q%") DO IF "%%C" NEQ "" SET "$T=%%B # %%C"%\n%
%=    =%SET "$T=%q%$T:  = %q%"%\n%
%=  =%)%\n%
%=  =%FOR %%A IN (+ - \ / @ $ # : ">" "<" "(" ";" ",") DO SET "$T=%q%$T: %%~A - = %%~A -%q%"^&SET "$T=%q%$T: %%~A + = %%~A %q%"%\n%
%=  =%FOR %%A IN ("< # >=<#>" "> #=>#" "< #=<#" "< >=<>" "# #=##" "+ #=+#" "- #=-#" "\ #=\#" "/ #=/#" "@ #=@#" "$ #=$#") DO SET "$T=%q%$T:%%~A%q%"%\n%
%=  =%SET $P=%b%$T: ;=^" ^"%b%%\n%
%=  =%SET $R=$T=%\n%
%=  =%FOR %%: IN ("%q%$P%q%") DO IF %%: NEQ "" IF DEFINED $R (%\n%
%=    =%SET "$P=(%%~: + 0 )"%\n%
%=    =%SET $P=%b%$P: =^" ^"%b%%\n%
%=    =%FOR %%A IN (C Q S T V) DO SET $%%A=%\n%
%=    =%FOR %%A IN ("%q%$P%q%") DO IF %%A NEQ "" IF DEFINED $R (%\n%
%=      =%SET/A $O=$K=0%\n%
%=      =%FOR /F %%B IN ("%q%$V%q%") DO SET "$K=%%B"%\n%
%=      =%FOR /L %%B IN (1,1,9) DO IF "%q%$O%%B%q%" NEQ "%q%$O%%B: %%~A =%q%" SET $O=%%B%\n%
%=      =%IF %b%$O%b%==0 (%\n%
%=        =%SET $Q=%%~A %b%$Q%b%%\n%
%=      =%) ELSE IF %b%$O%b%==4 (%\n%
%=        =%IF %b%$K%b% GTR 4 SET $R=^&ECHO Assignment error.^>^&2%\n%
%=      =%) ELSE IF %b%$O%b% NEQ 1 IF %b%$O%b% NEQ 9 IF %b%$O%b% LEQ %b%$K%b% (%\n%
%=        =%SET $T=%\n%
%=        =%IF "%q%$S%q%"=="%q%$S:(=%q%" SET $R=^&ECHO Missing open parenthesis.^>^&2%\n%
%=        =%SET $S=%b%$S: =^" ^"%b%%\n%
%=        =%FOR %%B IN ("%q%$S%q%") DO IF %%B NEQ "" IF DEFINED $R (%\n%
%=          =%IF %b%$O%b% LEQ %b%$K%b% (%\n%
%=            =%SET/A $C+=1%\n%
%=            =%FOR /F "tokens=1-3*" %%C IN ("%q%$Q%q%") DO (%\n%
%=              =%SET $A=%%D %%C^&SET $B=%%E %%F^&IF %%~B==T SET $A=%%D %%C %%E^&SET $B=%%F%\n%
%=              =%SETLOCAL%\n%
%=              =%FOR %%I IN (%b%$A%b%) DO FOR /F "tokens=* delims=-0123456789." %%J IN ("%%I") DO (%\n%
%=                =%SET/A $D+=1%\n%
%=                =%IF "%%J"=="" (SET $N%b%$D%b%=%%I) ELSE SET $N%b%$D%b%=%b%%%I%b%%\n%
%=              =%)%\n%
%=              =%FOR %%I IN (e0 n0 n1 n2 n3 u0 u1 u2 v0) DO SET %%I=%b%$%%I%b%%\n%
%=              =%SET/A xp=1,f0=gt=s11=s12=s21=s22=t0=t1=t2=t3=t4=x1=0^&SET "op=%%~B"%\n%
%=              =%FOR /L %%I IN (1,1,%b%$D%b%) DO (%\n%
%=                =%IF "%q%n%%I:~0,1%q%"=="-" SET n%%I=%b%n%%I:~1%b%^&SET u%%I=-%\n%
%=                =%FOR /F "tokens=* delims=0123456789." %%J IN ("%q%n%%I%q%") DO IF "%%J" NEQ "" SET n%%I=%b%n%%I:%%J=%b%%\n%
%=                =%FOR /F "tokens=1-2 delims=." %%J IN ("0%q%n%%I%q%") DO FOR /F "tokens=* delims=0" %%L IN ("%%J") DO SET o%%I1=%%L^&SET o%%I2=%%K%\n%
%=                =%IF "%q%o%%I1%q%"=="" SET o%%I1=0%\n%
%=                =%SET o%%I=%b%o%%I1%b%%b%o%%I2%b%%\n%
%=                =%FOR %%J IN (1 2) DO (%\n%
%=                  =%SET t1=%b%o%%I%%J%b%0%\n%
%=                  =%FOR %%K IN (%b%$Y%b%) DO IF "%q%t1:~%%K,1%q%" NEQ "" SET/A s%%I%%J+=%%K^&SET t1=%b%t1:~%%K%b%%\n%
%=                =%)%\n%
%=                =%SET/A s%%I=s%%I1+s%%I2%\n%
%=              =%)%\n%
%=              =%FOR %%I IN (1 2) DO SET/A "m%%I=(s1%%I+7)/8*8"^&IF %b%s2%%I%b% GTR %b%s1%%I%b% SET/A "m%%I=(s2%%I+7)/8*8"%\n%
%=              =%FOR %%I IN (1 2) DO FOR /F "tokens=1*" %%J IN ("%q%m1%q% %q%m2%q%") DO SET n%%I=%b%$Z%b%%b%o%%I1%b%^&SET t1=%b%o%%I2%b%%b%$Z%b%^&SET n%%I=%b%n%%I:~-%%J%b%%b%t1:~0,%%K%b%%\n%
%=              =%IF "%q%n1%q%" NEQ "%q%n2%q%" IF "%q%n1%q%" GTR "%q%n2%q%" (SET gt=1) ELSE SET gt=-1%\n%
%=              =%SET/A dc=m2,mx=m1+m2%\n%
%=              =%FOR %%I IN (+ - \ / @ $) DO IF %b%op%b%==%%I# SET v0=%%D^&SET op=%%I%\n%
%=              =%IF %b%op%b%==+ IF "%q%u2%q%" NEQ "%q%u1%q%" SET u2=%b%u1%b%^&SET op=-%\n%
%=              =%IF %b%op%b%==- IF "%q%u2%q%" NEQ "%q%u1%q%" SET u2=%b%u1%b%^&SET op=+%\n%
%=              =%IF %b%op%b%==@ SET op=/^&SET f0=1%\n%
%=              =%IF %b%op%b%==$ (%\n%
%=                =%SET op=\^&SET u2=%\n%
%=                =%IF %b%s22%b% NEQ 0 SET o2=0^&SET e0=X^&ECHO Non-integer exponent.^>^&2%\n%
%=                =%IF %b%o1%b% LEQ 1 SET o2=0%\n%
%=                =%SET/A xp=o2,x1=xp/3+1,t1=xp%%2,dc=0,n0=o2=s2=1%\n%
%=                =%IF %b%t1%b%==0 SET u1=%\n%
%=              =%)%\n%
%=              =%IF "%%D"=="" (%\n%
%=                =%SET e0=X^&ECHO Missing operand.^>^&2%\n%
%=              =%) ELSE IF %%~B==# (%\n%
%=                =%SET v0=%%D^&SET u0=%b%u2%b%^&SET n0=%b%n2%b%%\n%
%=              =%) ELSE IF %%~B==T (%\n%
%=                =%SET u0=%b%u1%b%^&SET n0=%b%n1%b%%\n%
%=                =%IF %b%o3%b% EQU 0 SET u0=%b%u2%b%^&SET n0=%b%n2%b%%\n%
%=                =%IF "%%E"=="" SET e0=X^&ECHO Missing operand.^>^&2%\n%
%=              =%) ELSE IF %b%op%b%==+ (%\n%
%=                =%SET u0=%b%u1%b%%\n%
%=                =%FOR /L %%I IN (8,8,%b%mx%b%) DO SET/A t1=1%b%n1:~-%%I,8%b%+1%b%n2:~-%%I,8%b%+t0,t0=t1/$3^&SET n0=%b%t1:~1%b%%b%n0%b%%\n%
%=                =%SET n0=%b%t0%b%%b%n0%b%%\n%
%=              =%) ELSE IF %b%op%b%==- (%\n%
%=                =%IF %b%gt%b% GEQ 0 (SET u0=%b%u1%b%) ELSE SET t1=%b%n1%b%^&SET n1=%b%n2%b%^&SET n2=%b%t1%b%^&IF "%q%u1%q%"=="" SET u0=-%\n%
%=                =%IF %b%gt%b% NEQ 0 FOR /L %%I IN (8,8,%b%mx%b%) DO SET/A t1=3%b%n1:~-%%I,8%b%-1%b%n2:~-%%I,8%b%+t0,t0=t1/$2-1^&SET n0=%b%t1:~1%b%%b%n0%b%%\n%
%=              =%) ELSE IF %b%op%b%==/ (%\n%
%=                =%SET/A dc=0,s11+=s22,s12-=s22^&IF %b%s12%b% LSS 0 SET s12=0%\n%
%=                =%SET/A s1=s11+s12,dv=s11+$MD^&SET t0=%b%$Z%b%^&SET o1=0%b%o1%b%%b%$Z%b%%\n%
%=                =%IF %b%f0%b%==1 SET dv=0%\n%
%=                =%IF %b%dv%b% LSS %b%s1%b% SET/A dv=s1%\n%
%=                =%IF "%q%u1%q%" NEQ "%q%u2%q%" SET u0=-%\n%
%=                =%FOR %%I IN (%b%mx%b%) DO SET n2=%b%$Z%b%%b%o2%b%^&SET n2=%b%n2:~-%%I%b%%\n%
%=                =%IF %b%gt%b%==0 (SET n0=1) ELSE IF %b%n2%b% EQU 0 (%\n%
%=                  =%SET e0=X^&ECHO Divide by zero error.^>^&2%\n%
%=                =%) ELSE IF %b%o1%b% NEQ 0 FOR /L %%I IN (1,1,%b%dv%b%) DO (%\n%
%=                  =%SET t1=0^&IF %%I GTR %b%s11%b% SET/A dc+=1%\n%
%=                  =%FOR %%J IN (%b%mx%b%) DO SET t0=%b%t0%b%%b%o1:~%%I,1%b%^&SET t0=%b%t0:~-%%J%b%%\n%
%=                  =%IF "%q%t0%q%" GEQ "%q%n2%q%" FOR /L %%J IN (1,1,9) DO IF "%q%t0%q%" GEQ "%q%n2%q%" (%\n%
%=                    =%SET/A t1+=1,t3=0%\n%
%=                    =%SET t4=%b%t0%b%^&SET t0=%\n%
%=                    =%FOR /L %%K IN (8,8,%b%mx%b%) DO SET/A t2=3%b%t4:~-%%K,8%b%-1%b%n2:~-%%K,8%b%+t3,t3=t2/$2-1^&SET t0=%b%t2:~1%b%%b%t0%b%%\n%
%=                  =%)%\n%
%=                  =%SET n0=%b%n0%b%%b%t1%b%%\n%
%=                =%)%\n%
%=                =%IF %b%f0%b%==1 SET u0=%b%u1%b%^&SET n0=%b%t0%b%%\n%
%=              =%) ELSE IF %b%op%b%==\ (%\n%
%=                =%IF "%q%u1%q%" NEQ "%q%u2%q%" SET u0=-%\n%
%=                =%FOR /L %%H IN (0,1,%b%x1%b%) DO IF %b%xp%b% NEQ 0 (%\n%
%=                  =%SET/A t3=xp%%2,xp/=2%\n%
%=                  =%IF %b%t3%b%==1 (%\n%
%=                    =%SET/A "dc=s12+s22,mx=(s2+7)/8*8"^&SET n2=0000000%b%o2%b%^&SET n0=%\n%
%=                    =%FOR /L %%I IN (1,1,%b%mx%b%) DO SET _%%I=0%\n%
%=                    =%FOR /L %%I IN (1,1,%b%s1%b%) DO (%\n%
%=                      =%SET/A t0=t2=0,c0=%%I%\n%
%=                      =%FOR /L %%J IN (8,8,%b%mx%b%) DO SET/A t1=%b%o1:~-%%I,1%b%,t1=t1*1%b%n2:~-%%J,8%b%+t0-t1*$1,t0=t1/$1,_%b%c0%b%+=t1%%$1,c0+=8%\n%
%=                      =%SET _%b%c0%b%=%b%t0%b%%\n%
%=                    =%)%\n%
%=                    =%FOR /L %%I IN (1,1,%b%c0%b%) DO SET/A _%%I+=t2,t2=_%%I/10^&SET n0=%b%_%%I:~-1%b%%b%n0%b%%\n%
%=                    =%SET n0=%b%t2%b%%b%n0%b%%\n%
%=                  =%)%\n%
%=                  =%IF %b%xp%b% NEQ 0 (%\n%
%=                    =%SET/A "s12*=2,s22=dc,mx=(s1+7)/8*8"^&SET n2=0000000%b%o1%b%^&SET n1=%\n%
%=                    =%FOR /L %%I IN (1,1,%b%mx%b%) DO SET _%%I=0%\n%
%=                    =%FOR /L %%I IN (1,1,%b%s1%b%) DO (%\n%
%=                      =%SET/A t0=t2=0,c0=%%I%\n%
%=                      =%FOR /L %%J IN (8,8,%b%mx%b%) DO SET/A t1=%b%o1:~-%%I,1%b%,t1=t1*1%b%n2:~-%%J,8%b%+t0-t1*$1,t0=t1/$1,_%b%c0%b%+=t1%%$1,c0+=8%\n%
%=                      =%SET _%b%c0%b%=%b%t0%b%%\n%
%=                    =%)%\n%
%=                    =%FOR /L %%I IN (1,1,%b%c0%b%) DO SET/A _%%I+=t2,t2=_%%I/10^&SET n1=%b%_%%I:~-1%b%%b%n1%b%%\n%
%=                    =%SET n1=%b%t2%b%%b%n1%b%^&SET n2=%b%n0%b%%\n%
%=                    =%FOR %%I IN (1 2) DO FOR /F "tokens=* delims=0" %%J IN ("%q%n%%I%q%") DO (%\n%
%=                      =%SET/A s%%I=0,t1=s%%I2-$MD^&SET o%%I=%%J%\n%
%=                      =%IF %b%t1%b% GTR 0 FOR %%K IN (%b%t1%b%) DO SET/A s%%I2=$MD^&SET o%%I=%b%o%%I:~0,-%%K%b%%\n%
%=                      =%SET t1=%b%o%%I%b%0^&FOR %%K IN (%b%$Y%b%) DO IF "%q%t1:~%%K,1%q%" NEQ "" SET/A s%%I+=%%K^&SET t1=%b%t1:~%%K%b%%\n%
%=                    =%)%\n%
%=                  =%)%\n%
%=                =%)%\n%
%=              =%) ELSE (%\n%
%=                =%IF "%q%u1%q%"=="-" (IF "%q%u2%q%"=="-" (SET/A gt*=-1) ELSE SET gt=-1) ELSE IF "%q%u2%q%"=="-" SET gt=1%\n%
%=                =%IF %%B==">" (%\n%
%=                  =%IF %b%gt%b% GTR 0 SET n0=1%\n%
%=                =%) ELSE IF %%B=="<" (%\n%
%=                  =%IF %b%gt%b% LSS 0 SET n0=1%\n%
%=                =%) ELSE IF %%B=="##" (%\n%
%=                  =%IF %b%gt%b% EQU 0 SET n0=1%\n%
%=                =%) ELSE IF %%B==">#" (%\n%
%=                  =%IF %b%gt%b% GEQ 0 SET n0=1%\n%
%=                =%) ELSE IF %%B=="<#" (%\n%
%=                  =%IF %b%gt%b% LEQ 0 SET n0=1%\n%
%=                =%) ELSE IF %%B=="<>" (%\n%
%=                  =%IF %b%gt%b% NEQ 0 SET n0=1%\n%
%=                =%) ELSE SET/A n0=gt%\n%
%=                =%SET dc=0^&IF %b%n0%b%==1 SET e0= 00%\n%
%=              =%)%\n%
%=              =%IF %b%dc%b% NEQ 0 (%\n%
%=                =%FOR %%I IN (%b%dc%b%) DO FOR %%J IN (%b%$MD%b%) DO SET t1=%b%n0:~-%%I%b%^&SET n0=%b%n0:~0,-%%I%b%.%b%t1:~0,%%J%b%%\n%
%=                =%FOR %%I IN (%b%$Y%b%) DO IF "%q%n0:~-%%I%q%"=="%q%$Z:~-%%I%q%" SET n0=%b%n0:~0,-%%I%b%%\n%
%=                =%IF "%q%n0:~-1%q%"=="." SET n0=%b%n0:~0,-1%b%%\n%
%=              =%)%\n%
%=              =%FOR /F "tokens=* delims=0" %%I IN ("%q%n0%q%") DO SET n0=%%I%\n%
%=              =%IF "%q%n0%q%"=="" SET n0=0%\n%
%=              =%IF %b%n0%b% NEQ 0 SET n0=%b%u0%b%%b%n0%b%%\n%
%=              =%IF "%q%e0%q%" NEQ "X" IF /I "%q%v0:~0,4%q%"=="ECHO" SET t1=%b%v0:~4%b%^&^<NUL SET/P=%b%n0%b%^&(FOR /L %%J IN (1,1,%b%t1%b%) DO ECHO.)^&SET v0=%\n%
%=              =%FOR /F "tokens=1-3 delims=;" %%I IN (^""%q%v0%q%";"%q%n0%q%";"%q%e0%q%"^") DO (%\n%
%=                =%ENDLOCAL%\n%
%=                =%IF %%I NEQ "" SET %%~I=%%~J^&SET $R=%b%$R%b%" "%%~I=%%~J%\n%
%=                =%SET $_%b%$C%b%=%%~J%\n%
%=                =%SET $X=%%~K%\n%
%=              =%)%\n%
%=              =%IF "%q%$X%q%"=="X" SET $R=%\n%
%=              =%SET $Q=$_%b%$C%b% %b%$B%b%%\n%
%=            =%)%\n%
%=            =%FOR /F "tokens=2*" %%C IN ("%q%$V%q%") DO SET $K=%%C^&SET $V=%%C %%D%\n%
%=          =%) ELSE SET "$T=%q%$T%q% %%~B"%\n%
%=        =%)%\n%
%=        =%SET $S=%b%$T%b%%\n%
%=      =%)%\n%
%=      =%IF %b%$O%b%==2 (%\n%
%=        =%FOR /F "tokens=1*" %%C IN ("%q%$V%q%") DO SET $V=%%D%\n%
%=        =%FOR /F "tokens=1*" %%C IN ("%q%$S%q%") DO (%\n%
%=          =%SET "$S=%%D"%\n%
%=          =%IF "%%C"=="," (IF %%A==":" (SET "$S=T %q%$S%q%"^&SET $V=3 %b%$V%b%) ELSE SET $R=^&ECHO Missing ternary close.^>^&2) ELSE IF %%A==":" SET $R=^&ECHO Missing ternary open.^>^&2%\n%
%=        =%)%\n%
%=      =%) ELSE IF %b%$O%b% GTR 0 SET "$S=%%~A %q%$S%q%"^&SET $V=%b%$O%b% %b%$V%b%%\n%
%=    =%)%\n%
%=    =%IF DEFINED $R (%\n%
%=      =%SET $S= %b%$S%b%%\n%
%=      =%IF "%q%$S: =%q%" NEQ "" (%\n%
%=        =%SET $R=^&ECHO Missing close parenthesis.^>^&2%\n%
%=      =%) ELSE FOR /F "tokens=1*" %%A IN ("%q%$Q%q%") DO (%\n%
%=        =%IF "%%~B" NEQ "" SET $R=^&ECHO Missing operator.^>^&2%\n%
%=        =%SET MM_=%b%%%~A%b%%\n%
%=      =%)%\n%
%=    =%)%\n%
%=  =%)%\n%
%=  =%IF "%q%$R%q%"=="" SET $R=$T=^&SET MM_=0^&SET $X= 00%\n%
%=  =%FOR /F "tokens=1-3 delims=;" %%A IN (^""%q%$R%q%";"%q%MM_%q%";"COLOR%q%$X%q%"^") DO (%\n%
%=    =%ENDLOCAL%\n%
%=    =%FOR %%: IN (%%A) DO SET %%:%\n%
%=    =%SET MM_=%%~B%\n%
%=    =%%%~C%\n%
%=  =%)%\n%
) ELSE SET $T= ;


REM 56789.123456789.123456789.123456789.123456789.123456789.123456789.123456789%\n%
REM 56789.123456789.123456789.123456789.123456789.123456789.123456789.123456789
::EXIT /B 0

setlocal enableDelayedExpansion

:mm_help
TITLE Press ENTER to skip...
SET base=1
SET "EKO=<NUL SET/P="
FOR %%# IN ($MD last root guess cnt xx) DO SET %%#=
IF NOT EXIST "%~dpn0_EZ.cmd" SET MM>"%~dpn0_EZ.cmd"
FOR /F "usebackq tokens=* delims=:" %%# IN ("%~f0") DO IF "%%#"=="" (SET base=) ELSE IF DEFINED base ECHO. %%#
ECHO example: NthRoot convergence (((root-1)*guess)+(base/guess$(root-1)))/root
SET /P "base=enter a base to find the root of (positive decimal): "
IF DEFINED base SET /P "root=enter the NthRoot to find (positive integer): "
IF DEFINED root SET /P "$MD=enter the accuracy (# of decimal places): "
IF DEFINED $MD SET /P "guess=enter your best guess (positive decimal): "
IF NOT DEFINED guess SET $MD=& ECHO. & ECHO okay, maybe try some expressions? max decimal is 8. & GOTO :mm_h2
ECHO. & ECHO Finding %root%th root of %base% to %$MD% decimals starting at %guess%
ECHO guess=(((%root%-1)*%guess%)+(%base%/%guess%$(%root%-1)))/%root%
TITLE Finding %root%th root of %base% to %$MD% decimals starting at %guess%
SET last=%guess%

:mm_h1
SET /A cnt+=1
SET prevLast=%last%
SET last=%guess%
%EKO%guess#%cnt%=
%MM% "echo1=guess=(((root-1)*guess)+(base/guess$(root-1)))/root,((guess<>last) * (guess<>prevLast))<>0"
IF ERRORLEVEL 1 IF %MM_% NEQ 0 ( GOTO :mm_h1 ) ELSE ECHO Error : Abort & GOTO :mm_h2
ECHO. & ECHO checking:
%EKO%guess$%root%=
%MM% cnt-=1,echo1=guess$root
ECHO That's %$MD% decimals of accuracy in %cnt% trys.
ECHO.
ECHO now, try some expressions. max decimal is %$MD%.

:mm_h2
TITLE Press ENTER to exit...
SET xx=
SET/P xx=
IF NOT DEFINED xx (IF "!!"=="" ENDLOCAL) & TITLE & EXIT /B 0
%MM% %xx%
IF ERRORLEVEL 1 ( IF %MM_%==0 ( ECHO Error : MM_=%MM_%
  ) ELSE ECHO True : MM_=%MM_%
) ELSE ECHO False : MM_=%MM_%
GOTO :mm_h2

CirothUngol
Posts: 46
Joined: 13 Sep 2017 18:37

Re: %MM% - a full-featured WinNT math macro

#3 Post by CirothUngol » 09 Aug 2018 00:55

Wow, thanks Dave! I should have looked before posting my update, but I'll post another later after I incorporate your alterations.
I remember hitting that loop when writing Math.cmd, but it disappeared when I added decimal-rounding to the script. I just read about the improved %\n% today, thanks for that and for the other fixes, love the tab replacements! 100+ characters is significant, that's where I was when I decided to try and add the final function (ternaryIf ?:). Wound up needing close to 200, but I squoze it in... surely 100+ is enough to add decimal rounding?
Any other idea for a bit of missing functionality?

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

Re: %MM% - a full-featured WinNT math macro

#4 Post by dbenham » 09 Aug 2018 02:19

One thing you need to do is document all of the internal variables that are at risk of conflicting with user defined variables. Ideally, all internal variables would be prefixed with a unique character, and then you could instruct users to simply avoid using variables with that prefix. You have already started using a $ prefix for many variables. But unfortunately adding the prefix to all variables will increase the length, possibly significantly.

I'm not convinced you need to support definition of MM while delayed expansion is enabled. Defining MM while delayed expansion is disabled saves significant space. If you decide to remove support for defining while delayed expansion is enabled, then you can revert to embedding ! within the source code, and eliminate %b% and %q%. That will make the source code easier to read.

I think you can safely remove EnableExtensions to gain some more space. Disabled extensions is extremely rare, and you can simply include a statement in your docs stating that extensions must be enabled when %MM% is "called".

I think you can save 90 additional bytes in your current code by changing all ) DO and ) ELSE to )DO and )ELSE.

I would remove the version REM from the definition, and indicate the version as a comment above (separate from) the definition.

=========

Some features I would like to see:

1) Logical operators AND OR XOR NOT.

2) Some mechanism to ECHO strings as well as numbers. This would help with debugging complex formulas.

3) Ability to specify internal precision separate from display precision. This may be related to your decimal rounding. I ask for this because I notice that computing the nth power of a decimal value is currently not very accurate. Doing internal computations to higher precision followed by rounding to the display precision should give more accurate results.


Dave Benham

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

Re: %MM% - a full-featured WinNT math macro

#5 Post by dbenham » 09 Aug 2018 06:38

I just now noticed your update (edit) to the first post :roll:

I like the comments - they help a lot.

But it is possible (even though extremely unlikely) that %# comment #% could be a valid variable.

It is impossible for batch to define a variable like %= comment =%, and there are no pseudo variables that contain more than one = in the name. So I think it is much better to use = instead of # when creating variable "comments".


Dave Benham

CirothUngol
Posts: 46
Joined: 13 Sep 2017 18:37

Re: %MM% - a full-featured WinNT math macro

#6 Post by CirothUngol » 09 Aug 2018 15:45

Again, thanks for the interest DB and for the suggestions. Let's see...
dbenham wrote:
09 Aug 2018 02:19
One thing you need to do is document all of the internal variables that are at risk of conflicting with user defined variables.
The macro uses nested SETLOCALs. The first captures the user's environment and only variables beginning with dollar sign are used, because the dollar sign is a special symbol and cannot be passed into the macro. The second-tier starts right before the math Loop and I make sure to capture the user's operands before I use other named variables. I've tested this by using internal variable names as user variables on purpose and believe the macro is completely safe in this manner. Hopefully there's no possibility of accidentally corrupting the user's environment, assuming none of their variables begin with $. if I'm incorrect and a problem is noticed, it should be fairly simple and straightforward to preface all of the internal variables with a dollar sign.
I'm not convinced you need to support definition of MM while delayed expansion is enabled.
Me neither, as I personally don't feel I have any use for it. I only did it because apparently it is useful to some people, it was a bit of an additional challenge, and I was trying to make the macro as openly useful as possible. That will be the first thing to go when I add anything new to the macro, because I'm so limited on space I have to eliminate that in order to add anything new. Doing so will definitely open up around 500 characters
I think you can safely remove EnableExtensions to gain some more space. Disabled extensions is extremely rare.
I figured, but again I was attempting to be as inclusive as possible. I'm not even sure when extensions would not be enabled, but it will still probably be the very last thing I remove because at 17 characters it's very small and may increase compatibility by whatever tiny percentage.
I think you can save 90 additional bytes in your current code by changing all ) DO and ) ELSE to )DO and )ELSE.
I would remove the version REM from the definition, and indicate the version as a comment above (separate from) the definition.
I've done a whole bunch of this type of thing to conserve space, but was unaware that I could also collapse the closing parentheses. This will certainly happen when I do the next round of size shrinking. It's beginning to look like I will probably have close to a thousand more characters for expansion when it's all done.
Some features I would like to see:
1) Logical operators AND OR XOR NOT.
My goal of course was to completely duplicate the behavior of SET/A and all of its capabilities, but more including exponents, decimals, and really huge numbers. I initially looked into including the bitwise operators, but was unsure how exactly that would work. Would it be useful for really big numbers? I'm assuming it would be limited to integers only? I'm quite familiar with how they operate, but I'm unsure exactly how I would Implement that into the current group of eight structure that the macro currently uses for all of its other operations. I'll certainly look into it, this would probably be the addition that I would most like to implement as it will bring the macro that much closer to being an all-inclusive and more useful version of SET/A.
2) Some mechanism to ECHO strings as well as numbers. This would help with debugging complex formulas.
This sounds interesting, but could you elaborate? I can't exactly picture how that would work. What would it do? What would it look like? How would it know when to print and when not to print? Would it always print? if you could provide examples and a little guidance this also sounds like something I would like to implement.
3) Ability to specify internal precision separate from display precision. This may be related to your decimal rounding.
when I first implemented the power of exponent function I quickly realized that the repeated squaring and multiplying of decimal numbers would rapidly increase the decimal size to hundreds or thousands of digits, bogging down the process immensely and causing it to run very slowly. I added a line to trim the new products of multiplication to the size set by $MD and it increased the speed of the routine from 10 to 100 times faster, depending on how big the exponent was. My assumption is that I could simply increase the max decimal during the power of routine and it should probably fix that issue. If not, I can always try to squeeze in a routine that would round numbers up from the last decimal place.
I think it is much better to use = instead of # when creating variable "comments".
the only reason I chose %# is because %= is seen everywhere in a batch file. I was trying to make the comments easier to recognize, but you have an excellent point. I will change all of the comments when I add the new tabs and I will repost version 0.1 before I change any of the actual code.

thanks again for all your interest and input, it would seem that I have a little bit more fun left in this macro before I can hang it up and call it done. ^_^

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

Re: %MM% - a full-featured WinNT math macro

#7 Post by dbenham » 09 Aug 2018 16:32

CirothUngol wrote:
09 Aug 2018 15:45
dbenham wrote:
09 Aug 2018 02:19
One thing you need to do is document all of the internal variables that are at risk of conflicting with user defined variables.
The macro uses nested SETLOCALs. The first captures the user's environment and only variables beginning with dollar sign are used, because the dollar sign is a special symbol and cannot be passed into the macro. The second-tier starts right before the math Loop and I make sure to capture the user's operands before I use other named variables. I've tested this by using internal variable names as user variables on purpose and believe the macro is completely safe in this manner. Hopefully there's no possibility of accidentally corrupting the user's environment, assuming none of their variables begin with $. if I'm incorrect and a problem is noticed, it should be fairly simple and straightforward to preface all of the internal variables with a dollar sign.
Yes, I noticed that might be the case once I saw your comments. Looks good. Your help should state that user variables should not begin with $

CirothUngol wrote:
09 Aug 2018 15:45
Some features I would like to see:
1) Logical operators AND OR XOR NOT.
My goal of course was to completely duplicate the behavior of SET/A and all of its capabilities, but more including exponents, decimals, and really huge numbers. I initially looked into including the bitwise operators, but was unsure how exactly that would work. Would it be useful for really big numbers? I'm assuming it would be limited to integers only? I'm quite familiar with how they operate, but I'm unsure exactly how I would Implement that into the current group of eight structure that the macro currently uses for all of its other operations. I'll certainly look into it, this would probably be the addition that I would most like to implement as it will bring the macro that much closer to being an all-inclusive and more useful version of SET/A.
Actually I was thinking of true logical operators that always produce 0 or 1, not bit operators. The logical operators would be really useful for the ternaryIf operator.

You could implement bit operators, and they would be limited to integer values like @ etc. But I don't really see the point unless you see a need to deal with bigger than 32 bit integers. But if so, how big? If you actually do bit operators, I would like to see them separate from the logical ones, and then you are faced with choosing different symbols for logical vs. bit. I suppose you could spell out the logical operators, something like #AND#, #OR#, etc., and use symbols for the bit operators.

CirothUngol wrote:
09 Aug 2018 15:45
2) Some mechanism to ECHO strings as well as numbers. This would help with debugging complex formulas.
This sounds interesting, but could you elaborate? I can't exactly picture how that would work. What would it do? What would it look like? How would it know when to print and when not to print? Would it always print? if you could provide examples and a little guidance this also sounds like something I would like to implement.
Yes, figuring out a useful syntax and design is awkward. I spent a little bit of time thinking how it might work, but realized it is not straight forward. I may think about it some more, but don't hold your breath...


Dave Benham

CirothUngol
Posts: 46
Joined: 13 Sep 2017 18:37

Re: %MM% - a full-featured WinNT math macro

#8 Post by CirothUngol » 09 Aug 2018 21:01

dbenham wrote:
09 Aug 2018 16:32
Actually I was thinking of true logical operators that always produce 0 or 1, not bit operators. The logical operators would be really useful for the ternaryIf operator.
You could implement bit operators... But I don't really see the point...
Me neither, really. My enthusiasm for doing it would be for discovering exactly how to do it, and producing a more accurate replacement for SET/A. To be honest, I've never even used the bitwise ops (although I know how they work).
I can easily see the usefulness of logic operators (binary logic gates and I actually go way back ^_^) and they should be straight-forward to implement. Now it's just a question of symbols, SET uses:
& = And
| = Or
^ = Xor
! = Not
And(&) and Or(|) should be fine (they're just like <> needing only quotes), but surely I don't want to try and use ^ or !. That could make writing expressions difficult, right? I chose Mod(@ instead of %) and Power($) specifically because they're innocuous and don't interact with batch in any way. Also, Not(!) is a unary operator, and although I shoe-horned in an ability to parse negative numerals as operands, it won't work on variables because I never actually implemented unary operators, I do however see how it could work (as I've already added a ternary operator, just expand that idea).
OK, so I should add both unary and boolean logic operators. Adding to the above list, SET also uses: ! - ~ as unary ops. I understand the 1st two (Not and Negative) but what does the Tilde(~) represent. What's it called. What's it for?
I would keep & | - ~ the same, which leaves Not(!) and Xor(^), and I believe that literally leaves ' [ ] _ ` { } ... pretty slim pickin's, methinks. I don't like any of those, to be honest... although I guess underscore ain't too bad. If I had to choose, I'd also use ' it's just so tiny! Maybe re-utilize the semi-colon (comma is enough of a separator)? Perhaps I'm overthinking, would ^ be OK to use?
Yes, figuring out a useful syntax and design is awkward. I spent a little bit of time thinking how it might work, but realized it is not straight forward. I may think about it some more, but don't hold your breath...
No worries. I've got unary, boolean logic, and perhaps decimal rounding to consider. I've re-posted v0.1 for the last time, still calling it that because no code has been altered despite how different it may appear.

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

Re: %MM% - a full-featured WinNT math macro

#9 Post by dbenham » 09 Aug 2018 21:39

I think bit operators are not worth it, which simplifies things.

I think ^ is good for XOR, it normally works fine as long as it is quoted. Things that can cause issues:
  • Quoted ^ is doubled if CALL is used. Since we are using macros, CALL is not normally an issue. But it could be an issue if %MM% is used within a CALLed subroutine. You could accept both ^ and ^^ as XOR, and then there should be no problem either way.
  • Delayed expansion can cause problems if and only if the formula contains both ! and ^. Typically delayed expansion should not be needed, so normally not an issue. But if you accept both ^ and ^^, then if the user blindly uses quoted ^^, then it will always work unless ! appears with delayed expansion. If you accept ^, ^^, and ^^^^, then quoted ^^ will always work.

If you don't implement bit operators, then ~ works well for NOT


Dave Benham

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

Re: %MM% - a full-featured WinNT math macro

#10 Post by dbenham » 10 Aug 2018 05:31

One other idea I had for XOR - use || for XOR and | for OR

CirothUngol
Posts: 46
Joined: 13 Sep 2017 18:37

Re: %MM% - a full-featured WinNT math macro

#11 Post by CirothUngol » 10 Aug 2018 07:10

As I thought about it, ^ seemed like it would only be a real issue if the user decided to expand variables with ! instead of just passing them in (for some reason), but I like the idea of just doubling || as it's already being parsed and is more easily managed.
It took some searching to find the meaning of the ~ unary operator... and it was a post of yours from 2011. Thanks again. ^_^
So, if one's compilent is not a useful unary operator for decimal numbers, then using it for Not(~) is an excellent idea. Great, that's settled. I've already implemented the logical ops into the macro:

) ELSE IF %%B=="|" (SET dc=0^&IF !o1!!o2! NEQ 0 SET n0=1%\n%%= 'logical or =%
) ELSE IF %%B=="&" (SET dc=0^&IF !o1! NEQ 0 IF !o2! NEQ 0 SET n0=1%\n%%= 'logical and =%
) ELSE IF %%B=="||" (SET dc=0^&IF !o1! NEQ 0 (IF !o2! EQU 0 SET n0=1) ELSE IF !o2! NEQ 0 SET n0=1%\n%%= 'logical exclusive or/xor =%
...and adding the symbols into the parser was all it takes.

Finding precedence for Xor(||) wasn't easy, but I've decided to separate them as follows:
And(&) > Xor(||) > Or(|)
That way, boolean algebra/logic should actually work. unary ops shouldn't be difficult to implement, they should act just like other right-associative ops (like Power($)) only popping a single value from the queue. The op itself is trivial.
Thanks again for the suggestions.

IcarusLives
Posts: 102
Joined: 17 Jan 2016 23:55

Re: %MM% - a full-featured WinNT math macro

#12 Post by IcarusLives » 10 Aug 2018 11:02

Hello :) I find this thread really intriguing. I have read everything here, and I'm struggling to understand basic usage (as if applied to a batch script) and in what cases would this be more practical than set /a?

This is really amazing work here! I'm just trying to learn how to use it. I would greatly appreciate your assistance!

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

Re: %MM% - a full-featured WinNT math macro

#13 Post by dbenham » 10 Aug 2018 13:12

@IcarusLives

The macro would be useful any time you are trying to do arithmetic with numbers that do not fit within the constraints of a 32 bit signed integer. This includes decimal (fractional) numbers, as well as numbers that are larger than 2,147,483,647 or less than -2,147,483,648.

One obvious example is if you are trying to sum up file size, since the sum total may easily exceed 2 GB. Without a custom batch utility like %MM% to deal with large numbers, you would have to do hybrid scripting that uses PowerShell, VBScript, or JScript. %MM% enables you to keep the code "pure" batch.

Code: Select all

echo off
set /a docSize=0
for /r "c:\" %%F in (*.doc) do %MM% "docSize+=%%~zF"
echo total .doc size on drive C: is %docSize%
Note - I think there may be a way to use ROBOCOPY to get the total file size directly, but this simple example should still demonstrate the usefulness of %MM%.


@CirothUngol
CirothUngol wrote: I've already implemented the logical ops into the macro:

) ELSE IF %%B=="|" (SET dc=0^&IF !o1!!o2! NEQ 0 SET n0=1%\n%%= 'logical or =%
) ELSE IF %%B=="&" (SET dc=0^&IF !o1! NEQ 0 IF !o2! NEQ 0 SET n0=1%\n%%= 'logical and =%
) ELSE IF %%B=="||" (SET dc=0^&IF !o1! NEQ 0 (IF !o2! EQU 0 SET n0=1) ELSE IF !o2! NEQ 0 SET n0=1%\n%%= 'logical exclusive or/xor =%
...and adding the symbols into the parser was all it takes.
:shock: That doesn't look right :?
It looks like you initialize the return value to 0 with SET dc=0, and then conditionally set the return value to 1, but using n0 instead of dc. What am I missing :?:

Assuming it is OK to modify the value of o1 and/or o2 during the computation, then I think XOR can be simplified to:

Code: Select all

)ELSE IF %%b=="||" (IF !o1! NEQ 0 SET o1=1^&IF !o2! NEQ 0 SET o2=1^&SET /A n0=o1^^o2)%\n%%= 'logical exclusive or /xor =%
If I am right, then that saves a few bytes. It also demonstrates usage of the SET /A bit XOR operator.


Dave Benham

IcarusLives
Posts: 102
Joined: 17 Jan 2016 23:55

Re: %MM% - a full-featured WinNT math macro

#14 Post by IcarusLives » 10 Aug 2018 15:50

@dbenham

Thank you for the explanation and example code! This is very useful indeed. So I can use this in the same way as I would set /a?

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

Re: %MM% - a full-featured WinNT math macro

#15 Post by dbenham » 10 Aug 2018 16:04

Very similar to SET /A, but not exactly the same. For example, @ is used for MOD instead of % (or %% in batch). Also %MM% has some features that SET /A doesn't, such as comparison operations that can be used with the ternary if construct: someCondition ? doThisIfTrue : elseDoThisIfFalse. And SET /A has some things like bit operators that %MM% does not.

Read the help at the top of the file and experiment.

Post Reply