Version 0.6 is posted. Changes: restore original number formats, to help using Monster in other scripts; removed work around AHK bugs, fixed in AHK version 1.0.46.09.
Monster: evaluate math expressions in strings
Started by
Laszlo
, Feb 28 2007 12:06 AM
87 replies to this topic
#16

Posted 04 March 2007  10:56 PM
Version 0.7 is posted. Changes:
 scientific constants (1.2e+5) are now allowed
 several minor simplifications, cleanups
 negative Fibonacci numbers: Fib(5)
 "·" characters araound internal function names are replaced by single quotes ', so only standard ASCII/ANSI characters (31 < code < 127)are used.
 scientific constants (1.2e+5) are now allowed
 several minor simplifications, cleanups
 negative Fibonacci numbers: Fib(5)
 "·" characters araound internal function names are replaced by single quotes ', so only standard ASCII/ANSI characters (31 < code < 127)are used.
#17

Posted 09 March 2007  06:13 PM
Version 0.8 is posted. Changes:
 Width field of binary output is supported. $b: as few bits as necessary (1st bit = sign), $b{W}: outputs the Least Significant W bits.
 Results are now rounded at binary or hex output (so 4/2 is displayed as 0x2, not 2.000000)
 Width field of binary output is supported. $b: as few bits as necessary (1st bit = sign), $b{W}: outputs the Least Significant W bits.
 Results are now rounded at binary or hex output (so 4/2 is displayed as 0x2, not 2.000000)
#18

Posted 11 March 2007  03:43 PM
*Mouth gaping open* :shock:
Laszlo you are amazing! I don't think i could even dream of being as good as you are! This is a very useful script! If this was built into ahk (or people could #include), it would solve alot of complaints from people.
Laszlo you are amazing! I don't think i could even dream of being as good as you are! This is a very useful script! If this was built into ahk (or people could #include), it would solve alot of complaints from people.
#19

