November 14, 2008

Back-of-the-napkin calculations with Frink

Author: Ben Martin

Linux users have a myriad of calculators and unit conversion tools at their disposal. To set itself apart, Frink aims to track units for you and give you a way to quickly perform little conversions and real-world calculations without burdening you with needless details.

openSUSE 11 users can install Frink with 1-Click, but there are no packages in the Ubuntu Intrepid or Fedora 9 repositories. I'll use the frink.jar file instead of packages. The frink.jar file contains Java class files, and you can run Frink directly using the jar if you have a Java Runtime Environment installed.

You can run Frink either as a console application or using a GUI created with either the AWT or Swing Java GUI toolkits. The main advantage to running the GUI tools is that they allow you to edit your last calculation with the up arrow. By default the console version of Frink does not handle the up arrow, but you can work around that by invoking the console Frink through rlwrap, which wraps the console with readline and makes the up arrow work properly, among other things. Packages for rlwrap are in the standard Ubuntu Intrepid and Fedora 9 repositories, and there is a 1-Click for openSUSE 11 users. Unless you are planning to use the graphics programming features of Frink I would recommend running it in a terminal through rlwrap as shown below.

$ cat ~/bin/frink
rlwrap java -cp /FromWeb/frink.jar frink.parser.Frink "$@"

$ ~/bin/frink
Frink - Copyright 2000-2008 Alan Eliasen,

A Windows binary is available on the Web site, but no Linux ones. Although the Frink Web site mentions that compiling a native executable with gcj takes more than an hour, I found that I could do it in much less time. Stripping the debug information from the binary results in an executable that is about 2.5MB. I found a noticeable difference in startup time between using the jar with the OpenJDK Runtime Environment (build 1.6.0-b09), which took 1.8 seconds, and the gcj (4.3.0 20080428) compiled version, which took 0.4 seconds for the same startup and simple calculation.

$ gcj -O3 -fomit-frame-pointer --main=frink.parser.Frink -o frinkx.exe frink.jar
$ ls -lh frinkx.exe
-rwxrwxr-x 1 ben ben 3.4M 2008-11-04 11:52 frinkx.exe*
$ strip frinkx.exe
$ ls -lh frinkx.exe
-rwxrwxr-x 1 ben ben 2.5M 2008-11-04 11:55 frinkx.exe
$ ldd frinkx.exe => (0x00007fff652e3000) => /lib64/ (0x0000003a52600000) => /usr/lib64/ (0x00000035fcc00000) => /lib64/ (0x000000000087a000) => /lib64/ (0x0000000000aff000) => /lib64/ (0x0000000000d1a000) => /lib64/ (0x0000003a50600000) => /lib64/ (0x0000000000f23000) => /lib64/ (0x00000000059b6000)
/lib64/ (0x0000000000110000)

The command-line arguments to Frink are switches followed by the name of a file containing Frink code to load and execute. You can use -e to tell Frink that the last argument is an expression to evaluate instead of a file name. If you want to run multiple files in a single Frink invocation, use -f filename1 -f filename2 .... The -k option tells Frink to remain open after evaluating the files you have specified. You might find -k useful in combination with -f to have Frink load custom variables and functions from a startup file before it prompts you for calculations.

What does a Frink calculation look like? Suppose you are looking at external laptop batteries, and one company lists capacity in amp hours and another in watt hours. You can convert between the two capacity measures, as shown below. Because the utility provides is no frink> prompt I have made lines of input bold in the examples.

6.6 amp hours * 16 volts -> watt hours

6.6 amp hours * 16 volts
380160.0 m^2 s^-2 kg (energy)
105.6 watt hours
380160.0 m^2 s^-2 kg (energy)

6.6 amp hours * 16 volts -> watts
Conformance error
Left side is: 380160.0 m^2 s^-2 kg (energy)
Right side is: 1 m^2 s^-3 kg (power)
Suggestion: multiply left side by frequency
or divide left side by time

Note that because I specified the units for the first calculation Frink does not report what the 105.6 pertains to. If you want the units in the response even when you explicitly supply the unit to convert to, enclose the unit specification in double quotes. The second two calculations cause Frink to report energy in your fundamental dimensions. These default to the metric-centric International System of Units (SI), with currencies in US dollars. Later on we'll see how you can change the default units that Frink uses to print values. In the final line I attempt a conversion that doesn't make sense and Frink suggests ways that I might make it work.

