Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Simple script for evaluating arithmetic expressions


  • Please log in to reply
37 replies to this topic
Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

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.

In case it's of interest, AutoHotkey stores intermediate expression results as 64-bit floating point numbers (or integers if there's no decimal portion). However, when the final result is stored in a variable (or for use by a command), the default precision is 6 decimal places. This can be increased via SetFormat. For example, the following retains a double's full 15 digits of precision:

SetFormat, Float, 0.15
MsgBox % 3.141592653589793/10000000000*10000000000
; It displays 3.141592653589793.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Yes, the fix point conversion at store or at set command parameters makes the arithmetic practically fix point. If you don't know the range of the intermediate results (as at a complicated expressions), you may end up with inaccurate results.
SetFormat Float, 0.15

a := 3.141592653589793/1000000000000

MsgBox % a*1000000000000
displays 3.142000000000000, only 3 decimals are accurate. Of course, you could set a very long float format at the beginning of the calculations and reset it to the desired length at the end, but you still have to have a reasonable estimate on the intermediate results.
SetFormat Float, 0.99

c  = 100000000000000000000000000000000

a := 3.141592653589793/c

b := a*c

SetFormat Float, 0.15

b += 0

MsgBox %b%


Decarlo110
  • Members
  • 303 posts
  • Last active: Feb 12 2006 02:15 AM
  • Joined: 15 Dec 2004
This set of scripts is, as Nemroth said, brilliant. Essential for computation until such time that dynamic expressions are built-in. The title is a bit odd though, since it isnt so simple to me. :p

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.

I was wondering if you had seen Rubberduck's BigInteger-Calculation with AHK script, and if so, what you thought of it, and could it be adapted for or used as a basis for exponents and roots.
1) The Open Source Definition http://www.opensourc...ition_plain.php

2) Intuitive. Logical. Versatile. Adaptable. <>

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Thanks for the compliments. Rubberduck's script is way too long for me to comprehend fully. It seems to work. Its main merit is educational: anyone interested in programming long arithmetic could find the necessary ideas there. I am sure it can be extended for handling other operators, too, but if you add expression parsing, functions, etc, it will go unmanageably large. We should use for that a structured, modular programming language.

I work often with long numbers, so for me speed is important. Therefore I wrote a script, which provides an interface from an editor or from MS Word to Pari/GP (an open source computer algebra system), which loads faster than the windows calculator and provides arbitrary precision at high speed. I thought there was no interest in the AHK forum for this kind of exotic stuff, but if there were, I could post the script. As you probably guessed, I try to keep the code size below 50 lines, otherwise most everybody would not bother to try to understand, and use it blindly just as a downloaded program.

Pari/GP is nice, but has some shortcomings. E.g. there is no hex I/O, or long random number generator. Therefore I compiled GMP, the Gnu Multi Precision arithmetic library to a Windows DLL, and wrote an AHK wrapper for most of its functions. It works, but the wrapper script is long. I haven't got the time to write (or modify/adapt/compile) an expression parser, so you have to write all operations as function calls, like gmp_add(x,1). One day I will finish it, but it might not be worth the effort. For longer calculations I run Mathematica. It takes minutes to start up, but then it is very nice. I can recommend it if you have three grands for SW.

Futurepower(R)
  • Members
  • 38 posts
  • Last active: Oct 11 2014 11:35 PM
  • Joined: 20 Jun 2005
Laszlo, I wrote the comment below and then read the part of your comment about you making a GMP DLL for AutoHotkey. However, maybe it is of interest anyway:

Laszlo, this seems relevant to your comment about GMP:

GMP for Windows
<!-- m -->http://www.cs.nyu.edu/exact/core/gmp/<!-- m -->

But I don't see a precompiled DLL.

Another subject: In general, I find arithmetic in AutoHotkey mystifying.

I offered to help re-write this page:
<!-- m -->http://www.autohotke...s/Variables.htm<!-- m -->
which I think should provide more clear information about assignment operators.

I don't know when to use := and when to use =

I notice that == works to test equivalency.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
In the mean time I found a better solution: calc. It is free, runs in Windows under GnuWin32. You can download the binaries from here. It has a nice expression parser, handles any number system (hex, octal, decimal...), fix or floating point operations with arbitrary precision, many transcendental functions, and it is lightning fast. If you do arithmetic with a million digits, GMP might be faster, otherwise, there is no noticeable difference.

If you want to use GMP in Windows the easiest is to install MinGW with the GNU C compiler and GNU make. The GMP make file under MinGW can create a dynamic library, which can be normally accessed from AHK using DllCalls. I did it the hard way: tweaked the Visual Studio setup, until it compiled GMP. It was a week work, and quite a stupid idea.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005