Posted 12 March 2007  04:48 PM
"Power can be given overnight, but responsibility must be taught. Long years go into its making."
Thanks, Veovis. It was originally made for my own use, but I am glad that somebody else finds it useful.
Here is Version 0.9. The main difference is the removed brackets [..] around user operators: min, max, gcd and choose. From the role and position of a name Monster determines that it is a variable, function or operator. This is not easy, because the constructs like ...min pi, min1, min abs(x) are very similar. To make the operators stand out, you can adopt your own conventions. E.g. variables and functions are written in lower case, operators are in capitals: x1 MIN sqrt(pi). (The list of the supported operators and functions is extensible, without changing the first pass, which identifies directives, constants, functions, variables and operators.)
There are a few minor internal changes: sgn, MIN and MAX are computed inline now, operator names are also enclosed internally in '...', and the search for the last operator/function name is simplified. I don't want to replace version 0.8 in the first post yet, because the identification of the new operator names might not always work. It would be safer after somebody gave it a few hours test.
Here is Version 0.9. The main difference is the removed brackets [..] around user operators: min, max, gcd and choose. From the role and position of a name Monster determines that it is a variable, function or operator. This is not easy, because the constructs like ...min pi, min1, min abs(x) are very similar. To make the operators stand out, you can adopt your own conventions. E.g. variables and functions are written in lower case, operators are in capitals: x1 MIN sqrt(pi). (The list of the supported operators and functions is extensible, without changing the first pass, which identifies directives, constants, functions, variables and operators.)
There are a few minor internal changes: sgn, MIN and MAX are computed inline now, operator names are also enclosed internally in '...', and the search for the last operator/function name is simplified. I don't want to replace version 0.8 in the first post yet, because the identification of the new operator names might not always work. It would be safer after somebody gave it a few hours test.
; MONSTER Version 0.9 (needs AHK 1.0.46.09+) ; EVALUATE ARITHMETIC EXPRESSIONS containing HEX, Binary ('1001), scientific numbers (1.2e+5) ; (..); variables, constants: e, pi, inch, foot, mile, ounce, pint, gallon, oz, lb; ; (? :); logicals ; &&; relationals =,<>; <,>,<=,>=; user operators GCD,MIN,MAX,Choose; ; ; ^; &; <<, >>; +, ; *, /, \ (or % = mod); ** (or @ = power); !,~; ; Functions AbsCeilExpFloorLogLnRoundSqrtSinCosTanASinACosATanSGNFibfac ; Output = No $: .6 digit decimal; $x,$h: Hex; $b{W}: Wbit binary; ${k}: .kdigit decimal ; "Assignments;" can preceed an expression: a:=1; b:=2; a+b #SingleInstance Force #NoEnv SetBatchLines 1 Process Priority,,High xe := 2.718281828459045, xpi := 3.141592653589793 ; referenced as "e", "pi" xinch := 2.54, xfoot := 30.48, xmile := 1.609344 ; [cm], [cm], [Km] xounce := 0.02841, xpint := 0.5682, xgallon := 4.54609 ; liters xoz := 28.35, xlb := 453.59237 ; gramms /* test cases MsgBox % Eval("1e3 .5e+2 + 100.e1") ; 960.000000 MsgBox % Eval("x := y:1; x := 5*x; y := x+1") ; 6 if y empty, x := 1... MsgBox % Eval("x:=!0; x<0 ? 2*x : sqrt(x)") ; 2 MsgBox % Eval("tan(atan(atan(tan(1))))exp(sqrt(1))") ; 1.718282 MsgBox % Eval("2+++9 + ~2 1 2*3") ; 15 MsgBox % Eval("x1:=1; f1:=sin(x1)/x1; y:=2; f2:=sin(y)/y; f1/f2") ; 1.850815 MsgBox % Eval("Round(fac(10)/fac(5)**2)  (10choose5) + Fib(8)") ; 21 MsgBox % Eval("1 min1 min2 min 2") ; 2 MsgBox % Eval("(1>>1<=9 && 3>2)<<2>>1") ; 2 MsgBox % Eval("(1 = 1) + (2<>3  2 < 1) + (9>=1 && 3>2)") ; 3 MsgBox % Eval("$b6 21/3") ; 111001 MsgBox % Eval("$b ('1001 << 5)  '01000") ; 100101000 MsgBox % Eval("$0 194*lb/1000") ; 88 Kg MsgBox % Eval("$x ~0xfffffff0 & 7  0x100 << 2") ; 0x407 MsgBox % Eval(" 1 * (+pi ((3%5))) +pi+ 12 + eROUND(abs(sqrt(floor(2)))**2)e+pi $9") ; 3.141592654 MsgBox % Eval("(20+4 GCD abs(2**4)) + (9 GCD (6 CHOOSE 2))") ; 11 t := A_TickCount Loop 1000 r := Eval("x:=" A_Index/1000 ";atan(x)exp(sqrt(x))") ; simulated plot t := A_TickCount  t MsgBox Result = %r%`nTime = %t% ; 1.932884. ~360 ms */ ^#:: ; Replace selection or `expression with result ^#=:: ; Append result to selection or `expression ClipBoard = SendInput ^c ; copy selection ClipWait 0.5 If (ErrorLevel) { SendInput +{HOME}^c ; copy, keep selection to overwrite (^x for some apps) ClipWait 1 IfEqual ErrorLevel,1, Return If RegExMatch(ClipBoard, "(.*)(``)(.*)", y) SendInput % "{RAW}" y1 . (A_ThisHotKey="^#=" ? y3 . " = " : "") . Eval(y3) } Else SendInput % "{RAW}" . (A_ThisHotKey="^#=" ? ClipBoard . " = " : "") . Eval(ClipBoard) Return Eval(x) { ; nonrecursive PRE/POST PROCESSING: I/O forms, 'func', ";" Local FORM, FormF, FormI, i, W, y, y1, y2, y3, y4 FormI := A_FormatInteger, FormF := A_FormatFloat SetFormat Integer, D ; decimal intermediate results! RegExMatch(x, "\$(bhx)(\d*)", y) FORM := y1, W := y2 ; HeX, Bin, .{digits} output format SetFormat FLOAT, % y1<>""W="" ? 0.6 : "0." . W ; Default = 6 decimal places StringReplace x, x, %y% ; remove $.. Loop If RegExMatch(x,"(.*?)(\d+[\.]?\d*\d*[\.]?\d+)e([\+]?\d+)(.*)",y) ; 1st group ungreedy: full constant x := y1 . y2*10**y3 . y4 ; convert scientific constants to decimal (for speed) Else Break Loop If RegExMatch(x, "i)(.*)(0x[af\d]*)(.*)", y) x := y1 . y2+0 . y3 ; convert hex numbers to decimal Else Break Loop If RegExMatch(x, "(.*)'([01]*)(.*)", y) x := y1 . FromBin(y2) . y3 ; convert binary numbers to decimal: sign = first bit Else Break StringReplace x, x,`%, \, All ; % > \ (= MOD) StringReplace x, x, **,@, All ; ** > @ for easier process StringReplace x, x, , #, All ; # = subtraction, different from sign x := RegExReplace(x, "([\)\.\w]\s+[\)\.\d])([az_AZ]+)","$1'$2'") ; op > 'op', vars remain x := RegExReplace(x,"\s*") ; remove spaces, tabs, newlines x := RegExReplace(x,"([az_AZ]\w*)\(","'$1'(") ; "func(" > "'func'(" to avoid atantan conflicts Loop Parse, x, `; y := Eval1(A_LoopField) ; work on preprocessed sub expressions ; return result of last subexpression If FORM = b ; convert to binary y := W ? ToBinW(Round(y),W) : ToBin(Round(y)) Else If (FORM="h" or FORM="x") { SetFormat Integer, Hex ; convert to hex y := Round(y) + 0 } SetFormat Integer, %FormI% ; restore original formats SetFormat FLOAT, %FormF% Return y } Eval1(x) { ; recursive PREPROCESSING of :=, vars, (..) [decimal, no space or ";"] Local i, y, y1, y2, y3 StringGetPos i, x, := ; execute leftmost ":=" operator If (i >= 0) { y := "x" . SubStr(x,1,i) ; user vars internally start with x to avoid name conflicts Return %y% := Eval1(SubStr(x,3+i)) } ; when here: no variable on the left of last ":=" x := RegExReplace(x,"([az_AZ]\w*)([^\w'\]]$)","%x$1%$2") ; VAR > %xVAR%; func', op] remains Transform x, Deref, %x% ; dereference all righthandside %var%s Loop { ; find last innermost (..) If RegExMatch(x, "(.*)\(([^\(\)]*)\)(.*)", y) x := y1 . Eval@(y2) . y3 ; replace "(x)" with value of x (global y3 does not change in Eval@) Else Break } Return Eval@(x) } Eval@(x) { ; EVALUATE PREPROCESSED EXPRESSIONS [decimal, NO: vars, (..), ";", ":="] Local i, y, y1, y2, y3, y4 If x is number Return x ; no more operators left ; execute rightmost ?,: operator RegExMatch(x, "(.*)(\?:)(.*)", y) IfEqual y2,?, Return Eval@(y1) ? Eval@(y3) : "" IfEqual y2,:, Return ((y := Eval@(y1)) = "" ? Eval@(y3) : y) StringGetPos i, x, , R ; execute rightmost  operator IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i))  Eval@(SubStr(x,3+i)) StringGetPos i, x, &&, R ; execute rightmost && operator IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) && Eval@(SubStr(x,3+i)) ; execute rightmost =, <> operator RegExMatch(x, "(.*)(?<![\<\>])(\<\>=)(.*)", y) IfEqual y2,=, Return Eval@(y1) = Eval@(y3) IfEqual y2,<>, Return Eval@(y1) <> Eval@(y3) ; execute rightmost <,>,<=,>= operator RegExMatch(x, "(.*)(?<![\<\>])(\<=?\>=?)(?![\<\>])(.*)", y) IfEqual y2,<, Return Eval@(y1) < Eval@(y3) IfEqual y2,>, Return Eval@(y1) > Eval@(y3) IfEqual y2,<=, Return Eval@(y1) <= Eval@(y3) IfEqual y2,>=, Return Eval@(y1) >= Eval@(y3) ; execute rightmost user operator (low precedence) RegExMatch(x, "i)(.*)'(gcdminmaxchoose)'(.*)", y) IfEqual y2,choose,Return Choose(Eval@(y1),Eval@(y3)) IfEqual y2,Gcd, Return GCD( Eval@(y1),Eval@(y3)) IfEqual y2,Min, Return (y1:=Eval@(y1)) < (y3:=Eval@(y3)) ? y1 : y3 IfEqual y2,Max, Return (y1:=Eval@(y1)) > (y3:=Eval@(y3)) ? y1 : y3 StringGetPos i, x, , R ; execute rightmost  operator IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i))  Eval@(SubStr(x,2+i)) StringGetPos i, x, ^, R ; execute rightmost ^ operator IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ^ Eval@(SubStr(x,2+i)) StringGetPos i, x, &, R ; execute rightmost & operator IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) & Eval@(SubStr(x,2+i)) ; execute rightmost <<, >> operator RegExMatch(x, "(.*)(\<\<\>\>)(.*)", y) IfEqual y2,<<, Return Eval@(y1) << Eval@(y3) IfEqual y2,>>, Return Eval@(y1) >> Eval@(y3) ; execute rightmost + (not unary) operator RegExMatch(x, "(.*[^!\@\*\/\\\~\+\#])(\+\#)(.*)", y) ; lower precedence ops are already handled IfEqual y2,+, Return Eval@(y1) + Eval@(y3) IfEqual y2,#, Return Eval@(y1)  Eval@(y3) ; execute rightmost */% operator RegExMatch(x, "(.*)(\*\/\\)(.*)", y) IfEqual y2,*, Return Eval@(y1) * Eval@(y3) IfEqual y2,/, Return Eval@(y1) / Eval@(y3) IfEqual y2,\, Return Mod(Eval@(y1),Eval@(y3)) ; execute rightmost power StringGetPos i, x, @, R IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ** Eval@(SubStr(x,2+i)) ; execute rightmost function, unary operator If !RegExMatch(x,"i)(.*)(!\+\#\~'(AbsCeilExpFloorLogLnRoundSqrtSinCosTanASinACosATanSgnFibfac)')([\d\.]+)", y) Return x ; no more function (y1 <> "" only at multiple unaries: +) IfEqual y2,!,Return Eval@(y1 . !y4) ; unary ! IfEqual y2,+,Return Eval@(y1 . y4) ; unary + IfEqual y2,#,Return Eval@(y1 . y4) ; unary  (they behave like functions) IfEqual y2,~,Return Eval@(y1 . ~y4) ; unary ~ GoTo %y3% ; functions are executed last: y4 is number Abs: Return Eval@(y1 . Abs(y4)) Ceil: Return Eval@(y1 . Ceil(y4)) Exp: Return Eval@(y1 . Exp(y4)) Floor: Return Eval@(y1 . Floor(y4)) Log: Return Eval@(y1 . Log(y4)) Ln: Return Eval@(y1 . Ln(y4)) Round: Return Eval@(y1 . Round(y4)) Sqrt: Return Eval@(y1 . Sqrt(y4)) Sin: Return Eval@(y1 . Sin(y4)) Cos: Return Eval@(y1 . Cos(y4)) Tan: Return Eval@(y1 . Tan(y4)) ASin: Return Eval@(y1 . ASin(y4)) ACos: Return Eval@(y1 . ACos(y4)) ATan: Return Eval@(y1 . ATan(y4)) Sgn: Return Eval@(y1 . (y4>0)(y4<0)) ; Sign of x = (x>0)(x<0) Fib: Return Eval@(y1 . Fib(y4)) Fac: Return Eval@(y1 . Fac(y4)) } ToBin(n) { ; Binary representation of n. 1st bit is SIGN: 8 > 1000, 1 > 1, 0 > 0, 8 > 01000 Return n=0n=1 ? n : ToBin(n>>1) . n&1 } ToBinW(n,W=8) { ; LS Wbits of Binary representation of n Loop %W% ; Recursive (slower): Return W=1 ? n&1 : ToBinW(n>>1,W1) . n&1 b := n&1 . b, n >>= 1 Return b } FromBin(bits) { ; Number converted from the binary "bits" string, 1st bit is SIGN n = 0 Loop Parse, bits n += n + A_LoopField Return n  (SubStr(bits,1,1)<<StrLen(bits)) } GCD(a,b) { ; Euclidean GCD Return b=0 ? Abs(a) : GCD(b, mod(a,b)) } Choose(n,k) { ; Binomial coefficient p := 1, i := 0, k := k < nk ? k : nk Loop %k% ; Recursive (slower): Return k = 0 ? 1 : Choose(n1,k1)*n//k p *= (ni)/(ki), i+=1 ; FOR INTEGERS: p *= ni, p //= ++i Return Round(p) } Fib(n) { ; nth Fibonacci number (n < 0 OK, iterative to avoid globals) a := 0, b := 1 Loop % abs(n)1 c := b, b += a, a := c Return n=0 ? 0 : n>0  n&1 ? b : b } fac(n) { ; n! Return n<2 ? 1 : n*fac(n1) }
#20

