# Simple script for evaluating arithmetic expressions

36 replies to this topic

### #1 Laszlo

Laszlo
• Fellows
• 4713 posts

Posted 23 August 2005 - 10:55 PM

Until AHK will have dynamic expressions, here is a simple script for evaluating arithmetic expressions in a variable or literal string. It handles +-*/ from left to right with the right precedence, but no unary minus (like -1*2 has to be written as 0-1*2), parentheses or functions, so there is plenty of room for extensions (left to the reader as an easy exercise :wink: ). It can be used to build a calculator or replace selected arithmetic expressions in texts.
```a = 12 + 3 * 4 / 2 - 1
MsgBox % Eval(a)
MsgBox % Eval("10/3")

Eval(x)
{
StringGetPos i, x, +, R
StringGetPos j, x, -, R
If (i > j)
Return Left(x,i)+Right(x,i)
If (j > i)
Return Left(x,j)-Right(x,j)
StringGetPos i, x, *, R
StringGetPos j, x, /, R
If (i > j)
Return Left(x,i)*Right(x,i)
If (j > i)
Return Left(x,j)/Right(x,j)
Return x
}

Left(x,i)
{
StringLeft x, x, i
Return Eval(x)
}
Right(x,i)
{
StringTrimLeft x, x, i+1
Return Eval(x)
}```
The rightmost + or - is located first. The expression is split here and the sum or the difference of the left hand side and the right hand side expression (calculated recursively) gives the answer. If there is no + or -, the rightmost * or / is located. After splitting the expression here, the product or the ratio of the left hand side and the right hand side expression (calculated recursively) gives the answer. If there is no more arithmetic operator left in a sub-expression, it must be a number, so it is returned unchanged.

Edit 20070224: Since the scripts were posted, regular expressions were introduced to AHK. They allow great simplifications and removing some restrictions. A newer version is posted here.

### #2 Laszlo

Laszlo
• Fellows
• 4713 posts

Posted 24 August 2005 - 05:36 AM

The following version is longer, but can handle mod (%) and power (** or @) as well as unary - and unary + (-2*3, +5). Two constants, pi and e are replaced by their numerical values. Add your favorite ones, like currency exchange ratios, Kg to Lbs, miles to Km, Fahrenheit to centigrade conversion values, and life will get a little easier.

Choose this one if you need this extra functionality. Still no parentheses, because they are harder. Having parentheses, function calls, like sqrt(2) or sin(pi/3) will be easier.
```; evaluate arithmetic expressions containing
; unary +,- (-2*3; +3)
; +,-; *,/,%(= mod); **(or @, = power)

Eval(x)                                ; pre-process
{
StringReplace x, x, %A_Space%,, All
StringReplace x, x, %A_Tab%,, All   ; remove white space
StringReplace x, x, pi, 3.141592653589793, All
StringReplace x, x, e,  2.718281828459045, All
StringReplace x, x, **, @, All      ; ** -> @ for easier process
Return Eval@(x)
}

Eval@(x)
{
If (Asc(x) = Asc("-"))
Return Eval@("0" x)              ; -x -> 0 - x
If (Asc(x) = Asc("+"))
Return Right(x,0)                ; +x -> x
StringGetPos i, x, +, R             ; i = -1 if no + is found
StringGetPos j, x, -, R
If (i > j)
Return Left(x,i)+Right(x,i)
If (j > i)                          ; i = j only if no + or - found
Return Left(x,j)-Right(x,j)
StringGetPos i, x, *, R
StringGetPos j, x, /, R
StringGetPos k, x,`%, R
If (i > j && i > k)
Return Left(x,i)*Right(x,i)
If (j > i && j > k)
Return Left(x,j)/Right(x,j)
If (k > i && k > j)
Return Mod(Left(x,k),Right(x,k))
StringGetPos i, x, @, R
If (i >= 0)
Return Left(x,i)**Right(x,i)
Return x
}

