Java Expression Parser & Evaluator
December 16, 2012 23 Comments
There are quite a lot of free and commercial expression evaluators for Java out there.
From simple to complicated, you have a lot of choices.
But when I needed one the other day, it was quickly clear that none of the existing solutions fitted exactly my needs.
Download free Java Expression Parser & Evaluator at GitHub
First of all, it had to understand decimal and also negative values. You would be surprised to see how many of the existing solutions do not support that. The reason for this lack of support, by the way, is IMHO the lack of public available general tokenizing algorithms.
Solving expression like 1+(6-4)*5
is easy, when you have access to Google and/or Wikipedia, because it is a standard task in learning algorithms and data structures. When it comes to expressions like 1+(6 - -4.3)*-5.21
, things get a bit more tricky and require some homemade logic in advance.
Second feature, the solution had to be simple and compact. Ideally it would be only a single class without the need of additional external libraries. Small footprint and based on standard 1.6 Java runtime.
Third and most important feature was the need for Java BigDecimal
support. Nearly all of the existing simple solutions use Java’s double
as a base data type for calculations. I had no need for high precision in trigonometric or logarithmic functions, but a strong need for exact precision in normal arithmetic. You think normal Java double
based arithmetic is enough for everything that is below rocket science? You are utterly wrong. Take a look at this code snippet that adds 0.1 tens times and prints each result:
public static void main(String[] args) { double x = 0; for (int i = 0; i < 10; i ++) { x = x + 0.1; System.out.println("Value of x: " + x); } }
It will print out the following:
Value of x: 0.1
Value of x: 0.2
Value of x: 0.30000000000000004
Value of x: 0.4
Value of x: 0.5
Value of x: 0.6
Value of x: 0.7
Value of x: 0.7999999999999999
Value of x: 0.8999999999999999
Value of x: 0.9999999999999999
This is not ‘wrong’, because of the limited possibilities to store fractional numbers in general.
Now, using Java double based arithmetic in a financial application should be considered harmful, from my point of view. At least if you are not extremely careful with rounding in every step. Therefore the need for BigDecimal support.
Fourth feature was the support for Boolean logic, ideally mixable with standard arithmetic. I wanted to be able to not only evaluate Boolean expressions like x < 4 && y > 5
, but also expressions like 2 * x^2 > sqrt(y - 3.2) && max(x, 100) > 100
.
And last but not least, of course support for functions and variables, as seen in the examples above.
So I sat down and implemented one, that had all what I wanted, plus some more features I caught on the way:
- Uses BigDecimal for calculation and result
- Single class implementation, very compact
- No dependencies to external libraries
- Precision and rounding mode can be set
- Supports variables
- Standard Boolean and mathematical operators
- Standard basic mathematical and Boolean functions
- Custom functions and operators can be added at runtime
The complete source can be found on GitHub, which is in fact only a single Java file, if you remove all the JUnit test cases and administrative files like license and readme.
The class com.udojava.evalex.Expression
is all you need to have this handy expression parser and evaluator up and running.
To work with it, simply create an expression by passing the expression string:
Expression e = new Expression("1+1/3");
Then you can evaluate the expression by calling the eval() method on the expression:
BigDecimal result = e.eval();
Once you have an expression, you can re-evaluate it again and again, possibly with adjusting parameters like variables, precision, rounding mode and even modified functions and operators:
e.setPrecision(2); e.setRoundingmode(RoundingMode.up); BigDecimal result2 = e.eval();
A way of combining those actions, called chaining, is becoming more and more en vogue in Java and other programming languages. Java’s BigDecimal supports that pattern and my EvalExer also does:
BigDecimal result = new Expression("1+1/3").setPrecision(3).setRoundingMode(RoundingMode.UP).eval();
Variables can be set on an expression in different ways and then the expression can be re-evaluated without creating a new expression every time:
Expression e = new Expression("SQRT(a^2 + b^2)"); e.setVariable("a","3.42"); e.setVariable("b", new BigDecimal(9.6)); e.eval(); e.setVariable("a","9.51"); e.setVariable("b", new BigDecimal(8.4)); e.eval();
There are also some convenient methods for chaining expressions with variable setters:
BigDecimal result = new Expressions("SQRT(a^2 + b^2)").with("a",new BigDecimal (3.42)).and("b2,"9,6").setPrecision(2).eval();
And, of course, with BigDecimal chaining combined, you can have more chaining fun:
BigDecimal result = new Expressions("SQRT(a^2 + b^2)").with("a",new BigDecimal (3.42)).and("b","9,6").setPrecision(2).eval().add(14.2).divide(3);
Initially, the EvalEx parser/evaluator comes with a predefined set of operators and functions. (Plus a 100 digits exact predefined variable for PI
, for free).
This set of operators, functions and variables can be extended at runtime without modifying the existing class.
Adding variables is easy, we have seen that before. Simply add them using the different chainable set()/with()/and()
functions to an expression.
Custom operators can be added easily, simply create an instance of Expression.Operator
and add it to the expression. Parameters are the operator name, its precedence and if it is left associative. The operators eval()
method will be called with the BigDecimal
values of the operands. All existing operators can also be overridden.
For example, add an operator x >> n
, that moves the decimal point of x
n
digits to the right:
Expression e = new Expression("2.1234 >> 2"); e.addOperator(e.new Operator("&&", 30, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { return v1.movePointRight(v2.toBigInteger().intValue()); } }); e.eval(); // returns 212.34
Adding custom functions is as easy as adding custom operators. Create an instance of Expression.Function
and add it to the expression. Parameters are the function name and the count of required parameters. The functions eval()
method will be called with a list of the BigDecimal
parameters. All existing functions can also be overridden.
For example, add a function average(a,b,c)
, that will calculate the average value of a, b and c:
Expression e = new Expression("2 * average(12,4,8)"); e.addFunction(e.new Function("average", 3) { @Override public BigDecimal eval(List parameters) { BigDecimal sum = parameters.get(0).add(parameters.get(1)).add(parameters.get(2)); return sum.divide(new BigDecimal(3)); } }); e.eval(); // returns 16
Supported Operators
Mathematical Operators | |
---|---|
Operator | Description |
+ | Additive operator |
– | Subtraction operator |
* | Multiplication operator |
/ | Division operator |
% | Remainder operator (Modulo) |
^ | Power operator |
Boolean Operators^{*} | |
---|---|
Operator | Description |
= | Equals |
== | Equals |
!= | Not equals |
<> | Not equals |
< | Less than |
<= | Less than or equal to |
> | Greater than |
>= | Greater than or equal to |
&& | Boolean and |
|| | Boolean or |
*Boolean operators result always in a BigDecimal value of 1 or 0 (zero). Any non-zero value is treated as a _true_ value. Boolean _not_ is implemented by a function.
Supported Functions
Function^{*} | Description |
---|---|
NOT(expression) | Boolean negation, 1 (means true) if the expression is not zero |
RANDOM() | Produces a random number between 0 and 1 |
MIN(e1,e2) | Returns the smaller of both expressions |
MAX(e1,e2) | Returns the bigger of both expressions |
ABS(expression) | Returns the absolute (non-negative) value of the expression |
ROUND(expression,precision) | Rounds a value to a certain number of digits, uses the current rounding mode |
LOG(expression) | Returns the natural logarithm (base e) of an expression |
SQRT(expression) | Returns the square root of an expression |
SIN(expression) | Returns the trigonometric sine of an angle (in degrees) |
COS(expression) | Returns the trigonometric cosine of an angle (in degrees) |
TAN(expression) | Returns the trigonometric tangens of an angle (in degrees) |
SINH(expression) | Returns the hyperbolic sine of a value |
COSH(expression) | Returns the hyperbolic cosine of a value |
TANH(expression) | Returns the hyperbolic tangens of a value |
RAD(expression) | Converts an angle measured in degrees to an approximately equivalent angle measured in radians |
DEG(expression) | Converts an angle measured in radians to an approximately equivalent angle measured in degrees |
*Functions names are case insensitive.
Supported Constants
Constant | Description |
---|---|
PI | The value of PI, exact to 100 digits |
I wanted to use this evaluator to make a calculator
so I want to get an input from the user and then print an output
when the project is executed in eclipse it should work like this :
Enter an Expression:
ADD(DIV(SIN(FACT(3)),CEIL(TAN(MUL(1.5,FIB(4))))),GCD(2,10))
The Result is: 1.94
how and where can I add lines like System.out.println(“Enter an Expression: “) and
string expr = s.nextLine() and such to your code ?
also how can I remove part related to boolean and arguments without making the code to malfunction? (I only need mathematical parts and I should add functions like arcsinh , combination , fibonacci and others that I have written the codes for them and only have to add them to your code)
also I’m a beginner in Java
Hello Sepehr,
there are alot of ways to do that, the easiest is probably to use the Scanner class from Java:
You can remove any no needed function and operator, by removing the complete code block for addFunction(…) and addOperator(…) in the constructor of Expression.java.
Hi,
I just released MEP4J a new, and free, high performance java library for parsing math expressions.
http://sourceforge.net/projects/mep4j/
I would appreciate if you could test it and give me some feedback.
Thanks
Mario Danelli
I see that you have not published any source code with your library, so there is not very much that I can say about it. What I noticed is that you are using Double values instead of BigDecimal. Though Double is a bit faster than BigDecimal, it is also less precise. BigDecimal is an exact eperesentation of a decimal number. WIth Double you may end up in the rounding hell, where 0.0001 + 0.0001 may result in 0.00019999999. Also there is no possibility to add custom functions and operator at runtime. Boolean operators and the possibility to set the rounding mode and precision is also missing.
Hi Udo,
if you are interested you can find the code at the link
https://sourceforge.net/p/mep4j/svn/HEAD/tree/
I’m sorry but at the moment it doesn’t contain so much comments.
Kind regards
Mario Danelli
Hello Udo
Thanks for your expression evaluator EvalEx!
My vector graphics program Drawj2d (http://drawj2d.sourceforge.net/) makes use of it to provide a function similar to expr in the tcl language.
A few notes:
1) I have added the functions ASIN, ACOS and ATAN. See http://sourceforge.net/p/drawj2d/code/HEAD/tree/trunk/src/com/udojava/evalex/Expression.java .
Maybe you include the functions upstream.
2) “-sqrt(3)” fails (empty stack).
Work-around “0-sqrt(3)” required.
3) Scientific notation (“2.2E-16”) is not supported.
This is an issue for my use, as built in drawj2d functions (inheriting from java/hecl) return values close to zero in scientific notation. The work-around I suggest in the function reference (http://drawj2d.sourceforge.net/drawj2d_en.pdf section 3.2.4 expr) is rather clumsy.
Regards
Adrian
Thank you very much, I have added the functions to the GitHub repository.
If you find a fix for the negative function prefix, feel free to submit it. I am always happy about enhancements. At the moment, the prefix minus considered to be part of the number, it is not treated a an operator. Therefore there is also no “NOT” boolean operator.
Scientific notation, this could get tricky. But I guess it is possible to implement in the tokenizer.
Hi Udo,
Your EvalEx library interests me in making a calculator. I am newer than new to Java, so I don’t understand the internals of EvalEx. My calculator would be different than most because I would like to use a “space” key to make the equation string more readable. I am finding that spaces do not throw an exception when I would expect one. A couple examples are “12 18 2” = 2. Or “(12)(18)” = 18. I don’t need an implied multiply on the second example, but would like to know if both examples can be made to throw an exception. Thanks.
This is not a problem with the spaces, they are allowed at any place. It is a problem with missing operators, which do not produce an exception. I will have a look at it.
I do not know much about catching exceptions. Perhaps I am doing it wrong because I don’t find any “catch” examples in Expression.java. Here is what I am doing now…
public void calcResultBigDecimal() {
try {
strEquation = txtEquation.getText().toString();
bigRawResult = new Expression(strEquation).setPrecision(128).setRoundingMode(RoundingMode.HALF_UP).eval();
strRawResult = bigRawResult.toPlainString();
txtResult.setText(String.format(“%,.6f”, bigRawResult));
txtRotate.setText(“Rotate for Visual Inch™”);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
} catch (Exception e) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
txtRotate.setText(e.getMessage());
txtResult.setText(“”);
}
}
I am using catch (Exception e) because it is all I know.
I see in Expression.java the use of ExpressionException and RuntimeException.
Do I need to do something to catch these exceptions and their e.getMessage()?
Thanks.
A general note, if you have questions or bug reports, please use the issue tracker at GitHub, where this expression parser is maintained: https://github.com/uklimaschewski/EvalEx/issues
Nicely done, this little project of yours! I see you convert to RPN only just before evaluation. I think I will slightly modify it so I know in advance which variables an expression contains, as obtaining variables is costly in my application (involves database access).
Thank you. You can use the public method toRPN() to get an RPN notation of the expression, if that helps.
The calculated RPN is cached in the Expression object, so it won’t get recalculated when it is called again, or the expression is evaluated.
Hi
Following code return 1+E4
I couldn’t identify my mistake
String formaula1 = “principalAmount*volatalityCharge” ;
map = new HashMap();
map.put(“principalAmount”, new BigDecimal(10000));
map.put(“volatalityCharge”, new BigDecimal(1));
System.out.println(“Multiplication”+evalFormula(formaula1, map));
My library does not have a evalFormula() method, but I guess your method is returning a BigDecimal object. The standard toString() method of BigDecimal likes to print in E-notation. Try using evalFormaula(…).toPlainString() to avoid this.
How to calculate -(-10) ?
I tried to the following jUnit and it failed
String formula = “-(-10)”
Expression expression = new Expression(formula).setPrecision(10);
expression.eval().toPlainString();
It is throwing java.util.EmptyStackException. How can we test negative of negative numbers using EvalEx
Unary operators are not (yet) supported by EvalEx. See Issue 36.
How to evaluate -(-10) using EvalEx api ?
Unary operators are not (yet) supported by EvalEx. See Issue 36.
I am trying to evaluate a simple string (I just want to check if the string is empty or not ) :: “kkkk != ””
It is not working by any combinations. Error shown : Unknown operator ”” at position 9
EvalEx does not support string expressions. You can’t use strings in variables or constants.
Hi Udo, Impressive expression evaluating library.
I feel basic error handling is missing.
Like following will result into java.lang.ArithmeticException: Division undefined.
Expression exp = new Expression(“a/b”);
exp.setVariable(“a”, “0”);
exp.setVariable(“b”, “0”);
exp.eval();
Thank you very much. EvalEx will pass the underlying arithmetic errors to the caller. In this case, java.lang.BigDecimal will throw this error, when you try to divide 0 by 0. If, for example, you try to evaluate 1/0, a “java.lang.ArithmeticException: Division by zero” will be thrown.