Posted 12 March 2007  05:55 PM
keep up the good work. Its getting better and better.It was originally made for my own use, but I am glad that somebody else finds it useful.
BTW, are you still planing user functions ?
#21

Posted 12 March 2007  08:04 PM
I am flirting with the following ideas:
 iterators, to print tables
 arrays
 macros (as a way to implement user functions and operators)
They are hard. Which one seems to be the most useful?
 iterators, to print tables
 arrays
 macros (as a way to implement user functions and operators)
They are hard. Which one seems to be the most useful?
#22

Posted 12 March 2007  08:15 PM
At first absolutely superb !!!
Even if I don't know the usage and the purpose of some (maths) functions (Choose, Fib, ...) !!!
For me I'd like to see macros and, qhy not, array.
Is there a way to :
1) determine the decimal separator ("," in France and Germany, "." in UK and USA)
2) specify the allowed number of decimals allowed after the decimal separator, and if the result is rounded or not (if 12,345 gives 12,34 or 12,35 with the "," séparator and 2 decimals allowed)
Thanks Lazlo for this superb script.
Even if I don't know the usage and the purpose of some (maths) functions (Choose, Fib, ...) !!!
For me I'd like to see macros and, qhy not, array.
Is there a way to :
1) determine the decimal separator ("," in France and Germany, "." in UK and USA)
2) specify the allowed number of decimals allowed after the decimal separator, and if the result is rounded or not (if 12,345 gives 12,34 or 12,35 with the "," séparator and 2 decimals allowed)
Thanks Lazlo for this superb script.
#23