Left(x,i)
{
StringLeft x, x, i
Return Eval@(x)
}
Right(x,i)
{
StringTrimLeft x, x, i+1
Return Eval@(x)
}```

### #3 Chris

Chris
• 10727 posts

Posted 24 August 2005 - 12:31 PM

I always enjoy posts like this where you apply your extensive algorithm expertise to create elegant, efficient scripts and functions.

Thanks for providing another solution for something AutoHotkey lacks.

### #4 Nemroth

Nemroth
• Members
• 278 posts

Posted 24 August 2005 - 02:09 PM

Thanks a lot, Laszlo.
Of course a brillant script, as every time.
I will try to understand it and if I can, to implement parentheses and functions call !!!
thanks again for all you very clever contribution for AHK.

### #5 Laszlo

Laszlo
• Fellows
• 4713 posts

Posted 24 August 2005 - 06:32 PM

Here is a still larger version with parenthesis, function and variable handling. There could be a problem with expressions like 2*(1-2), which is reduced to 2*-1, an invalid expression. To combat that the subtraction operator is changed internally to # (so it could not be used in function names, as @ is forbidden, used as the power operator). After the replacement the negative sign is not considered an operator but part of a number.

The script also recognizes 3 functions, abs(), sqrt() and floor(), as examples. You can add as many to the list as you wish, even your private functions of one parameter. (If a function name contains another, it has to be listed first, like abs0, MyAbs, ABS.)

Constants might conflict with function names, like e is in Ceil(). To avoid problems, constants have to be in [ ], like [e] or [pi]. Now these can be any variable name, defined in the function Eval(). (Some vars are used, like x, i, j, L,and R. If you want to use them in expression, rename the ones used by the function to some obscure string, like appending a \$ sign to them.)

Bitwise operators (~,&,|,^,<<,>>) can be easily added, just follow the pattern of the other operators. Still missing is multi parameter functions, like round([pi],3). It would require handling the "," as operator, which is not hard, but seldom needed.
```; AHK 1.0.37
; evaluate arithmetic expressions containing
; unary +,- (-2*3; +3)
; +,-(or # = subtract); *,/,%(= mod); **(or @ = power)
; (,); [var] (like [pi],[e]); abs(),sqrt(),floor()

Eval(x)                                ; top-level process
{
pi = 3.141592653589793
e  = 2.718281828459045              ; add below your constants

StringReplace x, x, %A_Space%,, All ; remove white space
StringReplace x, x, %A_Tab%,, All
StringReplace x, x, -, #, All       ; # = subtraction
StringReplace x, x, (#, (0#, All    ; (-x -> (0-x
If (Asc(x) = Asc("#"))
x = 0%x%                         ; leading -x -> 0-x
StringReplace x, x, (+, (, All      ; (+x -> (x
If (Asc(x) = Asc("+"))
StringTrimLeft x, x, 1           ; leading +x -> x
StringReplace x, x, **, @, All      ; ** -> @ for easier process
Loop
{                                   ; replace constants
StringGetPos i, x, [             ; find [
IfLess i,0, Break
StringGetPos j, x, ], L, i+1     ; closest ]
StringMid  y, x, i+2, j-i-1      ; variable in []
StringLeft L, x, i
StringTrimLeft R, x, j+1
x := L . %y% . R                 ; replace [var] with value of var
}
Loop
{                                   ; finding an innermost (..)
StringGetPos i, x, (, R          ; rightmost (
IfLess i,0, Break
StringGetPos j, x, ), L, i+1     ; closest )
StringMid  y, x, i+2, j-i-1      ; expression in ()
StringLeft L, x, i
StringTrimLeft R, x, j+1
x := L . Eval@(y) . R            ; replace (x) with value of x
}
Return Eval@(x)
}

Eval@(x)
{
StringGetPos i, x, +, R             ; i = -1 if no + is found
StringGetPos j, x, #, R
If (i > j)
Return Left(x,i)+Right(x,i)
If (j > i)                          ; i = j only if no + or - found
Return Left(x,j)-Right(x,j)
StringGetPos i, x, *, R
StringGetPos j, x, /, R
StringGetPos k, x,`%, R
If (i > j && i > k)
Return Left(x,i)*Right(x,i)
If (j > i && j > k)
Return Left(x,j)/Right(x,j)
If (k > i && k > j)
Return Mod(Left(x,k),Right(x,k))
StringGetPos i, x, @, R             ; power
If (i >= 0)
Return Left(x,i)**Right(x,i)
StringGetPos i1, x, abs, R          ; no more operators
StringGetPos i2, x, sqrt, R         ; look for functions
StringGetPos i3, x, floor, R        ; insert further functions below
m := Max1(i1,i2,i3)
If (m = i1)                         ; apply the rightmost function
Return abs(Right(x,i1+2))        ; only one function is applied
Else If (m = i2)                    ; in one recursion
Return sqrt(Right(x,i2+3))
Else If (m = i3)
Return floor(Right(x,i3+4))      ; offset j + StrLen(func) - 2
Return x
}

Left(x,i)
{
StringLeft x, x, i
Return Eval@(x)
}
Right(x,i)
{
StringTrimLeft x, x, i+1
Return Eval@(x)
}
Max1(x0,x1="",x2="",x3="",x4="",x5="",x6="",x7="",x8="",x9="",x10="",x11="",x12="",x13="",x14="",x15="",x16="",x17="",x18="",x19="",x20="")
{
x := x0
Loop 20
{
IfEqual   x%A_Index%,, Break
IfGreater x%A_Index%, %x%
x := x%A_Index%
}
IfLess x,0, Return -2               ; prevent match with -1
Return %x%
}```
The script grew quite complex, so there might be some bugs left. Please experiment with it and report any abnormal behavior.

### #6 Nemroth

Nemroth
• Members
• 278 posts

Posted 24 August 2005 - 06:59 PM

Laszlo, you're very fast !!!
Of course I will try it, and if I can extend it I will post.
But I need first to carrefully read and study the script.
Thanks Laszlo for that superb work.
Why didn't like too mutch maths at school ?

### #7 Laszlo

Laszlo
• Fellows
• 4713 posts

Posted 24 August 2005 - 07:12 PM

I am sure you got a bad teacher. A good one can make all the kids just love math.

### #8 Nemroth

Nemroth
• Members
• 278 posts

Posted 24 August 2005 - 07:34 PM

May be there are some problems, may be my understanding is not OK :
My first tests :

-1+45/2 = 21.500
I think it is equivalent to -1+45 = 44 then 44/2 = 22
but
(-1+45)/2 = 22.000 OK

45-1/2 = 44.500
but
(45-1)/2 = 22.000 OK

-1+45*2 = 89
but
(-1+45)*2 = 88 OK

45-1*2 = 43
but
(45-1)*2 = 88 OK

abs(12.6) = 12.600

may be I made mistakes.

Thanks for your time.

### #9 Nemroth

Nemroth
• Members
• 278 posts

Posted 24 August 2005 - 08:02 PM

I am sure you got a bad teacher. A good one can make all the kids just love math.

I think I had bad ones and good ones. But my studies ar far from now...
I think the first time I really begins to have some interest in maths was with my first computer : a ZX81, 16K RAM, working with TV set and using a tape recoder as "hard disk", my first tests to programm in Z80 ASM, but there is a long time ago !!!

### #10 Laszlo

Laszlo
• Fellows
• 4713 posts

Posted 24 August 2005 - 08:06 PM

These come from the differences between how a cheap calculator reacts to keystrokes and how a computer language handles operator precedence: First the power is calculated, then *,/ and %, from left to right, then +,- also fro left to right. In case -1+45/2 first 45/2 is computed (22.5) then -1 is added to it, so the right answer is 21.500. Also, in 45-1/2 first 1/2 is computed (0.5) and then it is subtracted form 45, giving 44.500. Similarly with multiplication: in 45-1*2 first 1*2 is computed (2) and it gets subtracted from 45, giving 43 as the result.

The cheap calculator order of operations is easier, but confusing. If you want that, make the search flat in the Eval@() function, that is, find out the rightmost operator, any of the +-*/% or **, and perform that.

### #11 Nemroth

Nemroth
• Members
• 278 posts

Posted 24 August 2005 - 08:49 PM

I understand the operator precedence topic but in fact I thought that the analysis of the entered calculation would be "naturally'" parsed from left to right, like in cheap calculators.
I thought that the calculation is similar in its behaviour as the one of the Windows Calc : an operation, then the other witch modify the prefious result and so on, but on one line...
I think that the parenthesis (in this way to do the things) allow to change the order of the calculations, if it is what the user wants to do.
I will try when I can to make modifications in ths way, as I think that for the use I have of this functionnality, this basic behaviour is better.
Thanks again for the fabulous work

### #12 Laszlo

Laszlo
• Fellows
• 4713 posts

Posted 26 August 2005 - 07:11 PM

Actually, if you use the Windows Calculator, you don't need all these (unless you need your own functions or constants). The calculator buttons all have keyboard shortcuts, so you only have to write the expressions conform to the AHK send command, that is, {F4}, ^p, `% etc. For the XP calculator:
```Button    Key       Button    Key

%         %         Hyp       h

(         (         Int       ;

)         )         Inv       i

*         *         ln        n

+         +         log       l

+/-       F9        Lsh       <

-         -         M+        CTRL+P

.         . or ,    MC        CTRL+L

/         /         Mod       %

0-9       0-9       MR        CTRL+R

1/x       r         MS        CTRL+M

=         ENTER     n!        !

A-F       A-F       Not       ~

And       &         Oct       F7

Ave       CTRL+A    Or        |(pipe)

Backspace BACKSPACE pi        p

Bin       F8        Qword     F12

Byte      F4        Radians   F3

C         ESC       s         CTRL+D

CE        DEL       sin       s

cos       o         sqrt      @

Dat       INS       Sta       CTRL+S

Dec       F6        Sum       CTRL+T

Degrees   F2        tan       t

dms       m         Word      F3

Dword     F2        Xor       ^

Exp       x         x^2       @

F-E       v         x^3       #

Grads     F4        x^y       y

Hex       F5```
The following script copies the selected expression to the clipboard, starts the Windows Calculator, pastes the clipboard into it, waits a second, copies the result to the clipboard, closes Calculator and finally pastes the result back to the original application, overwriting the selected expression.
```!z::

Send ^c

WinGet ID, ID, A

Run Calc.exe

WinWaitActive Calculator

Send ^v=

Sleep 1000

Send ^c

WinClose Calculator

WinActivate ahk_id %ID%

WinWaitActive ahk_id %ID%

Send ^v

Return```
It is just a proof of the concept, 1 sec waiting time might not always be enough. To be sure wait some 30 ms, copy the result again and see if it changed. The same result can be just bad luck, so wait until the result is the same at several times, like
```!z::

Send ^c

WinGet ID, ID, A

Run Calc.exe

WinWaitActive Calculator

Send ^v=

Loop

{

Sleep 30

Send ^c

If (OldClip = clipboard)

IfGreater cnt,5, Break

Else cnt++

Else {

OldClip = %clipboard%

cnt =

}

}

WinClose Calculator

WinActivate ahk_id %ID%

WinWaitActive ahk_id %ID%

Send ^v

Return```
Also, you could pre-process the original expression, like replacing F4 by {F4}, so the user need not remember the AHK send syntax.

### #13 Nemroth

Nemroth
• Members
• 278 posts

Posted 27 August 2005 - 03:01 PM

Hi Lazslo.
I use the Calculator to do the calculations, and I "borrowed" you an other of your scripts to do that. But I'd like to do the calculations internally, as
1) I can't launch the calc hidden
2) the Windows calculator doesn't support function calls.
Here is the script :
```FICH_G_Base = D:\Dossiers\Dossier Personnel\Gdr\
FICH_G_SvgClipKC = %FICH_G_Base%Gest\Temp\SvgClipKC.cb		; sauvegarde clipboard Keep Calc

NPACnt =
Return

:*:++::				; I want the calculation to be saved but not displayed
PP_Typ = 0
Goto EE_Suite
:*:%%::				; I want the calculation to be saved and displayed
PP_Typ = 1
EE_Suite:
PP_Svgde = %clipboard%

;	Gosub, ActiveExtra	; To activate the app in witch I want to use this procedure
; A IBM3270 Windows emulator from Attachmate I use at work

Loop
{
Input, PP_OutVar, L1 M V, {Space}{Enter}{Tab}{BackSpace}
IfInString, ErrorLevel, EndKey:Space
{
Break
}
else IfInString, ErrorLevel, EndKey:Tab
{
Break
}
else IfInString, ErrorLevel, EndKey:Enter
{
Break
}
else IfInString, ErrorLevel, EndKey:BackSpace
{
Continue
}
else
{
PP_Res = %PP_Res%%PP_OutVar%
Continue
}
}

clipboard = %PP_Res%
Run, Calc
WinWaitActive Calculatrice
Send, ^v
Send,`n
Sleep, 500
Send, ^c
WinClose, Calculatrice
FileDelete, %FICH_G_SvgClipKC%
FileAppend, %clipboard%, %FICH_G_SvgClipKC%	; I save the result of the last calculation
;	Send, {Home}{End}				; Keys used in Extra! to del the command line content
Send, +{Home}{Del}				; For Windows use and test
If PP_Typ					; I display the result (or not)
{
Send, ^v
}
PP_Res =
clipboard = %PP_Svgde%
Return

; ^+ Send ^v
; ^++ Send content last calculation
HotKey ~^NumpadAdd, Off			; No self interrupt

Loop
{
NPACnt++
Input key, L1 M V, {NumpadAdd}			; Next key
IfEqual NPACnt,,				; Counter was reset outside
{
{
GetKeyState st, Ctrl, P
IfEqual st,D, SetEnv NPACnt,1	; New Ctrl-Enter arrived
}
break
}
IfEqual ErrorLevel,EndKey:NumpadAdd,continue	; At Enter: keep on counting
GoSub NPAAct
break
}

return

~Ctrl Up::				; Ctrl released: Stop counting
IfEqual NPACnt,,return		; Exit if no Ctrl-Enter was pressed yet
GoSub NPAAct
return

NPAAct:
Cnt := NPACnt
NPACnt =			; Reset while MsgBox is still active
If Cnt = 1
{
Send, %NPA_DernCalc%
}
If Cnt = 2
{
NPA_Svgde = %clipboard%
Send, ^v
clipboard = %NPA_Svgde%
}
return```
You can recognize your slightly modified work...

For the moment I haven't the time but I'll work your "Simple script for evaluating arithmetic expressions" to make it do the calculations in the Windows Calc way (but without calling the Windows calc) and to support multi parameters function call, if I can.

I think that the use of the operator precedence isn't very efficient for a "standard" user and that the Windows Calc's behavior is the one witch is expected in this case.
But of course your script is perfectly correct.
Thanks again for your fabulous work.

### #14 Invalid User

Invalid User
• Members
• 447 posts

Posted 07 September 2005 - 04:35 AM

Laszlo, do you plan on making a Evaluation function for handling undefined variables or irrationals, or 'solve for...' equations? or fractions

### #15 Laszlo

Laszlo
• Fellows
• 4713 posts

Posted 07 September 2005 - 12:19 PM

You could add yourself checking for undefined functions or variables, that is easy. On the other hand, making it a full blown math package is hard. AHK was not made for that. For example, numbers are practically fix point ([pi]/10000000000*10000000000 <> [pi]), and if you work with large or small numbers the precision of the intermediate results ruin the accuracy. Maybe the best is to interface AHK to GMP, the GNU multi precision numerical library, compiled to a Windows DLL.

Keeping fractions unevaluated to get rational results can be done, like complex number handling, but I don't know how you want to handle irrationals, especially transcendent numbers (pi, e), which are not roots of integer coefficient polynomials. Symbolic math packages keep expressions unevaluated, and manipulate them symbolically. It is hard.

Symbolic solution of general equations is also hard. Numerical root finding with an iterative method, like Regula Falsi, can be done for a single variable, otherwise it is hard. But then you could ask for optimization, too: finding the minimum. In the multivariate case, you guessed, is hard, too.