EVAL - AN EVALUATOR FOR MATHEMATICAL EXPRESSIONS¶
The EVAL project contains the building blocks for a very flexible calculator. It knows many physical constants, uses strong typing for physical values (so you cannot accidentally add a speed to a time). The following four-line program demonstrates strong typing:
public static void main(final String... args)
{
System.out.println(new Eval().evaluate("100[m/s] * 10 [s]"));
}
The output of this program is
1000.00000 m
How does it know that the result should have the unit m (meter) attached? Eval uses our djunits project for the strong typing and this project contains the knowledge that a speed times a time yields a distance. It also ensures that you cannot add (or subtract) incompatible quantities. If you replace the * by a +, a RuntimeException will be thrown. The message of the exception describes in detail what was wrong
Cannot add 100.000000 m/s to 10.0000000 s because the types are incompatible at position 17
That number 17 is the position of the last closing ] in the expression. This is where the evaluator had to go before it could decide that the + operation cannot be performed.
The built-in unit parser only parses units expressed in the 7 basic SI units (rad, sr, kg, m, s, A, K, mol, cd). These can be written together with exponents and division signs to create any SI unit. If you need to parse other units (e.g. km/h, or mi/h), you must supply your own unit parser (that will take precedence over the built-in unit parser).
Expressions with plain (untyped) values cannot fail due to incompatible types. They can, of course, fail due to operands being invalid for the operation. In some cases this results in the special value NaN (Not-a-Number). E.g.
sqrt(-1)
yields the value NaN without throwing an exception.
Other cases, like division by zero do throw an exception.
The evaluator can deal with boolean values. These can be created with the TRUE() and FALSE() functions, but also with the binary comparison operators like <, <=, >, >=, ==, != and combined with boolean binary operators like && and || . You can even write conditional expressions using the ? : notation of java, C, and many other programming languages.
Operators¶
The following binary and ternary operators are available in Eval.
operators | binding strength and evaluation direction |
---|---|
^ | 8 right to left |
*, / | 7 left to right |
*, - | 6 left to right |
<, <=, >, >= | 5 left to right |
!=, == | 4 left to right |
&& | 3 left to right |
|| | 2 left to right |
? : | 1 left to right |
That last one is used in conditional expressions (see end of the preceding section). Expressions with multiple binary or ternary operators are evaluated by binding strength (highest first).
In addition to this, there are two unary operators:
-: the unary minus operator
!: the unary, boolean not operator
These have a higher binding strength than any binary or ternary operator.
Using pre-defined constants¶
A long list of physical and mathematical constants are pre-defined in Eval. Using these looks like calling a zero-argument function like:
new Eval.evaluate("2 * PI()")
Currently, all pre-defined constants have names that are entirely upper-case.
The complete list of pre-defined constants can be found in Eval.java, search for the F0 entries in builtinFunctions.
Using variables (user-defined constants)¶
Every decent desk calculator has a way to store and retrieve values. The retrieve operation of such a mechanism is embedded in Eval. The store operation is not. To allow Eval to find the value of a named variable, it has to be taught were to retrieve them. This is done by providing a RetrieveValue object. RetrieveValue is an object that implements a lookup method that takes a String argument and returns an Object. The Object can be a strongly typed djunits value, or a Double, a Boolean, an Integer, etc.
RetrieveValue values = new RetrieveValue() {
@Override
public Object lookup(final String name)
{
if (name.equals("myVariable"))
{
return 123.456;
}
return null;
}};
new Eval().setRetrieveValue(values).evaluate("myVariable+20");
This trivial example creates a RetrieveValue object that knows only one variable (myVariable). The lookup method should return null if the value is not known (which will result in a RuntimeException). The setRetrieveValue method returns the Eval object which is useful for method chaining; evidenced by the one-liner above that creates the Eval object, sets the RetrieveValue object and calls the evaluate method.
Actual implementations of RetrieveValue should probably use a HashMap<String, Object>. Variables (and function names) start with a letter, hash (#), at sign (@), or underscore character (_), followed by zero or more letters, digits, hashes, at signs and underscores.
Using built-in functions¶
The built-in functions have case-sensitive names. The usual trigonometric functions are implemented. E.g.
new Eval().evaluate("sin(1)");
will yield a double value of approximately 0.84147098 (angles are in Radians). A zero-argument function is followed by an empty pair of parentheses. This distinguishes it from a variable which is not followed by an opening parenthesis.
The 2-parameter atan2 function is also available.
The complete list of one- and two-argument functions can be found in Eval.java. Look for the F1 and F2 entries in builtinFunctions.
One very special built-in function is
CURRENTTIME()
This returns the number of seconds and milliseconds since January 1st, 1970, 00:00:00 UTC. The result is a double value in seconds with a granularity of 1 ms. It is, of course, strongly typed so you cannot just add some value to it unless that value is a duration.
System.out.println(new Eval().evaluate("CURRENTTIME()+5"));
will fail with an exception, while
System.out.println(new Eval().evaluate("CURRENTTIME()+5[s]"));
prints a (rather large) number.
Adding your own functions¶
The method setUserDefinedFunctions can be used to expand the available set of functions.