Posted 13 March 2007  07:38 PM
Decimal separator: Unfortunately, allowing comma interferes with possible future extensions: lists and multivariate functions. Round(x,3) is clear, but what should Round(2,3) mean (2 or 2,000)? We could completely swap the roles of commas and periods, but Round(x.3) looks weird, as lists {1.2}. One could use semicolons in place of some commas, and define another statement terminator, like colons, but it will be confusing. Nevertheless, some pre and post processing could achieve what you want. If you can come up with a consistent scheme, not confusing for programmers, please post it.
Decimal places can be specified now with $3 somewhere in the expression. It sets the decimal places used in the calculations and in the output. It is not perfect, because intermediate results should be more accurate than the output. I just hope AHK will support exponential notations (1.2e6), which would allow using maximum precision inside, and only change the output to the desired format.
Decimal places can be specified now with $3 somewhere in the expression. It sets the decimal places used in the calculations and in the output. It is not perfect, because intermediate results should be more accurate than the output. I just hope AHK will support exponential notations (1.2e6), which would allow using maximum precision inside, and only change the output to the desired format.
#24

Posted 13 March 2007  08:03 PM
Thanks for the explanation for the decimal separator. I'm agree !!!
I didn't understood that "$" was for floating point values. I thought it was for hex and binary values. Thanks for the info.
I didn't understood that "$" was for floating point values. I thought it was for hex and binary values. Thanks for the info.
#25

