 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Laszlo
Joined: 14 Feb 2005 Posts: 3959 Location: Pittsburgh
|
Posted: Tue Aug 23, 2005 11:55 pm Post subject: Simple script for evaluating arithmetic expressions |
|
|
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 ). It can be used to build a calculator or replace selected arithmetic expressions in texts. | Code: | 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.
Last edited by Laszlo on Sat Feb 24, 2007 9:02 pm; edited 1 time in total |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 3959 Location: Pittsburgh
|
Posted: Wed Aug 24, 2005 6:36 am Post subject: |
|
|
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. | Code: | ; 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)
} |
|
|
| Back to top |
|
 |
Chris Site Admin
Joined: 02 Mar 2004 Posts: 10467
|
Posted: Wed Aug 24, 2005 1:31 pm Post subject: |
|
|
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. |
|
| Back to top |
|
 |
Nemroth
Joined: 07 Sep 2004 Posts: 262 Location: France
|
Posted: Wed Aug 24, 2005 3:09 pm Post subject: |
|
|
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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 3959 Location: Pittsburgh
|
Posted: Wed Aug 24, 2005 7:32 pm Post subject: (your homework) |
|
|
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. | Code: | ; 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. |
|
| Back to top |
|
 |
Nemroth
Joined: 07 Sep 2004 Posts: 262 Location: France
|
Posted: Wed Aug 24, 2005 7:59 pm Post subject: |
|
|
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 ? |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 3959 Location: Pittsburgh
|
Posted: Wed Aug 24, 2005 8:12 pm Post subject: |
|
|
| I am sure you got a bad teacher. A good one can make all the kids just love math. |
|
| Back to top |
|
 |
Nemroth
Joined: 07 Sep 2004 Posts: 262 Location: France
|
Posted: Wed Aug 24, 2005 8:34 pm Post subject: |
|
|
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. |
|
| Back to top |
|
 |
Nemroth
Joined: 07 Sep 2004 Posts: 262 Location: France
|
Posted: Wed Aug 24, 2005 9:02 pm Post subject: |
|
|
| Laszlo wrote: | | 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 !!! |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 3959 Location: Pittsburgh
|
Posted: Wed Aug 24, 2005 9:06 pm Post subject: |
|
|
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. |
|
| Back to top |
|
 |
Nemroth
Joined: 07 Sep 2004 Posts: 262 Location: France
|
Posted: Wed Aug 24, 2005 9:49 pm Post subject: |
|
|
Thanks for your answer, Laszlo.
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 |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 3959 Location: Pittsburgh
|
Posted: Fri Aug 26, 2005 8:11 pm Post subject: |
|
|
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: | Code: | 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. | Code: | !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 | Code: | !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. |
|
| Back to top |
|
 |
Nemroth
Joined: 07 Sep 2004 Posts: 262 Location: France
|
Posted: Sat Aug 27, 2005 4:01 pm Post subject: |
|
|
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 :
| Code: | 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
~^NumpadAdd::
; ^+ 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
{
IfEqual ErrorLevel,EndKey:NumpadAdd,
{
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
}
HotKey ~^NumpadAdd, On
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
{
FileRead, NPA_DernCalc, %FICH_G_SvgClipKC%
Send, %NPA_DernCalc%
}
If Cnt = 2
{
NPA_Svgde = %clipboard%
FileRead, clipboard, %FICH_G_SvgClipKC%
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. |
|
| Back to top |
|
 |
Invalid User
Joined: 14 Feb 2005 Posts: 442 Location: Texas, Usa
|
Posted: Wed Sep 07, 2005 5:35 am Post subject: |
|
|
Laszlo, do you plan on making a Evaluation function for handling undefined variables or irrationals, or 'solve for...' equations? or fractions _________________ my lame sig  |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 3959 Location: Pittsburgh
|
Posted: Wed Sep 07, 2005 1:19 pm Post subject: |
|
|
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. |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|