+ * BigDecimal result = null;
+ *
+ * Expression expression = new Expression("1+1/3");
+ * result = expression.eval():
+ * expression.setPrecision(2);
+ * result = expression.eval():
+ *
+ * result = new Expression("(3.4 + -4.1)/2").eval();
+ *
+ * result = new Expression("SQRT(a^2 + b^2").with("a","2.4").and("b","9.253").eval();
+ *
+ * BigDecimal a = new BigDecimal("2.4");
+ * BigDecimal b = new BigDecimal("9.235");
+ * result = new Expression("SQRT(a^2 + b^2").with("a",a).and("b",b).eval();
+ *
+ * result = new Expression("2.4/PI").setPrecision(128).setRoundingMode(RoundingMode.UP).eval();
+ *
+ * result = new Expression("random() > 0.5").eval();
+ *
+ * result = new Expression("not(x<7 || sqrt(max(x,9)) <= 3))").with("x","22.9").eval();
+ *
+ * | 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 |
| Function* | Description |
|---|---|
| NOT(expression) | Boolean negation, 1 (means true) if the expression is not zero |
| IF(condition,value_if_true,value_if_false) | Returns one value if the condition evaluates to true or the other if it evaluates to false |
| 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 |
| FLOOR(expression) | Rounds the value down to the nearest integer |
| CEILING(expression) | Rounds the value up to the nearest integer |
| LOG(expression) | Returns the natural logarithm (base e) of an expression |
| LOG10(expression) | Returns the common logarithm (base 10) 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) |
| ASIN(expression) | Returns the angle of asin (in degrees) |
| ACOS(expression) | Returns the angle of acos (in degrees) |
| ATAN(expression) | Returns the angle of atan (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 |
| Constant | Description |
|---|---|
| PI | The value of PI, exact to 100 digits |
| TRUE | The value one |
| FALSE | The value zero |
+ * Expression e = new Expression("2.1234 >> 2");
+ *
+ * e.addOperator(e.new Operator(">>", 30, true) {
+ * {@literal @}Override
+ * public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ * return v1.movePointRight(v2.toBigInteger().intValue());
+ * }
+ * });
+ *
+ * e.eval(); // returns 212.34
+ *
+ *
+ * Expression e = new Expression("2 * average(12,4,8)");
+ *
+ * e.addFunction(e.new Function("average", 3) {
+ * {@literal @}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
+ *
+ * The software is licensed under the MIT Open Source license (see LICENSE file).
+ * true if the operator is left associative,
+ * else false.
+ */
+ public Operator(String oper, int precedence, boolean leftAssoc) {
+ this.oper = oper;
+ this.precedence = precedence;
+ this.leftAssoc = leftAssoc;
+ }
+
+ public String getOper() {
+ return oper;
+ }
+
+ public int getPrecedence() {
+ return precedence;
+ }
+
+ public boolean isLeftAssoc() {
+ return leftAssoc;
+ }
+
+ /**
+ * Implementation for this operator.
+ *
+ * @param v1
+ * Operand 1.
+ * @param v2
+ * Operand 2.
+ * @return The result of the operation.
+ */
+ public abstract BigDecimal eval(BigDecimal v1, BigDecimal v2);
+ }
+
+ /**
+ * Expression tokenizer that allows to iterate over a {@link String}
+ * expression token by token. Blank characters will be skipped.
+ */
+ private class Tokenizer implements Iteratornull if none.
+ */
+ private String previousToken;
+
+ /**
+ * Creates a new tokenizer for an expression.
+ *
+ * @param input
+ * The expression string.
+ */
+ public Tokenizer(String input) {
+ this.input = input.trim();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return (pos < input.length());
+ }
+
+ /**
+ * Peek at the next character, without advancing the iterator.
+ *
+ * @return The next character or character 0, if at end of string.
+ */
+ private char peekNextChar() {
+ if (pos < (input.length() - 1)) {
+ return input.charAt(pos + 1);
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public String next() {
+ StringBuilder token = new StringBuilder();
+ if (pos >= input.length()) {
+ return previousToken = null;
+ }
+ char ch = input.charAt(pos);
+ while (Character.isWhitespace(ch) && pos < input.length()) {
+ ch = input.charAt(++pos);
+ }
+ if (Character.isDigit(ch)) {
+ while ((Character.isDigit(ch) || ch == decimalSeparator)
+ && (pos < input.length())) {
+ token.append(input.charAt(pos++));
+ ch = pos == input.length() ? 0 : input.charAt(pos);
+ }
+ } else if (ch == minusSign
+ && Character.isDigit(peekNextChar())
+ && ("(".equals(previousToken) || ",".equals(previousToken)
+ || previousToken == null || operators
+ .containsKey(previousToken))) {
+ token.append(minusSign);
+ pos++;
+ token.append(next());
+ } else if (Character.isLetter(ch) || (ch == '_')) {
+ while ((Character.isLetter(ch) || Character.isDigit(ch) || (ch == '_')) && (pos < input.length())) {
+ token.append(input.charAt(pos++));
+ ch = pos == input.length() ? 0 : input.charAt(pos);
+ }
+ } else if (ch == '(' || ch == ')' || ch == ',') {
+ token.append(ch);
+ pos++;
+ } else {
+ while (!Character.isLetter(ch) && !Character.isDigit(ch) && ch != '_'
+ && !Character.isWhitespace(ch) && ch != '('
+ && ch != ')' && ch != ',' && (pos < input.length())) {
+ token.append(input.charAt(pos));
+ pos++;
+ ch = pos == input.length() ? 0 : input.charAt(pos);
+ if (ch == minusSign) {
+ break;
+ }
+ }
+ if (!operators.containsKey(token.toString())) {
+ throw new ExpressionException("Unknown operator '" + token
+ + "' at position " + (pos - token.length() + 1));
+ }
+ }
+ return previousToken = token.toString();
+ }
+
+ @Override
+ public void remove() {
+ throw new ExpressionException("remove() not supported");
+ }
+
+ /**
+ * Get the actual character position in the string.
+ *
+ * @return The actual character position.
+ */
+ public int getPos() {
+ return pos;
+ }
+
+ }
+
+ /**
+ * Creates a new expression instance from an expression string.
+ *
+ * @param expression
+ * The expression. E.g. "2.4*sin(3)/(2-4)" or
+ * "sin(y)>0 & max(z, 3)>3"
+ */
+ public Expressions(String expression) {
+ this.expression = expression;
+ addOperator(new Operator("+", 20, true) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.add(v2, mc);
+ }
+ });
+ addOperator(new Operator("-", 20, true) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.subtract(v2, mc);
+ }
+ });
+ addOperator(new Operator("*", 30, true) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.multiply(v2, mc);
+ }
+ });
+ addOperator(new Operator("/", 30, true) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.divide(v2, mc);
+ }
+ });
+ addOperator(new Operator("%", 30, true) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.remainder(v2, mc);
+ }
+ });
+ addOperator(new Operator("^", 40, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ /*-
+ * Thanks to Gene Marin:
+ * http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java
+ */
+ int signOf2 = v2.signum();
+ double dn1 = v1.doubleValue();
+ v2 = v2.multiply(new BigDecimal(signOf2)); // n2 is now positive
+ BigDecimal remainderOf2 = v2.remainder(BigDecimal.ONE);
+ BigDecimal n2IntPart = v2.subtract(remainderOf2);
+ BigDecimal intPow = v1.pow(n2IntPart.intValueExact(), mc);
+ BigDecimal doublePow = new BigDecimal(Math.pow(dn1,
+ remainderOf2.doubleValue()));
+
+ BigDecimal result = intPow.multiply(doublePow, mc);
+ if (signOf2 == -1) {
+ result = BigDecimal.ONE.divide(result, mc.getPrecision(),
+ RoundingMode.HALF_UP);
+ }
+ return result;
+ }
+ });
+ addOperator(new Operator("&&", 4, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ boolean b1 = !v1.equals(BigDecimal.ZERO);
+ boolean b2 = !v2.equals(BigDecimal.ZERO);
+ return b1 && b2 ? BigDecimal.ONE : BigDecimal.ZERO;
+ }
+ });
+
+ addOperator(new Operator("||", 2, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ boolean b1 = !v1.equals(BigDecimal.ZERO);
+ boolean b2 = !v2.equals(BigDecimal.ZERO);
+ return b1 || b2 ? BigDecimal.ONE : BigDecimal.ZERO;
+ }
+ });
+
+ addOperator(new Operator(">", 10, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.compareTo(v2) == 1 ? BigDecimal.ONE : BigDecimal.ZERO;
+ }
+ });
+
+ addOperator(new Operator(">=", 10, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.compareTo(v2) >= 0 ? BigDecimal.ONE : BigDecimal.ZERO;
+ }
+ });
+
+ addOperator(new Operator("<", 10, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.compareTo(v2) == -1 ? BigDecimal.ONE
+ : BigDecimal.ZERO;
+ }
+ });
+
+ addOperator(new Operator("<=", 10, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.compareTo(v2) <= 0 ? BigDecimal.ONE : BigDecimal.ZERO;
+ }
+ });
+
+ addOperator(new Operator("=", 7, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.compareTo(v2) == 0 ? BigDecimal.ONE : BigDecimal.ZERO;
+ }
+ });
+ addOperator(new Operator("==", 7, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return operators.get("=").eval(v1, v2);
+ }
+ });
+
+ addOperator(new Operator("!=", 7, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return v1.compareTo(v2) != 0 ? BigDecimal.ONE : BigDecimal.ZERO;
+ }
+ });
+ addOperator(new Operator("<>", 7, false) {
+ @Override
+ public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ return operators.get("!=").eval(v1, v2);
+ }
+ });
+
+ addFunction(new Function("NOT", 1) {
+ @Override
+ public BigDecimal eval(Listtrue, if the input string is a number.
+ */
+ private boolean isNumber(String st) {
+ if (st.charAt(0) == minusSign && st.length() == 1)
+ return false;
+ for (char ch : st.toCharArray()) {
+ if (!Character.isDigit(ch) && ch != minusSign
+ && ch != decimalSeparator)
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Implementation of the Shunting Yard algorithm to transform an
+ * infix expression to a RPN expression.
+ *
+ * @param expression
+ * The input expression in infx.
+ * @return A RPN representation of the expression, with each token as a list
+ * member.
+ */
+ private Listnull if
+ * there was none.
+ */
+ public Operator addOperator(Operator operator) {
+ return operators.put(operator.getOper(), operator);
+ }
+
+ /**
+ * Adds a function to the list of supported functions
+ *
+ * @param function
+ * The function to add.
+ * @return The previous operator with that name, or null if
+ * there was none.
+ */
+ public Function addFunction(Function function) {
+ return functions.put(function.getName(), function);
+ }
+
+ /**
+ * Sets a variable value.
+ *
+ * @param variable
+ * The variable name.
+ * @param value
+ * The variable value.
+ * @return The expression, allows to chain methods.
+ */
+ public Expressions setVariable(String variable, BigDecimal value) {
+ variables.put(variable, value);
+ return this;
+ }
+
+ /**
+ * Sets a variable value.
+ *
+ * @param variable
+ * The variable to set.
+ * @param value
+ * The variable value.
+ * @return The expression, allows to chain methods.
+ */
+ public Expressions setVariable(String variable, String value) {
+ if (isNumber(value))
+ variables.put(variable, new BigDecimal(value));
+ else {
+ expression = expression.replaceAll("\\b" + variable + "\\b", "(" + value + ")");
+ rpn = null;
+ }
+ return this;
+ }
+
+ /**
+ * Sets a variable value.
+ *
+ * @param variable
+ * The variable to set.
+ * @param value
+ * The variable value.
+ * @return The expression, allows to chain methods.
+ */
+ public Expressions with(String variable, BigDecimal value) {
+ return setVariable(variable, value);
+ }
+
+ /**
+ * Sets a variable value.
+ *
+ * @param variable
+ * The variable to set.
+ * @param value
+ * The variable value.
+ * @return The expression, allows to chain methods.
+ */
+ public Expressions and(String variable, String value) {
+ return setVariable(variable, value);
+ }
+
+ /**
+ * Sets a variable value.
+ *
+ * @param variable
+ * The variable to set.
+ * @param value
+ * The variable value.
+ * @return The expression, allows to chain methods.
+ */
+ public Expressions and(String variable, BigDecimal value) {
+ return setVariable(variable, value);
+ }
+
+ /**
+ * Sets a variable value.
+ *
+ * @param variable
+ * The variable to set.
+ * @param value
+ * The variable value.
+ * @return The expression, allows to chain methods.
+ */
+ public Expressions with(String variable, String value) {
+ return setVariable(variable, value);
+ }
+
+ /**
+ * Get an iterator for this expression, allows iterating over an expression
+ * token by token.
+ *
+ * @return A new iterator instance for this expression.
+ */
+ public Iterator