Posted 13 March 2007  08:24 PM
Version 1.0 is posted, with the main missing feature: user defined functions in the expressions. It turned out relatively easy, by saving the function variable and the function definition as text, and replace the variable with the parameter at function call, and finally evaluate the resulting expression with the script, recursively. This way recursive functions can be defined, too. Global variables have their last assigned values, they need not exist, when the function is defined.
So far there were no bugs reported. Please try the script with complicated expressions, to be able to spot problems.
So far there were no bugs reported. Please try the script with complicated expressions, to be able to spot problems.
#26

Posted 14 March 2007  04:46 PM
Astounding! :shock:
My spouse ( who is very good at Maths ) is helping me to understand the script's capabilities.
Many Thanks.
My spouse ( who is very good at Maths ) is helping me to understand the script's capabilities.
Many Thanks.
#27

Posted 02 April 2007  07:37 AM
Thx, that is really something.
Did you thinking about creating some better help file, with some examples and more friendly organisation in doc or html or something?
Did you thinking about creating some better help file, with some examples and more friendly organisation in doc or html or something?
#28

Posted 02 April 2007  10:15 AM
Thanks, Skan, for the nice words. If you have a question, don't hesitate to post it here.
Examples are included in the script, but I wait with a better help, until AHK offers some tools for fixing the main limitation: precision (fix point intermediate values). Avoiding the conversion of each partial result to fix point can be done if AHK will support scientific number formats (2.007e3), hexfloats or binary forms of numbers. I don't know which one will be supported in the next main AHK release, and when.better help file, with some examples and more friendly organization
#29

Posted 02 April 2007  01:58 PM