I don't know when to use := and when to use =

For doing expressions, you must use := or derivatives (+=, etc.).
AFAIK, = works only for simple initializations (numeric constant or string) and string concatenation with variable substitution. That's all.

@Laszlo: I hope you don't mind if I plug in my simple utility: MPCalc. I made a simple wrapper around a little expression parser library I found. I made it, at the time, for AutoIt2, when most of its math power used EnvAdd and the like...

Its advantages: small (native Windows code), to the point (not everybody compute with thousand of decimal digits...), able to output to stdout (thus to a flat file), to a .ini file or to the clipboard.

Now, it is not as powerful as calc, but it still may be useful to some people. Which can actually use your script for such simple expressions...
It is mostly obsolete, I know :-)
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
MPCalc looks like a nice little utility (it is hard to beat its size or speed), but the simplest solution (if you don't need infinite precision), is the Windows calculator (or the PowerToy calculator, etc.). It accepts input pasted from the Clipboard, understands brackets and operator precedence, logic operations, decimal, octal, hex and binary I/O, practically everything a normal user wants. It is fast and already installed in your PC.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Since the scripts were posted, regular expressions were introduced to AHK. They allow great simplifications and removing some restrictions. The script below evaluates an arithmetic expression contained in a string, without any external programs. Therefore, it can be compiled and run in machines, where AHK is not installed. It can handle the usual arithmetic operations: + - * / mod (as % or \) and power (as **), predefined constants (pi and e) and some AHK functions (abs, floor, sqrt). Terms can be grouped with parentheses (..).

; AHK 1.0.46+
; evaluate arithmetic expressions containing
; unary +,- (-2*3; +3)
; +,-,*,/,\(or % = mod); **(or @ = power)
; (..); var (pi, e); abs(),sqrt(),floor()

MsgBox % Eval("-floor(abs(sqrt(1))) * (+pi -((3%5))) +pi+ 2-1-1 + e-abs(sqrt(floor(2)))**2-e") ; 1

Eval(x) {                              ; expression preprocessing
   Static pi = 3.141592653589793, e = 2.718281828459045

   StringReplace x, x,`%, \, All       ; % -> \ for MOD
   x := RegExReplace(x,"\s*")          ; remove whitespace
   x := RegExReplace(x,"([a-zA-Z]\w*)([^\w\(]|$)","%$1%$2") ; var -> %var%
   Transform x, Deref, %x%             ; dereference all %var%

   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 {                              ; find innermost (..)
      If !RegExMatch(x, "(.*)\(([^\(\)]*)\)(.*)", y)
         Break
      x := y1 . Eval@(y2) . y3         ; replace "(x)" with value of x
   }
   Return Eval@(x)                     ; no more (..)
}

Eval@(x) {
   RegExMatch(x, "(.*)(\+|\#)(.*)", y) ; execute rightmost +- operator
   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
   If !RegExMatch(x,".*(abs|floor|sqrt)(.*)", y)
      Return x                         ; no more function
   IfEqual y1,abs,  Return abs(  Eval@(y2))
   IfEqual y1,floor,Return floor(Eval@(y2))
   IfEqual y1,sqrt, Return sqrt( Eval@(y2))
}
It is a major rewrite, so look out for bugs. More constants can be added after the Static directive, more AHK functions can be referenced (even user functions of one parameter), with editing the "If !RegExMatch(x,".*(abs|floor|sqrt|MyFun)..." command and adding new calls to the end of the script: "IfEqual y1,MyFun, Return MyFun( Eval@(y2))".

This is my 2500th post to the Forum. Have fun!

Labouche10
  • Members
  • 17 posts
  • Last active: Apr 29 2008 02:07 AM
  • Joined: 28 Nov 2007
I am blown away by the crazy logic that you guys have implemented in creating a dynamic evaluator. In the meantime I wrote a quick & dirty little program that seems to do the same thing but much easier and with alot less code. I need some help though filling in the last gap. This great little calculator is actually able to take advantage of all the mathematical wonderfulness in autohotkey including all the trig and powers etc etc. I added a little regexreplace function for pi also. This script writes a dynamic little script that uses the built in evaluation of autohotkey then writes the solution to another file which is read. Both temp files are then deleted. It is a shame that all the previous code was written. I'm sure it took a long time to write. The only problem I'm having at this point is getting the dynamically written script to execute on computers that do not have autohotkey installed. Hopefully one of you more experienced guys can help with that. Thanks in advance for the help!

Thanks
Labouche10

/*
Title: My Calculator
Compiled/Written 01.16.2008 by labouche10
This is just a basic calculator that I wrote that was inspired by my favorite calculator. 
I wrote this for the people in my office to use.
I'm a noob so don't come down on me too hard! I hope someone can use this as much as I know I will.
*/


#NoEnv
SendMode Input
SetWorkingDir %A_ScriptDir%
CoordMode, tooltip, relative
groupadd, calculatorgroup, %A_Username%'s Calculator

; Add Menus
Menu, FileMenu, add, &Print           Ctrl+P, printout
Menu, FileMenu, add, &Save          Ctrl+S, save
Menu, FileMenu, add, Save &As     Ctrl+Shift+S, saveas
Menu, FileMenu, add, E&xit            Alt+F4, guiclose
Menu, MyMenuBar, Add, &File, :FileMenu
Menu, EditMenu, add, &Copy History      Ctrl+C, copyhistorytoclipboard
Menu, EditMenu, add, Clear History      Ctrl+Backspace, clearhistory
Menu, EditMenu, add, &Undo Clear         Ctrl+Z, undoclearhistory
Menu, MyMenuBar, Add, &Edit, :EditMenu
Menu, OptionsMenu, add, &Set Decimal Places, rounding
Menu, OptionsMenu, add, &Stop Rounding, stoprounding
Menu, MyMenuBar, Add, &Options, :OptionsMenu
Menu, HelpMenu, Add, &Shortcuts	F1, Helpmenushortcuts
Menu, HelpMenu, add, &About..., helpmenuabout
Menu, MyMenuBar, Add, &Help, :HelpMenu

; Gui functions
Gui, Menu, MyMenuBar
Gui, +Resize +MinSize
Gui, Color, 0x000000, 0x000000  ;0xD2E8F2
Gui, font, s10 bold cWhite, copperplate gothic    :2D7597
Gui, add, text,, Description
Gui, font, s10 norm cWhite, copperplate gothic
Gui, add, edit, w175 R1 +veditdescription
Gui, font, s10 bold cWhite, copperplate gothic
Gui, add, text,, History
Gui, font, s10 norm cWhite, copperplate gothic
Gui, add, edit, w175 h200 +readonly +vedithistory
Gui, font, s10 bold cWhite, copperplate gothic
Gui, add, text,+vinput, Input
Gui, Font, s12 bold cWhite, copperplate gothic
Gui, add, edit, w175 R1 +veditcalculate
Gui, Font, s10 norm, copperplate gothic
Guicontrol, +0x40, edithistory
Guicontrol, focus, editcalculate
Gui, Show,, %A_Username%'s Calculator
;WinSet, ExStyle, ^0x80, %A_Username%'s Calculator
;WinSet, ExStyle, +0x80, %A_Username%'s Calculator
Return

; Context sensitive GUI hotkeys
#IfWinActive ahk_group calculatorgroup

; The following four functions use the most recent result as the first argument when pressing only +,-,*,/ 
NumpadAdd::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, +
			Return
		}
		Else
		{
			Sendraw, answer+
			Return
		}
	}
	Else
	{
		Sendraw, +
		Return
	}
}
Else
	Sendraw, +
Return

+::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, +
			Return
		}
		Else
		{
			Sendraw, answer+
			Return
		}
	}
	Else
	{
		Sendraw, +
		Return
	}
}
Else
	Sendraw, +
Return

~F1::
Gosub, helpmenushortcuts
Return

NumpadSub::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, -
			Return
		}
		Else
		{
			Sendraw, answer-
			Return
		}
	}
	Else
	{
		Sendraw, -
		Return
	}
}
Else
	Sendraw, -
Return

-::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, -
			Return
		}
		Else
		{
			Sendraw, answer-
			Return
		}
	}
	Else
	{
		Sendraw, -
		Return
	}
}
Else
	Sendraw, -
Return

NumpadMult::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, *
			Return
		}
		Else
		{
			Sendraw, answer*
			Return
		}
	}
	Else
	{
		Sendraw, *
		Return
	}
}
Else
	Sendraw, *
Return

*::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, *
			Return
		}
		Else
		{
			Sendraw, answer*
			Return
		}
	}
	Else
	{
		Sendraw, *
		Return
	}
}
Else
	Sendraw, *
Return

NumpadDiv::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, /
			Return
		}
		Else
		{
			Sendraw, answer/
			Return
		}
	}
	Else
	{
		Sendraw, /
		Return
	}
}
Else
	Sendraw, /
Return

/::
Gui, Submit, NoHide
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	controlgettext, temptext, edit3
	if temptext = 
	{
		If answerall =
		{
			Sendraw, /
			Return
		}
		Else
		{
			Sendraw, answer/
			Return
		}
	}
	Else
	{
		Sendraw, /
		Return
	}
}
Else
	Sendraw, /
Return

; This char doesn't belong in the edit box
=::
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	TrayTip, Entry Error,Enter a numerical calculation,,3
	SetTimer, Removetraytip, 5000
	Return
}
Else
	Sendraw, =
Return

; Proceed with the calculation
~NumPadEnter::
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
	Gosub, Calculate
Else
	Sendinput, {Enter}
Return

~Enter::
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
	Gosub, Calculate
Else
	Sendinput, {Enter}
Return

; Print results in a sort of 'adding machine' output.
^p::
printout:
Gui, Submit, NoHide
FormatTime, currentime
edit = 
edit := "`nPrinted by: " . A_Username . "`n`nPrinted time: " . currentime . "`n`n`n`nDescription: " . editdescription . "`n`n`n`nCalculations:`n`n" . answerall
Print(edit)
Return

; Why would you want to save your results? I dunno if you want to then do it.
^s::
save:
If savefilename =
	Fileselectfile, savefilename, 2, %A_Desktop%\calchistory.rtf, Save As, *.rtf
Gui, Submit, NoHide
FormatTime, currentime
edit = 
edit := "`nPrinted by: " . A_Username . "`n`nPrinted time: " . currentime . "`n`n`n`nDescription: " . editdescription . "`n`n`n`nCalculations:`n`n" . answerall
fileappend, %edit%, %savefilename%
Return

^+s::
saveas:
Fileselectfile, savefilename, , %A_Desktop%\calchistory.rtf, Save As, *.rtf
savefilename := savefilename . ".rtf"
Gui, Submit, NoHide
FormatTime, currentime
edit = 
edit := "`nPrinted by: " . A_Username . "`n`nPrinted time: " . currentime . "`n`n`n`nDescription: " . editdescription . "`n`n`n`nCalculations:`n`n" . answerall
fileappend, %edit%, %savefilename%
Return


; Just a helpful idea (hopefully)
^c::
copyhistorytoclipboard:
clipboard := answerall
Return

; Clear the history box
^Backspace::
clearhistory:
controlgettext, temptext, edit2
If answerall = 
{
	TrayTip, Error!,You must have history before you can clear it!,,3
	SetTimer, Removetraytip, 5000
	Return
}
Answerallbackup := Answerall
Answerall = 
Guicontrol, text, edithistory, %Answerall%
Return

; I wished my last calculator actually had this
^z::
Undoclearhistory:
controlgettext, temptext, edit2
If answerallbackup = 
{
	TrayTip, Error!,You must clear the history before you can restore it!,,3
	SetTimer, Removetraytip, 5000
	Return
}
Else If temptext <>
{
	TrayTip, Error!,You cannot restore history after starting new calculations,,3
	SetTimer, Removetraytip, 5000
	Return
}
Answerall := Answerallbackup
Guicontrol, text, edithistory, %Answerall%
ControlSend Edit2, ^{End}, ahk_group calculatorgroup
Return

; While in the 'Input' box this will just bring the last calculation back to be edited
~up::						;Brings back the last calculation
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	Guicontrol, text, edit3, %editcalculatebackup%
	controlsend edit3, ^{End}, ahk_group calculatorgroup
}
Else
	Sendraw, {up}
Return

^::
ControlGetFocus, focusvar, ahk_group calculatorgroup
If focusvar = edit3
{
	Sendraw, **
}
Else
	Sendraw, ^
Return

rounding:
Loop,
{
	Inputbox, roundingvar, Decimal Places to round, Enter the number of decimal places (between 0 and 10) to round your answers to:
	If errorlevel
		Break
	If rounding var is not integer
	{
		Msgbox, Error you must enter a whole number beween 0 and 10!
		Continue
	}
	Else If (roundingvar < 0 or roundingvar > 10)
	{
		Msgbox, Error you must enter a value beween 0 and 10!
		Continue
	}
	Else
		Break
}
Return

stoprounding:
roundingvar = 
Return

; The superficial portion of the calculation
Calculate:
Gui, Submit, NoHide
if editcalculate = 
	Return
editcalculate := RegExReplace(editcalculate,"answer",answer)
editcalculate := RegExReplace(editcalculate,"pi","3.1415926535897932384626433832795")
editcalculate := RegExReplace(editcalculate,"p","3.1415926535897932384626433832795")
editcalculatebackup := editcalculate
filedelete, tempcalc.ahk
filedelete, tempanswer
fileappend,
(
#NoTrayIcon
#ErrorStdOut
answer := %editcalculate%
fileappend, `%answer`%, tempanswer                                      ; This is the simple solution
filesetattrib, h, tempanswer
exit
), tempcalc.ahk
filesetattrib, h, tempcalc.ahk
RunWait, tempcalc.ahk
IfExist, tempanswer
{
	fileread, answer, tempanswer
	filedelete, tempcalc.ahk
	filedelete, tempanswer
}
Else
{
	filedelete, tempcalc.ahk
	If answerall = 
	{
		answerall := "ERROR"
		TrayTip, Entry Error, There is an error in your calculation,,3
		SetTimer, Removetraytip, 5000
	}
	Else
	{
		answerall := answerall . "`nERROR"
		TrayTip, Entry Error, There is an error in your calculation,,3
		SetTimer, Removetraytip, 5000
	}
	Guicontrol, text, edithistory, %Answerall%
	ControlSend Edit2, ^{End}, ahk_group calculatorgroup
	Return
}
If answer is not number
{
	If answerall = 
	{
		answerall := "ERROR"
		TrayTip, Entry Error, There is an error in your calculation,,3
		SetTimer, Removetraytip, 5000
	}
	Else
	{
		answerall := answerall . "`nERROR"
		TrayTip, Entry Error, There is an error in your calculation,,3
		SetTimer, Removetraytip, 5000
	}
}
Else
{
	answerround := answer
	Loop,
	{
		stringright, rightvar, answerround, 1
		If roundingvar <>
		{
			answerround := Round(answerround,roundingvar)
			Break			
		}
		Else If instr(answerround,".") = 0
			Break			
		Else If rightvar = .
		{
			Stringtrimright, answerround, answerround, 1
			Continue
		}
		Else If rightvar = 0
		{
			Stringtrimright, answerround, answerround, 1
			Continue
		}
		Else
			Break
	}
	If answerall = 
	{
		answerall := editcalculatebackup . " =`n                " . answerround
		Guicontrol, text, editcalculate, 
	}
	Else
	{
		answerall := answerall . "`n" . editcalculatebackup . " =`n                " . answerround
		Guicontrol, text, editcalculate, 
	}
}
Guicontrol, text, edithistory, %Answerall%
ControlSend Edit2, ^{End}, ahk_group calculatorgroup
Return

removetooltip:
settimer, removetooltip, off
tooltip
return

removetraytip:
settimer, removetraytip, off
traytip
return

guiclose:
exitapp

helpmenuabout:
MsgBox, 0, My Calculator, Version 1.04.02`n`nBuilt 01.12.08`n`nby Mike Marrs
Return

helpmenushortcuts:
Gui, 2:Font, s16 Bold, Arial
Gui, 2:Add, Text, +xm w900 Center,Shortcuts
Gui, 2:Font, s8 Norm, Arial
Gui, 2:Add, Text, +xm +ys+20, Print:			Ctrl + P
Gui, 2:Add, Text, +xm +ys+40, Save History:		Ctrl + S
Gui, 2:Add, Text, +xm +ys+60, Save As:		Ctrl + Shift + S
Gui, 2:Add, Text, +xm +ys+80, Exit:			Alt + F4
Gui, 2:Add, Text, +xm +ys+100,
Gui, 2:Add, Text, +xm +ys+120, Copy History:		Ctrl + C
Gui, 2:Add, Text, +xm +ys+140, Clear History:		Ctrl + Backspace
Gui, 2:Add, Text, +xm +ys+160, Undo Clear History:	Ctrl + Z
Gui, 2:Font, s16 Bold, Arial
Gui, 2:Add, Text, +xm w900 center,Functionality
Gui, 2:Font, s8 Bold, Arial
Gui, 2:Add, Text, +xm +ys+210 w900 center,Rounding:
Gui, 2:Font, s8 Norm, Arial
Gui, 2:Add, Text, +xm +ys+240 w900, Set Decimal Places: Use this option to automatically round answers to a set number of digits. After enabling the Set Decimal Places option, all significant digits are actually maintained throughout all calculations. To view these digits select Stop Rounding from the options menu then add '0' to your answer.
Gui, 2:Font, s8 Bold, Arial
Gui, 2:Add, Text, +xm +ys+280 w900 center, General Math
Gui, 2:Font, s8 Norm, Arial
Gui, 2:Add, Text, +xm +ys+295 w900, Note: Basic Operators: Add: +, Subtract: -, Multiply: *, Divide: /, Exponent **`n`nAbs(Number): Returns the absolute value of Number. The return value is the same type as Number (integer or floating point).`n`nCeil(Number): Returns Number rounded up to the nearest integer (without any .00 suffix). For example, Ceil(1.2) is 2 and Ceil(-1.2) is -1.Exp(N): Returns e (which is approximately 2.71828182845905) raised to the Nth power. N may be negative and may contain a decimal point. To raise numbers other than e to a power, use the ** operator.`n`nFloor(Number): Returns Number rounded down to the nearest integer (without any .00 suffix). For example, Floor(1.2) is 1 and Floor(-1.2) is -2.`n`nLog(Number): Returns the logarithm (base 10) of Number. The result is formatted as floating point. If Number is negative, an empty string is returned.`n`nLn(Number): Returns the natural logarithm (base e) of Number. The result is formatted as floating point. If Number is negative, an empty string is returned.`n`nMod(Dividend, Divisor): Modulo. Returns the remainder when Dividend is divided by Divisor. The sign of the result is always the same as the sign of the first parameter. For example, both mod(5, 3)and mod(5, -3) yield 2, but mod(-5, 3) and mod(-5, -3) yield -2. If either input is a floating point number, the result is also a floating point number. For example, mod(5.0, 3) is 2.0 and mod(5, 3.5) is 1.5. If the second parameter is zero, the function yields a blank result (empty string).`n`nRound(Number [, N]): If N is omitted or 0, Number is rounded to the nearest integer. If N is positive number, Number is rounded to N decimal places. If N is negative, Number is rounded by N digits to the leftof the decimal point. For example, Round(345, -1) is 350 and Round (345, -2) is 300. Unlike Transform Round, the result has no .000 suffix whenever N is omitted or less than 1. In v1.0.44.01+, a value of N greater than zero displays exactly N decimal places rather than obeying SetFormat. To avoid this, perform another math operation on Round()'s return value; for example: Round(3.333, 1)+0.`n`nSqrt(Number): Returns the square root of Number. The result is formatted as floating point. If Number is negative, the function yields a blank result (empty string).
Gui, 2:Font, s8 Bold, Arial
Gui, 2:Add, Text, +xm Section w900 center, Trigonometry
Gui, 2:Font, s8 Norm, Arial
Gui, 2:Add, Text, +xm +ys+15 w900, Sin(Number) | Cos(Number) | Tan(Number) : Returns the trigonometric sine|cosine|tangent of Number. Number must be expressed in radians.`n`nASin(Number): Returns the arcsine (the number whose sine is Number) in radians. If Number is less than -1 or greater than 1, the function yields a blank result (empty string).`n`nACos(Number): Returns the arccosine (the number whose cosine is Number) in radians. If Number is less than -1 or greater than 1, the function yields a blank result (empty string).`n`nATan(Number): Returns the arctangent (the number whose tangent is Number) in radians.`n`nNote: To convert a radians value to degrees, multiply it by 180/pi (approximately 57.29578). To convert a degrees value to radians, multiply it by pi/180 (approximately 0.01745329252). The value of pi (approximately 3.141592653589793) is 4 times the arctangent of 1.`n
Gui, 2:Font, s8 Bold, Arial
Gui, 2:Add, Text, +xm w900 center, Press 'Esc' to close this window
Gui, 2:Color, White
Gui, 2:Show, , Help File
;Gui, 2:+LastFound +0x200000 +Resize +0x2000000
;Winset, Style, +0x200000, Help File
;OnMessage(0x115, "OnScroll") ; WM_VSCROLL
;OnMessage(0x114, "OnScroll") ; WM_HSCROLL
;Gui, 2:; WS_VSCROLL | WS_HSCROLL
;Gui, 2:+Resize
Return

2GuiEscape:
Gui, 1:-Disabled
Gui Destroy
Return

Guisize:
settitlematchmode, 3
;Guicontrol, Move, edithistory, w100 h100
Anchor("editdescription", "w")
Anchor("edithistory", "wh")
Anchor("editcalculate", "yw")
Anchor("history", "yw")
Anchor("input", "yw")
controlgetpos, , yvar,,,edit2,Mike's Calculator
controlmove, static2,, yvar - 20,,,Mike's Calculator
Return

Anchor(i, a = "", r = false) {						; Thanks to Titan and all who contributed to the anchor function!
	static c, cs = 12, cx = 255, cl = 0, g, gs = 8, z = 0, k = 0xffff, gx = 1
	If z = 0
		VarSetCapacity(g, gs * 99, 0), VarSetCapacity(c, cs * cx, 0), z := true
	If a =
	{
		StringLeft, gn, i, 2
		If gn contains :
		{
			StringTrimRight, gn, gn, 1
			t = 2
		}
		StringTrimLeft, i, i, t ? t : 3
		If gn is not digit
			gn := gx
	}
	Else gn := A_Gui
	If i is not xdigit
	{
		GuiControlGet, t, Hwnd, %i%
		If ErrorLevel = 0
			i := t
		Else ControlGet, i, Hwnd, , %i%
	}
	gb := (gn - 1) * gs
	Loop, %cx%
		If (NumGet(c, cb := cs * (A_Index - 1)) == i) {
			If a =
			{
				cf = 1
				Break
			}
			Else gx := A_Gui
			d := NumGet(g, gb), gw := A_GuiWidth - (d >> 16 & k), gh := A_GuiHeight - (d & k), as := 1
				, dx := NumGet(c, cb + 4, "Short"), dy := NumGet(c, cb + 6, "Short")
				, dw := NumGet(c, cb + 8, "Short"), dh := NumGet(c, cb + 10, "Short")
			Loop, Parse, a, xywh
				If A_Index > 1
					av := SubStr(a, as, 1), as += 1 + StrLen(A_LoopField)
						, d%av% += (InStr("yh", av) ? gh : gw) * (A_LoopField + 0 ? A_LoopField : 1)
			DllCall("SetWindowPos", "UInt", i, "Int", 0, "Int", dx, "Int", dy, "Int", dw, "Int", dh, "Int", 4)
			If r != 0
				DllCall("RedrawWindow", "UInt", i, "UInt", 0, "UInt", 0, "UInt", 0x0101) ; RDW_UPDATENOW | RDW_INVALIDATE
			Return
		}
	If cf != 1
		cb := cl, cl += cs
	If (!NumGet(g, gb)) {
		Gui, +LastFound
		WinGetPos, , , , gh
		VarSetCapacity(pwi, 68, 0), DllCall("GetWindowInfo", "UInt", WinExist(), "UInt", &pwi)
			, NumPut(((bx := NumGet(pwi, 48)) << 16 | by := gh - A_GuiHeight - NumGet(pwi, 52)), g, gb + 4)
			, NumPut(A_GuiWidth << 16 | A_GuiHeight, g, gb)
	}
	Else d := NumGet(g, gb + 4), bx := d >> 16, by := d & k
	ControlGetPos, dx, dy, dw, dh, , ahk_id %i%
	If cf = 1
	{
		Gui, +LastFound
		WinGetPos, , , gw, gh
		d := NumGet(g, gb), dw -= gw - bx * 2 - (d >> 16), dh -= gh - by - bx - (d & k)
	}
	NumPut(i, c, cb), NumPut(dx - bx, c, cb + 4, "Short"), NumPut(dy - by, c, cb + 6, "Short")
		, NumPut(dw, c, cb + 8, "Short"), NumPut(dh, c, cb + 10, "Short")
	Return, true
}

Print( edit ){						;Thanks to Fry for this and for majkinetor for surprising him with a better version!
	FileAppend %edit%, Calculations.txt
	Run, notepad /p calculations.txt
	Sleep, 1500
	FileDelete, calculations.txt
}

My perfect script:
Gui, Font, s14 cTan norm, Arial
Gui, add, UpDown, vMyUpDown
Gui, Show, Autosize, Arial
If Arial not contains % large * 2
Gui, Destroy

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The main point of the thread was to run the calculator on machines w/o AHK. The last version is only 40 lines, and uses no temporary files. If you need a more complete solution, look here. It uses Lexikos’ low level AHK functions for dynamically execute AHK code. It also runs without temp files and on machines, where AHK is not installed.

If you use temp files, 10 lines are enough for a simple calculator.

Still, your version has a nice GUI, and menus. Well done!

wep
  • Members
  • 2 posts
  • Last active: Jul 10 2008 02:23 AM
  • Joined: 01 Jul 2008

It can be used to build a calculator ...


So I used it. :D Thanks.
Try my WinMouseTipCalculator for arithmetic calculations.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;-= WinKey Enhancement Project =-;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wep.dcmembers.com;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; • WinMouseTipCalc • ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;Do input arithmetic expression with +,-,* and / math signs holding <Win>-key;;;
;;; <Win>+<NumPad numbers> inputs digits                                       ;;;
;;; <Win+<Backspace> • clears last digit in the input                          ;;;
;;; <Win>+<NumpadEnter> • gives result                                         ;;;
;;; <Win>+<Enter> • also copies result to the clipboard                        ;;;
;;; <Win+<Esc> • clear expression (input)                                      ;;;
;;; <Win+<Ins> • inserts result to active window into the text cursor          ;;;
;;; <Win>+<=> • copies selected expression and gives result instead of it      ;;;
;;; <Win>+<End> • copies selected expression then inserts full result equation ;;;
;;; <Win>+<Up> & <Win>+<Down> • set roundoff for the result                    ;;;
;;; <Win>+<PageUp> • add "$"-sign at the start position of result              ;;;
;;; <Win>+<PageDown> • add "%"-sign at the end position of result              ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

TRAYMENU:
Menu,Tray,NoStandard
Menu,Tray,DeleteAll
Menu,Tray,Add,E&xit,EXIT

A=
p=6
SetFormat, Float, 0.64

Gosub, TIP

#PgUp::
A:="$" . A
ToolTip, %A%
return

#PgDn::
A:=A . "%"
ToolTip, %A%
return

#1::
p=1
Gosub, TIP
return

#2::
p=2
Gosub, TIP
return

#3::
p=3
Gosub, TIP
return

#4::
p=4
Gosub, TIP
return

#5::
p=5
Gosub, TIP
return

#6::
p=6
Gosub, TIP
return

#7::
p=7
Gosub, TIP
return

#8::
p=8
Gosub, TIP
return

#9::
p=9
Gosub, TIP
return

#0::
p=0
Gosub, TIP
return


#Numpad0::
A=%A%0
ToolTip, %A%
return

#Numpad1::
A=%A%1
ToolTip, %A%
return

#Numpad2::
A=%A%2
ToolTip, %A%
return

#Numpad3::
A=%A%3
ToolTip, %A%
return

#Numpad4::
A=%A%4
ToolTip, %A%
return

#Numpad5::
A=%A%5
ToolTip, %A%
return

#Numpad6::
A=%A%6
ToolTip, %A%
return

#Numpad7::
A=%A%7
ToolTip, %A%
return

#Numpad8::
A=%A%8
ToolTip, %A%
return

#Numpad9::
A=%A%9
ToolTip, %A%
return

#BackSpace::
A:= SubStr(A, 1 , (StrLen(A)-1))
ToolTip, %A%
return

#NumpadDot::
A=%A%.
ToolTip, %A%
return

#NumpadDiv::
A=%A%/
ToolTip, %A%
return

#NumpadMult::
A=%A%*
ToolTip, %A%
return

#NumpadAdd::
A=%A%+
ToolTip, %A%
return

#NumpadSub::
A=%A%-
ToolTip, %A%
return

#NumpadEnter::
if SubStr(A, 1, 1)="-" OR SubStr(A, 1, 1)="+"
A=0%A%
A:= Eval(A)
A:= A=Round(A, 0) ? Round(A, 0) : Round(A, p)
ToolTip, %A%
Return

#Enter::
if SubStr(A, 1, 1)="-" OR SubStr(A, 1, 1)="+"
A=0%A%
A:= Eval(A)
A:= A=Round(A, 0) ? Round(A, 0) : Round(A, p)
clipboard:=A
clipboard = %clipboard%
ToolTip, %A%
Return

#Esc::
A=
ToolTip, %A%
return

#Ins::
result:= A_PriorHotkey = "#Ins" ? "=" A : A
result = %result%
SendRaw, %result%
if (A_PriorHotkey = "#Ins")
Send {Enter}
if SubStr(A, 1, 1)="-" OR SubStr(A, 1, 1)="+"
A=0%A%
A:= Eval(A)
A:= A=Round(A, 0) ? Round(A, 0) : Round(A, p)
ToolTip, %A%
Return

#End::
Send ^{Ins}
clipboard = %clipboard%
A:=clipboard
if SubStr(A, 1, 1)="-" OR SubStr(A, 1, 1)="+"
A=0%A%
A:= Eval(A)
A:= A=Round(A, 0) ? Round(A, 0) : Round(A, p)
ToolTip, %A%
SendRaw, %clipboard%=%A%
Return

#=::
Send ^{Ins}
clipboard = %clipboard%
A:=clipboard
if SubStr(A, 1, 1)="-" OR SubStr(A, 1, 1)="+"
A=0%A%
A:= Eval(A)
A:= A=Round(A, 0) ? Round(A, 0) : Round(A, p)
ToolTip, %A%
Return

#Down::
if (p>=1)
{
p-=1
}
Gosub, TIP
Return

#Up::
if (p<=63)
{
p+=1
}
Gosub, TIP
Return

;;;;;;;;;; script posted on the Autohotkey forum by Laszlo ;;;;;;;;;;
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)
}
;;;;;;;;;;;;;;;;;;;;;;;;;end of Laszlo's code ;;;;;;;;;;;;;;;;;;;;;;;;


EXIT:
ExitApp

TIP:
TrayTip , WinMouseTipCalc, Result rounded to %p% decimal places, 2
Menu,Tray,Tip,WinMouseTipCalc`nResult rounded to %p% decimal places.
Return


HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Many thanks for this wonderful function Laszo. It is very useful.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I am glad you found it useful. You could simplify the handling of unary + and – a little more. Give it a try! It is a good exercise...

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
I use it as a quick calculator in my script, works perfect.

It is exactly what I am been looking for, especially brackets "(2+2)*2" are very useful for more complex calculation.

Very good job!