Frink can calculate in floating point, rationals, and intervals among other data types. In addition to the normal math operations that one expects from a calculator, it also provides specific operators for handling units. The conforms operator returns true if both of its operands are of the same dimension. The square (sq) and cubic (cu) words have the expected meaning -- 3 square feet is the same as 3 feet^2.

Frink provides support for variables, if/then/else logic, looping, arrays, dictionaries (associative arrays), sets, and functions. Functions can have default values for parameters and constrain parameters to be of certain units.

The tracking of units works well and can indicate when you either have not supplied the correct unit for a variable or when you have just remembered an equation incorrectly. To see how this work, consider the equations of motion, in particular the one stating that for a body initially at rest its final velocity is the square root of 2*a*s, where a is the acceleration and s is the distance travelled. Let's say we have the correct value and units for gravity, because it is a predefined value in Frink, but forget to supply the units for the distance traveled, as shown in the first example. Because the variable s does not have a unit, things start getting funny at the initial calculation, which should be something that can be expressed as specific energy but still shows up as an acceleration. Of course, when you take the square root, you do not get a velocity because the units were not correct.

196133/20000 (exactly 9.80665) m s^-2 (acceleration)

5880.0 m s^-2 (acceleration)
76.68115805072325 m^(1/2) s^-1 (unknown unit type)

If instead you express the distance traveled as a given number of meters, as shown below, then everything works out as expected. You can just as easily input the distance as another unit and things still work as expected.

s=300 meters
300 m (length)
5880.0 m^2 s^-2 (specific_energy)
76.68115805072325 m s^-1 (velocity)

1143/250 (exactly 4.572) m (length)
89.6112 m^2 s^-2 (specific_energy)
9.466319242451101 m s^-1 (velocity)

Of course the Earth's gravity is not always the same. You might be wondering how difficult it would be to use intervals to express a range of gravity that we might be interested in. In the example below, the first line declares the gint variable as a gravity interval. Notice that, apart from using gint instead of gravity, the rest of the equations do not need to change at all. When Frink needs to round numbers, it will make sure that the lower bound is rounded down and the upper bound upwards so that the resulting interval always includes the full range of possible values. Note that for some cases, such as overlapping intervals, you should use the explicit interval comparison operators to avoid ambiguity.

gint = new interval [ 9.797645, 9.80665 ] meters / (second^2)
[9.797645, 9.80665] m s^-2 (acceleration)
762/5 (exactly 152.4) m (length)
[89.58966588, 89.6720076] m^2 s^-2 (specific_energy)
[9.4651817668759, 9.4695304846651] m s^-1 (velocity)

Frink offers a collection of builtin functions that include common trigonometric, rounding, and base conversion operations, as well as functions for getting random information, performing log and exponent calculations, and some functions to help when performing modular arithmetic. There are also functions for number theory calculations and string manipulation.

You can change Frink's default International System of Units with the dimension :-> measure syntax. As shown below, when you use the Fahrenheit function to get a temperature, the result is printed in Kelvin by default. The dimension is shown in the parenthesis. With the middle command I change the default unit to use for the temperature dimension to Celsius. For the second use of the Fahrenheit function I use the shorthand invocation "F," which is built into Frink, and Frink reports the result in my desired units without my having to explicitly convert it. To find out what functions are available by default, take a look at units.txt from the frink.jar file.

310.15 K (temperature)

temperature :-> CelsiusF[98.6]

If you invoke Frink with the -f option to have it interpret a file that contains Frink commands and the -k option to hold Frink open once it has finished executing the file, you can easily set up your own preferences for how you want units reported. You have to use -kbecause your preferences file loaded with -f will only set up your defaults with dimension :-> unit, and you will want Frink to remain open after processing that file so you can input your calculations.


Some things that Frink can do -- notably currency conversions and language translations -- require an Internet connection to get the latest information. You might have to tell Frink about your Web proxy information to use these services.

Final words

Because Frink offers many GUI possibilities and is written in Java, it can be run on platforms from limited mobile devices through to multicore desktop monsters.

Frink is great as a back-of-the-napkin calculator that just happens to include the base of a computer language should you want to wrap up common calculations into functions for later use. You may not want to forgo Python or Perl to write more involved applications in Frink, but it's nice to know you can throw in a few loops and if/else logic to take advantage of user input and regular expressions without leaving Frink.


  • Desktop Software