This project came from a need for a quick calculator application that is not as basic as a typical 1-line calculator, but not as complex as a full Scilab or Matlab console. This would be an application where it is possible to deal with long multi-step formulas concisely, and which would not require long start-up times or large installation files or a lot of screen space. There should be minimal visual clutter and calculations should be entered step by step to match the mental layout of the formula, allowing to enter and store numeric values so they do not have to be held in mind for long. The syntax itself should be terse so mental effort is not wasted on unnecessary keystrokes or editing of entries. Many of these ideals are seen in the APL-like (tacit) and stack-based programming languages, from which the syntax used here is inspired. Unlike APL / Dyalog, the MCalc input does not use anything other than standard ASCII characters, so it can run in a console window. The use of a console is also inspired by Zoesoft's Console Calculator, a great program with similar goals which also gave me incentive to implement unit conversion for MCalc.
The overall concept is as follows: the calculator is equipped with a stack, where all values are stored. Entries are added to the stack one line at a time, and a line contains one numeric value which is either manually entered or the result of a computation. Values must be entered in the stack before a computation is called, so instead of "5+7" one enters [5] [7] [+](12) resulting in 3 lines added to the stack where the 3rd line is the result of the summation. The most recent entries in the stack are referenced implicitly, and other than most recent ones may be referenced usually by a single character. This may seem clunky, however it has some unique advantages:
A screenshot of the MCalc console window with entries to solve the equation x^2+8*x+15=0 using the quadratic formula (-B+-sqrt(B^2-4*A*C))/(2*A). The first 3 lines are the coefficients (A,B,C)=(1,8,15) and the last 2 lines are the results x=(-3,-5). The leftmost column is the absolute reference letter for each entry, the next column is the typed text, followed by the numeric result and the comment. Each entry is followed by comments for clarity and the comments show up twice (on the left as typed, and on the right as parsed).
In principle, many of these things could be done in a spreadsheet application such as Excel, which also allows referencing of other cells. The difference here is a terse syntax and a lean layout requiring minimal typing or editing of previously typed values (and no need to use a mouse), an implicit referencing system which can minimize or eliminate the need to type references, and the hypothetical evaluation command which can effectively make a function out of anything entered in the stack with no foresight required as to which variables are expected to be useful later on. This is in addition to MCalc being fast to load and having low resource requirements (though it is implemented in the .NET framework (for ease of programming in C#) and not bare bones C++ speed). To extend the calculator's functionality, custom commands and unit conversions may be defined using a simple text editor.
When examples are provided in this guide, text to type in MCalc appears inside square brackets [] and numeric results appear in parentheses (). Both square brackets and parentheses are not used inside MCalc. For instance to evalue 3+7 one enters [3](3) [7](7) [+](10), with the Enter key pressed after each bracketed entry to evaluate the input string and add it to the stack. Since numeric entries evaluate as themselves, this will be shortened as [3] [7] [+](10).
After starting MCalc, a console window opens with text entry on the first line. Use the keyboard to enter an input, then press enter to add the input to the stack. The stack starts at the visual top of the console and grows downwards. Once added to the stack, an entry cannot be modified, and it can be referenced by future commands. The entry may be a numeric value (such as [1.23e5]) or a command (such as [+]), and these may be followed by a space and comment (such as [1.23 m radius] or [+ adding sides for circumference]). An entry may also be a comment by itself (such as [ next equation]), which will show up as a line in the stack in the same manner as a numeric value. Since the stack may be referenced at any point, there are no commands to swap, rotate, or delete (other than the final) stack elements. Each command can only add new values to the stack and cannot change previous ones. Commands can only reference values already in the stack. Values cannot be entered on the same line as a command, and only one command may be entered per line. Commands may explicitly reference entries in the stack, or in the absence of that, will implicitly reference the immediately preceding entries. Absolute address references are capital letters A-Z. The entries precede a command statement, so two numbers stored as Z and Y are summed by the expression [ZY+]. The summation command takes 2 or more arguments, so [+] will sum the previous 2 entries in the stack, [Z+] will sum the previous entry with Z, [ZY+] will sum Z with Y, [ZYX+] will sum Z with Y with X.
A number causes the entered value to be parsed and stored in the stack (as 64-bit floating point), with an optional comment. Examples: [7] [1.9 L] [.5] [-2] [3e6] [+5E+5]
Items in the stack may be referenced by an absolute address which starts at Z and on down to C,B,AZ,AY..AB,AAZ,AAY (each 'A' serves as an extending character, as otherwise the reference is assumed to end with 1 character). This address does not change as the stack grows, and for convenience is shown at the beginning of each line. Items may also be referenced by their relative position to the current line. These references are digits 1-9 where 1 is the previous item, 2 is the previous-1 item, and on to 9,01,02..09,001,002 (each '0' again serves as an extending character). Examples: [21+] sums the previous 2 entries and is equivalent to [+] which is implicitly completed. [YZ/] divides Y by Z (that is, Y/Z) while [ZY/] is the opposite (Z/Y), and may be read as transposing the division sign left one character to place it between the references. [/] is equivalent to [21/] and evaluates as the 2nd to last entry divided by the last entry (2/1), while [2/] expands into [12/] and evaluates as (1/2) because implicit references are prepended to the expression. Similarly, [ZY/] expands as (Z/Y) while [Z/] expands as [1Z/] which is (1/Z).
Each command has a minimum and a maximum number of arguments. It will take up to the minimum number of arguments from the end of the stack implicitly, while supplying more arguments than the minimum requires all of them to be entered individually. Special reference characters simplify this task.
It is possible to produce multiple outputs by using iterators. An iterator causes the same command to be evaluated multiple times with different arguments. An iterator contains a list of references in curly brackets [{XYZ}], and executes the command once for each reference from left to right. For instance to evaluate 7-1 and 7-5 in one line, [1] [5] [7] [{32}-](6)(2). An iterator takes place of one argument for a command. Multiple iterators may be used if they all contain the same number of references, and they will be scanned through concurrently. For instance to evaluate 1-5 and 5-7 in one line, [1] [5] [7] [{32}{21}-](-4)(-2). There are also special reference symbols for iterators.
A command causes an action to be carried out and the result(s) written to the bottom of the stack. Commands will get data from the most recent entries in the stack unless preceded by manually specified stack references. Numeric values for the command may not be entered on the same line as a command; rather the values should first be entered into the stack and then be referenced when entering the command. Command words start by a sequence of special characters (such as [+] or [-']) or lower case letters (such as [sqrt] or [s]) and are preceded by a list of references and followed by an optional comment (after a space). Multiple arguments are passed by concatenating references, where different valid characters from above may be mixed, such as [XYZ] or [#$] or [5RV]. The order of arguments, read from left to right, is passed to the commands, so [VR-] is different from [RV-].
Some commands produce multiple numeric outputs. In this case, the command is typed once and appends more than one entry to the stack, with each entry carrying one of the numeric outputs. The multiple outputs are from then on treated as independent entries, following the same referencing rules as if they were added one at a time. For example the [/%] command returns an integer divisor and a remainder, and may be used as [23] [5] [/%](4)(3).
Command | Arguments | Description |
---|---|---|
- | 2 | Subtraction |
-' | 2 | Subtraction Y-Z |
-- | 1 | Subtract 1 |
% | 2 | Modulo |
%' | 2 | Modulo Y%Z |
%i | 2 | IEEE Remainder |
%i' | 2 | IEEE Remainder Y%Z |
* | 2+ | Multiplication |
/ | 2 | Division |
/' | 2 | Division Y/Z |
-/ | 2 | Difference as average ratio |
-/' | 2 | Difference as average ratio reversed args |
\ | 1 | Inversion 1/Z |
^ | 2 | Exponentiation |
^' | 2 | Exponentiation Y^Z |
| | 2+ | Parallel combination 1/(1/x+1/y) |
~ | 1 | Negation |
+ | 2+ | Addition |
++ | 1 | Add 1 |
< | 1 | /2 |
-< | 2 | Half-difference |
-<' | 2 | Half-difference reversed args |
<< | 1 | /4 |
<<< | 1 | /8 |
<<<< | 1 | /16 |
> | 1 | *2 |
>> | 1 | *4 |
>>> | 1 | *8 |
>>>> | 1 | *16 |
abs | 1 | Absolute value |
abv | 4 | Counter-clockwise angle between 2 2D vectors |
ac | 1 | Arccosine |
acd | 1 | Arccosine as degrees |
ach | 1 | Hyperbolic arccosine |
as | 1 | Arcsine |
asd | 1 | Arcsine as degrees |
ash | 1 | Hyperbolic arcsine |
at | 1-2 | Arctangent |
at' | 2 | Arctangent (args reversed) |
at2 | 2 | Arctangent |
at2' | 2 | Arctangent (args reversed) |
atd | 1-2 | Arctangent as degrees |
atd' | 2 | Arctangent as degrees (args reversed) |
atd2 | 2 | Arctangent as degrees |
atd2' | 2 | Arctangent as degrees (args reversed) |
ath | 1 | Hyperbolic arctangent |
c | 1 | Cosine |
cb | 1 | Cube |
cbrt | 1 | Cube root |
cd | 1 | Cosine of degrees |
ceil | 1 | Next higher integer |
ch | 1 | Hyperbolic cosine |
cp | 4 | Cross product (determinant) of 2 2D vectors |
dp | 4 | Dot product of 2 2D vectors |
dtor | 1 | Degrees to radians |
e | 0 | e constant |
exp | 1 | e^Z |
exp10 | 1 | 10^Z |
exp2 | 1 | 2^Z |
floor | 1 | Next lower integer |
fpart | 1 | Fractional part |
ftor | 1 | Frequency (1/s) to angular frequency (rad/s) |
gmean | 2+ | Geometric mean |
hmean | 2+ | Harmonic mean |
ipart | 1 | Integer part |
ln | 1 | Log base e |
log | 1-2 | Log base 10 (1 arg) or custom |
log' | 2 | Log custom base (args reversed) |
log10 | 1 | Log base 10 |
log2 | 1 | Log base 2 |
logb | 2 | Log custom base |
logb' | 2 | Log custom base (args reversed) |
mdist | 2+ | Manhattan distance |
mean | 2+ | Arithmetic mean |
norm | 2+ | Norm (Euclidean distance) |
pi | 0 | Pi constant |
round | 1 | Round to nearest integer |
round1 | 1 | Round to 1 significant digit |
round2 | 1 | Round to 2 significant digits |
round3 | 1 | Round to 3 significant digits |
round4 | 1 | Round to 4 significant digits |
rt | 2 | Zth root Y |
rt' | 2 | Yth root Z |
rtod | 1 | Radians to degrees |
rtof | 1 | Angular frequency (rad/s) to frequency (1/s) |
s | 1 | Sine |
sd | 1 | Sine of degrees |
sh | 1 | Hyperbolic sine |
sq | 1 | Square Z*Z |
sqrt | 1 | Square root |
sstd | 2+ | Standard deviation 1/(N-1) |
std | 2+ | Standard deviation 1/N |
t | 1 | Tangent |
tau | 0 | Tau constant (2*Pi) |
td | 1 | Tangent of degrees |
th | 1 | Hyperbolic tangent |
Command | Arguments | Outputs | Description |
---|---|---|---|
/% | 2 | 2 | Division with remainder |
/%' | 2 | 2 | Division with remainder reversed args |
/%i | 2 | 2 | Division with IEEE remainder |
/%i' | 2 | 2 | Division with IEEE remainder reversed args |
+- | 2 | 2 | Sum and difference |
+-' | 2 | 2 | Sum and difference reversed args |
-+ | 2 | 2 | Difference and sum |
-+' | 2 | 2 | Difference and sum reversed args |
+-< | 2 | 2 | Half of Sum and difference |
+-<' | 2 | 2 | Half of Sum and difference reversed args |
-+< | 2 | 2 | Half of Difference and sum |
-+<' | 2 | 2 | Half of Difference and sum reversed args |
box | 2+ | 2 | Enclosing range A+-B |
linf | 4 | 2 | Fit a line y=a*x+b to 2 2D points |
msstd | 2+ | 2 | Mean and standard deviation 1/(N-1) |
mstd | 2+ | 2 | Mean and standard deviation 1/N |
parts | 1 | 2 | Integer and fractional parts |
ptor | 2 | 2 | Polar coordinates to rectangular coordinates, or (mag,phs) to (re,im) |
rot | 3 | 2 | Rotate 2D vector counter-clockwise |
rotd | 3 | 2 | Rotate 2D vector by degrees counter-clockwise |
rtop | 2 | 2 | Rectangular coordinates to polar coordinates, or (re,im) to (mag,phs) |
sc | 1 | 2 | Sine and cosine |
scd | 1 | 2 | Sine and cosine of degrees |
vp | 4 | 2 | Dot and cross products of 2 2D vectors (cosine and sine of angles between vectors) |
[@] re-evaluates the stack with modified input values. Unlike all other commands which only have arguments to the left of the command word, this command requires at least 2 arguments to the right of the @ symbol, and the number of arguments determines how many entries are hypothetically modified in the stack. The last entry on the right is what is evaluated, while all preceding entries are modified as filled from references prepended to the command in the standard manner. For instance:
Example: [3] [8] [+](11) [9] [@4!](17) evaluates 3+8 and also 9+8. Repeated @ commands are evaluated as chained, and changes accumulate. The referenced command runs at its original stack location (relative references still point to the same values as originally). A referenced line within a multiline result will return only the specified line (use an iterator on the right-most output argument to include all lines).
[conv] may be used to convert measurement units. It is special because it parses the entry comment to determine the conversion. The 1-argument command is followed by a space, the original unit, another space, the desired unit, and optionally another space and a remaining comment. Example: [2] [conv m^2 ft^2](21.528) converts 2 square meters into square feet. Input and output units should be entered as a string without spaces containing letters, exponents, and multiplication or division signs such as m/s^2 or N*m. The division symbol applies only to the immediate next unit, so m/s^2*kg is equivalent to m*kg/s^2 or m*kg*s^-2.
Command | Arguments | Outputs | Description |
---|---|---|---|
hms | 1 | 3 | Convert seconds to hours:minutes:seconds |
pr | 3 | 1 | Progress ratio, input A,B,C, return 0 when C=A, 1 when C=B |
quad | 3 | 2 | Quadratic equation solver, input A,B,C, return x such that A*x^2+B*x+C=0 |
rc | 2 | 1 | RC Filter Calculator, input R and C, return Fc |
Custom units may be defined. A non-comprehensive list of units is specified in a text file "units_list.txt" in the MCalc executable directory. The file is not parsed until [conv] is called for the first time in a session. Use [reload] when actively modifying the units list to avoid restarting MCalc. The units must be defined in groups relating to base units. A base unit is defined as equal to one of itself, such as "m=1 m" to specify the meter as a base unit. Other units are defined with reference to base units, such as "ft=0.3048 m", where an implied "1" is to the left of the expression, that is 1 ft = 0.3048 m. Note that all prefixes must be defined explicitly, such as "cm=0.01 m" or "km=1000 m". Each line in the "units_list.txt" file may start with a space to contain a comment, or with a letter (a-z or A-Z) to define a unit name (case-sensitive). The unit name must not have any powers (exponents) attached. Exponents are calculated automatically, so defining "m" will carry over to "m^2" and others. The unit name must be followed by "=" and a numeric value (corresponding to 1 of the new unit), then one space " " and a defining unit name. For instance to represent 1 week is 7 days, the entry would be "week=7 day". Defining units must reduce to base units for the conversion to be successful. Look through the existing entries in the file to gain a better understanding.
Custom external commands may be defined. They operate in the same stack-based manner as the console, and constitute an isolated "sandbox stack" where inputs are read in from the main stack, the computation is carried out, and results are taken back into the main stack. Commands should be stored as *.txt files in ./extcmds directory relative to the MCalc executable. Valid names must start with a lowercase letter a-z and may contain digits 0-9 later, no other characters are allowed. The files are enumerated on MCalc startup but not parsed until individual commands are called. Use [reload] when actively modifying the external commands to avoid restarting MCalc. Look through the included examples in the extcmds folder to gain a better understanding. An external command must have a fixed number of inputs and outputs. Some features are not available in external commands:
External commands have their own stack which starts with Z as the first line. The first line comment may be used to provide a description of the command. Multiline commands called from the file must be followed by a matching number of blank or comment lines " :" which will be filled with return values when the script runs and may be referenced from that point on. Inputs are read with the command [in], and outputs are written with the command [out]. The entire script is parsed to find the number of inputs, and these inputs are read from the main stack such that the last [in] corresponds to the last value on the stack. Similarly the first [out] is the first value that will be added to the main stack. The [in] command does not support any arguments and reads in exactly one value at the line it appears. The [out] command may have arguments, in the absence of arguments it will return the previous entry in the external stack and it duplicates the last value as its own output. Example:
convert rectangular to polar coordinates (with comments) using expression syntax this is R=norm(X,Y);theta=atan2(Y,X);return (R,theta); in Read X in Read Y norm Find R out Output R WXat Find theta out Output theta
The C# code files for the project may be downloaded here. The compiled executable (for Windows .NET framework 7.0) with units list and external command examples may be downloaded here.
The MCalc console calculator has successfully met the design criteria. While it has taken me a few days to become used to the line-by-line data entry method, I now miss having that ability when I must use a regular calculator app on a computer without MCalc installed. The unit conversions are functional and new unit definitions can be easily added. The external commands provide a way to implement "convenience functions" which are not mathematically sophisticated but which save time and mental effort when doing a calculation, such as the included [rc] to solve for the RC filter cutoff frequency 1/(2*pi*R*C). The ability to have multiple outputs further enhances the convenience aspect. This allows MCalc to fill a niche not well covered by other computational software. Overall I would consider MCalc a success, and I use it regularly on every computer where I have an account.