All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.udojava.evalex.Expression Maven / Gradle / Ivy

Go to download

EvalEx is a handy expression evaluator for Java, that allows to evaluate simple mathematical and boolean expressions.

There is a newer version: 2.7
Show newest version
/*
 * Copyright 2012-2018 Udo Klimaschewski
 * 
 * http://UdoJava.com/
 * http://about.me/udo.klimaschewski
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */
package com.udojava.evalex;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;

/**
 * 

EvalEx - Java Expression Evaluator

* *

Introduction

EvalEx is a handy expression evaluator for Java, that * allows to evaluate simple mathematical and boolean expressions.
* For more information, see: * EvalEx GitHub * repository * * * @authors Thanks to all who contributed to this project: Contributors * @see GitHub repository */ public class Expression { /** * Unary operators precedence: + and - as prefix */ public static final int OPERATOR_PRECEDENCE_UNARY = 60; /** * Equality operators precedence: =, ==, !=. <> */ public static final int OPERATOR_PRECEDENCE_EQUALITY = 7; /** * Comparative operators precedence: <,>,<=,>= */ public static final int OPERATOR_PRECEDENCE_COMPARISON = 10; /** * Or operator precedence: || */ public static final int OPERATOR_PRECEDENCE_OR = 2; /** * And operator precedence: && */ public static final int OPERATOR_PRECEDENCE_AND = 4; /** * Power operator precedence: ^ */ public static final int OPERATOR_PRECEDENCE_POWER = 40; /** * Multiplicative operators precedence: *,/,% */ public static final int OPERATOR_PRECEDENCE_MULTIPLICATIVE = 30; /** * Additive operators precedence: + and - */ public static final int OPERATOR_PRECEDENCE_ADDITIVE = 20; /** * Definition of PI as a constant, can be used in expressions as variable. */ public static final BigDecimal PI = new BigDecimal( "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); /** * Definition of e: "Euler's number" as a constant, can be used in * expressions as variable. */ public static final BigDecimal e = new BigDecimal( "2.71828182845904523536028747135266249775724709369995957496696762772407663"); /** * The {@link MathContext} to use for calculations. */ private MathContext mc = null; /** * The characters (other than letters and digits) allowed as the first * character in a variable. */ private String firstVarChars = "_"; /** * The characters (other than letters and digits) allowed as the second or * subsequent characters in a variable. */ private String varChars = "_"; /** * The original infix expression. */ private final String originalExpression; /** * The current infix expression, with optional variable substitutions. */ private String expression = null; /** * The cached RPN (Reverse Polish Notation) of the expression. */ private List rpn = null; /** * All defined operators with name and implementation. */ private Map operators = new TreeMap( String.CASE_INSENSITIVE_ORDER); /** * All defined functions with name and implementation. */ private Map functions = new TreeMap( String.CASE_INSENSITIVE_ORDER); /** * All defined variables with name and value. */ private Map variables = new TreeMap(String.CASE_INSENSITIVE_ORDER); /** * What character to use for decimal separators. */ private static final char decimalSeparator = '.'; /** * What character to use for minus sign (negative values). */ private static final char minusSign = '-'; /** * The BigDecimal representation of the left parenthesis, used for parsing * varying numbers of function parameters. */ private static final LazyNumber PARAMS_START = new LazyNumber() { public BigDecimal eval() { return null; } public String getString() { return null; } }; /** * The expression evaluators exception class. */ public static class ExpressionException extends RuntimeException { private static final long serialVersionUID = 1118142866870779047L; public ExpressionException(String message) { super(message); } } /** * LazyNumber interface created for lazily evaluated functions */ public interface LazyNumber { BigDecimal eval(); String getString(); } /** * Construct a LazyNumber from a BigDecimal */ private LazyNumber CreateLazyNumber(final BigDecimal bigDecimal) { return new LazyNumber() { @Override public String getString() { return bigDecimal.toPlainString(); } @Override public BigDecimal eval() { return bigDecimal; } }; } public abstract class LazyFunction extends AbstractLazyFunction { /** * Creates a new function with given name and parameter count. * * @param name * The name of the function. * @param numParams * The number of parameters for this function. * -1 denotes a variable number of parameters. * @param booleanFunction * Whether this function is a boolean function. */ public LazyFunction(String name, int numParams, boolean booleanFunction) { super(name, numParams, booleanFunction); } /** * Creates a new function with given name and parameter count. * * @param name * The name of the function. * @param numParams * The number of parameters for this function. * -1 denotes a variable number of parameters. */ public LazyFunction(String name, int numParams) { super(name, numParams); } } /** * Abstract definition of a supported expression function. A function is * defined by a name, the number of parameters and the actual processing * implementation. */ public abstract class Function extends AbstractFunction { public Function(String name, int numParams) { super(name, numParams); } public Function(String name, int numParams, boolean booleanFunction) { super(name, numParams, booleanFunction); } } /** * Abstract definition of a supported operator. An operator is defined by * its name (pattern), precedence and if it is left- or right associative. */ public abstract class Operator extends AbstractOperator { /** * Creates a new operator. * * @param oper * The operator name (pattern). * @param precedence * The operators precedence. * @param leftAssoc * true if the operator is left associative, * else false. * @param booleanOperator * Whether this operator is boolean. */ public Operator(String oper, int precedence, boolean leftAssoc, boolean booleanOperator) { super(oper, precedence, leftAssoc, booleanOperator); } /** * Creates a new operator. * * @param oper * The operator name (pattern). * @param precedence * The operators precedence. * @param leftAssoc * true if the operator is left associative, * else false. */ public Operator(String oper, int precedence, boolean leftAssoc) { super(oper, precedence, leftAssoc); } } public abstract class UnaryOperator extends AbstractUnaryOperator { public UnaryOperator(String oper, int precedence, boolean leftAssoc) { super(oper, precedence, leftAssoc); } } enum TokenType { VARIABLE, FUNCTION, LITERAL, OPERATOR, UNARY_OPERATOR, OPEN_PAREN, COMMA, CLOSE_PAREN, HEX_LITERAL, STRINGPARAM } class Token { public String surface = ""; public TokenType type; public int pos; public void append(char c) { surface += c; } public void append(String s) { surface += s; } public char charAt(int pos) { return surface.charAt(pos); } public int length() { return surface.length(); } @Override public String toString() { return surface; } } /** * Expression tokenizer that allows to iterate over a {@link String} * expression token by token. Blank characters will be skipped. */ private class Tokenizer implements Iterator { /** * Actual position in expression string. */ private int pos = 0; /** * The original input expression. */ private String input; /** * The previous token or null if none. */ private Token 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; } } private boolean isHexDigit(char ch) { return ch == 'x' || ch == 'X' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); } @Override public Token next() { Token token = new Token(); if (pos >= input.length()) { return previousToken = null; } char ch = input.charAt(pos); while (Character.isWhitespace(ch) && pos < input.length()) { ch = input.charAt(++pos); } token.pos = pos; boolean isHex = false; if (Character.isDigit(ch) || (ch == decimalSeparator && Character.isDigit(peekNextChar()))) { if (ch == '0' && (peekNextChar() == 'x' || peekNextChar() == 'X')) isHex = true; while ((isHex && isHexDigit( ch)) || (Character.isDigit(ch) || ch == decimalSeparator || ch == 'e' || ch == 'E' || (ch == minusSign && token.length() > 0 && ('e' == token.charAt(token.length() - 1) || 'E' == token.charAt(token.length() - 1))) || (ch == '+' && token.length() > 0 && ('e' == token.charAt(token.length() - 1) || 'E' == token.charAt(token.length() - 1)))) && (pos < input.length())) { token.append(input.charAt(pos++)); ch = pos == input.length() ? 0 : input.charAt(pos); } token.type = isHex ? TokenType.HEX_LITERAL : TokenType.LITERAL; } else if (ch == '"') { pos++; if (previousToken.type != TokenType.STRINGPARAM) { ch = input.charAt(pos); while (ch != '"') { token.append(input.charAt(pos++)); ch = pos == input.length() ? 0 : input.charAt(pos); } token.type = TokenType.STRINGPARAM; } else { return next(); } } else if (Character.isLetter(ch) || firstVarChars.indexOf(ch) >= 0) { while ((Character.isLetter(ch) || Character.isDigit(ch) || varChars.indexOf(ch) >= 0 || token.length() == 0 && firstVarChars.indexOf(ch) >= 0) && (pos < input.length())) { token.append(input.charAt(pos++)); ch = pos == input.length() ? 0 : input.charAt(pos); } // Remove optional white spaces after function or variable name if (ch == ' ') { while (ch == ' ' && pos < input.length()) { ch = input.charAt(pos++); } pos--; } token.type = ch == '(' ? TokenType.FUNCTION : TokenType.VARIABLE; } else if (ch == '(' || ch == ')' || ch == ',') { if (ch == '(') { token.type = TokenType.OPEN_PAREN; } else if (ch == ')') { token.type = TokenType.CLOSE_PAREN; } else { token.type = TokenType.COMMA; } token.append(ch); pos++; } else { String greedyMatch = ""; int initialPos = pos; ch = input.charAt(pos); int validOperatorSeenUntil = -1; while (!Character.isLetter(ch) && !Character.isDigit(ch) && firstVarChars.indexOf(ch) < 0 && !Character.isWhitespace(ch) && ch != '(' && ch != ')' && ch != ',' && (pos < input.length())) { greedyMatch += ch; pos++; if (operators.containsKey(greedyMatch)) { validOperatorSeenUntil = pos; } ch = pos == input.length() ? 0 : input.charAt(pos); } if (validOperatorSeenUntil != -1) { token.append(input.substring(initialPos, validOperatorSeenUntil)); pos = validOperatorSeenUntil; } else { token.append(greedyMatch); } if (previousToken == null || previousToken.type == TokenType.OPERATOR || previousToken.type == TokenType.OPEN_PAREN || previousToken.type == TokenType.COMMA) { token.surface += "u"; token.type = TokenType.UNARY_OPERATOR; } else { token.type = TokenType.OPERATOR; } } return previousToken = token; } @Override public void remove() { throw new ExpressionException("remove() not supported"); } } /** * Creates a new expression instance from an expression string with a given * default match context of {@link MathContext#DECIMAL32}. * * @param expression * The expression. E.g. "2.4*sin(3)/(2-4)" or * "sin(y)>0 & max(z, 3)>3" */ public Expression(String expression) { this(expression, MathContext.DECIMAL32); } /** * Creates a new expression instance from an expression string with a given * default match context. * * @param expression * The expression. E.g. "2.4*sin(3)/(2-4)" or * "sin(y)>0 & max(z, 3)>3" * @param defaultMathContext * The {@link MathContext} to use by default. */ public Expression(String expression, MathContext defaultMathContext) { this.mc = defaultMathContext; this.expression = expression; this.originalExpression = expression; addOperator(new Operator("+", OPERATOR_PRECEDENCE_ADDITIVE, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.add(v2, mc); } }); addOperator(new Operator("-", OPERATOR_PRECEDENCE_ADDITIVE, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.subtract(v2, mc); } }); addOperator(new Operator("*", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.multiply(v2, mc); } }); addOperator(new Operator("/", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.divide(v2, mc); } }); addOperator(new Operator("%", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.remainder(v2, mc); } }); addOperator(new Operator("^", OPERATOR_PRECEDENCE_POWER, false) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, 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("&&", OPERATOR_PRECEDENCE_AND, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0; boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0; return b1 && b2 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator("||", OPERATOR_PRECEDENCE_OR, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0; boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0; return b1 || b2 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator(">", OPERATOR_PRECEDENCE_COMPARISON, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.compareTo(v2) == 1 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator(">=", OPERATOR_PRECEDENCE_COMPARISON, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.compareTo(v2) >= 0 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator("<", OPERATOR_PRECEDENCE_COMPARISON, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.compareTo(v2) == -1 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator("<=", OPERATOR_PRECEDENCE_COMPARISON, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return v1.compareTo(v2) <= 0 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator("=", OPERATOR_PRECEDENCE_EQUALITY, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { if (v1 == v2) { return BigDecimal.ONE; } if (v1 == null || v2 == null) { return BigDecimal.ZERO; } return v1.compareTo(v2) == 0 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator("==", OPERATOR_PRECEDENCE_EQUALITY, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { return operators.get("=").eval(v1, v2); } }); addOperator(new Operator("!=", OPERATOR_PRECEDENCE_EQUALITY, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { if (v1 == v2) { return BigDecimal.ZERO; } if (v1 == null || v2 == null) { return BigDecimal.ONE; } return v1.compareTo(v2) != 0 ? BigDecimal.ONE : BigDecimal.ZERO; } }); addOperator(new Operator("<>", OPERATOR_PRECEDENCE_EQUALITY, false, true) { @Override public BigDecimal eval(BigDecimal v1, BigDecimal v2) { assertNotNull(v1, v2); return operators.get("!=").eval(v1, v2); } }); addOperator(new UnaryOperator("-", OPERATOR_PRECEDENCE_UNARY, false) { @Override public BigDecimal evalUnary(BigDecimal v1) { return v1.multiply(new BigDecimal(-1)); } }); addOperator(new UnaryOperator("+", OPERATOR_PRECEDENCE_UNARY, false) { @Override public BigDecimal evalUnary(BigDecimal v1) { return v1.multiply(BigDecimal.ONE); } }); addFunction(new Function("NOT", 1, true) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); boolean zero = parameters.get(0).compareTo(BigDecimal.ZERO) == 0; return zero ? BigDecimal.ONE : BigDecimal.ZERO; } }); addLazyFunction(new LazyFunction("IF", 3) { @Override public LazyNumber lazyEval(List lazyParams) { BigDecimal result = lazyParams.get(0).eval(); assertNotNull(result); boolean isTrue = result.compareTo(BigDecimal.ZERO) != 0; return isTrue ? lazyParams.get(1) : lazyParams.get(2); } }); addFunction(new Function("RANDOM", 0) { @Override public BigDecimal eval(List parameters) { double d = Math.random(); return new BigDecimal(d, mc); } }); addFunction(new Function("SIN", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.sin(Math.toRadians(parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("COS", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.cos(Math.toRadians(parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("TAN", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.tan(Math.toRadians(parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("ASIN", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.toDegrees(Math.asin(parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("ACOS", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.toDegrees(Math.acos(parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("ATAN", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.toDegrees(Math.atan(parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("ATAN2", 2) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0), parameters.get(1)); double d = Math.toDegrees(Math.atan2(parameters.get(0).doubleValue(), parameters.get(1).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("SINH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.sinh(parameters.get(0).doubleValue()); return new BigDecimal(d, mc); } }); addFunction(new Function("COSH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.cosh(parameters.get(0).doubleValue()); return new BigDecimal(d, mc); } }); addFunction(new Function("TANH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.tanh(parameters.get(0).doubleValue()); return new BigDecimal(d, mc); } }); addFunction(new Function("SEC", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: sec(x) = 1 / cos(x) */ double one = 1; double d = Math.cos(Math.toRadians(parameters.get(0).doubleValue())); return new BigDecimal((one / d), mc); } }); addFunction(new Function("CSC", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: csc(x) = 1 / sin(x) */ double one = 1; double d = Math.sin(Math.toRadians(parameters.get(0).doubleValue())); return new BigDecimal((one / d), mc); } }); addFunction(new Function("SECH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: sech(x) = 1 / cosh(x) */ double one = 1; double d = Math.cosh(parameters.get(0).doubleValue()); return new BigDecimal((one / d), mc); } }); addFunction(new Function("CSCH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: csch(x) = 1 / sinh(x) */ double one = 1; double d = Math.sinh(parameters.get(0).doubleValue()); return new BigDecimal((one / d), mc); } }); addFunction(new Function("COT", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) */ double one = 1; double d = Math.tan(Math.toRadians(parameters.get(0).doubleValue())); return new BigDecimal((one / d), mc); } }); addFunction(new Function("ACOT", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: acot(x) = atan(1/x) */ if (parameters.get(0).doubleValue() == 0) { throw new ExpressionException("Number must not be 0"); } double d = Math.toDegrees(Math.atan(1 / parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("COTH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: coth(x) = 1 / tanh(x) */ double one = 1; double d = Math.tanh(parameters.get(0).doubleValue()); return new BigDecimal((one / d), mc); } }); addFunction(new Function("ASINH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) */ double d = Math.log(parameters.get(0).doubleValue() + (Math.sqrt(Math.pow(parameters.get(0).doubleValue(), 2) + 1))); return new BigDecimal(d, mc); } }); addFunction(new Function("ACOSH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) */ if (Double.compare(parameters.get(0).doubleValue(), 1) < 0) { throw new ExpressionException("Number must be x >= 1"); } double d = Math.log(parameters.get(0).doubleValue() + (Math.sqrt(Math.pow(parameters.get(0).doubleValue(), 2) - 1))); return new BigDecimal(d, mc); } }); addFunction(new Function("ATANH", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /** Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) */ if (Math.abs(parameters.get(0).doubleValue()) > 1 || Math.abs(parameters.get(0).doubleValue()) == 1) { throw new ExpressionException("Number must be |x| < 1"); } double d = 0.5 * Math.log((1 + parameters.get(0).doubleValue()) / (1 - parameters.get(0).doubleValue())); return new BigDecimal(d, mc); } }); addFunction(new Function("RAD", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.toRadians(parameters.get(0).doubleValue()); return new BigDecimal(d, mc); } }); addFunction(new Function("DEG", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.toDegrees(parameters.get(0).doubleValue()); return new BigDecimal(d, mc); } }); addFunction(new Function("MAX", -1) { @Override public BigDecimal eval(List parameters) { if (parameters.size() == 0) { throw new ExpressionException("MAX requires at least one parameter"); } BigDecimal max = null; for (BigDecimal parameter : parameters) { assertNotNull(parameter); if (max == null || parameter.compareTo(max) > 0) { max = parameter; } } return max; } }); addFunction(new Function("MIN", -1) { @Override public BigDecimal eval(List parameters) { if (parameters.size() == 0) { throw new ExpressionException("MIN requires at least one parameter"); } BigDecimal min = null; for (BigDecimal parameter : parameters) { assertNotNull(parameter); if (min == null || parameter.compareTo(min) < 0) { min = parameter; } } return min; } }); addFunction(new Function("ABS", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); return parameters.get(0).abs(mc); } }); addFunction(new Function("LOG", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.log(parameters.get(0).doubleValue()); return new BigDecimal(d, mc); } }); addFunction(new Function("LOG10", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); double d = Math.log10(parameters.get(0).doubleValue()); return new BigDecimal(d, mc); } }); addFunction(new Function("ROUND", 2) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0), parameters.get(1)); BigDecimal toRound = parameters.get(0); int precision = parameters.get(1).intValue(); return toRound.setScale(precision, mc.getRoundingMode()); } }); addFunction(new Function("FLOOR", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); BigDecimal toRound = parameters.get(0); return toRound.setScale(0, RoundingMode.FLOOR); } }); addFunction(new Function("CEILING", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); BigDecimal toRound = parameters.get(0); return toRound.setScale(0, RoundingMode.CEILING); } }); addFunction(new Function("SQRT", 1) { @Override public BigDecimal eval(List parameters) { assertNotNull(parameters.get(0)); /* * From The Java Programmers Guide To numerical Computing * (Ronald Mak, 2003) */ BigDecimal x = parameters.get(0); if (x.compareTo(BigDecimal.ZERO) == 0) { return new BigDecimal(0); } if (x.signum() < 0) { throw new ExpressionException("Argument to SQRT() function must not be negative"); } BigInteger n = x.movePointRight(mc.getPrecision() << 1).toBigInteger(); int bits = (n.bitLength() + 1) >> 1; BigInteger ix = n.shiftRight(bits); BigInteger ixPrev; do { ixPrev = ix; ix = ix.add(n.divide(ix)).shiftRight(1); // Give other threads a chance to work; Thread.yield(); } while (ix.compareTo(ixPrev) != 0); return new BigDecimal(ix, mc.getPrecision()); } }); variables.put("e", CreateLazyNumber(e)); variables.put("PI", CreateLazyNumber(PI)); variables.put("NULL", null); variables.put("TRUE", CreateLazyNumber(BigDecimal.ONE)); variables.put("FALSE", CreateLazyNumber(BigDecimal.ZERO)); } private void assertNotNull(BigDecimal v1) { if (v1 == null) { throw new ArithmeticException("Operand may not be null"); } } private void assertNotNull(BigDecimal v1, BigDecimal v2) { if (v1 == null) { throw new ArithmeticException("First operand may not be null"); } if (v2 == null) { throw new ArithmeticException("Second operand may not be null"); } } /** * Is the string a number? * * @param st * The string. * @return true, if the input string is a number. */ private boolean isNumber(String st) { if (st.charAt(0) == minusSign && st.length() == 1) return false; if (st.charAt(0) == '+' && st.length() == 1) return false; if (st.charAt(0) == decimalSeparator && (st.length() == 1 || !Character.isDigit(st.charAt(1)))) return false; if (st.charAt(0) == 'e' || st.charAt(0) == 'E') return false; for (char ch : st.toCharArray()) { if (!Character.isDigit(ch) && ch != minusSign && ch != decimalSeparator && ch != 'e' && ch != 'E' && ch != '+') 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 List shuntingYard(String expression) { List outputQueue = new ArrayList(); Stack stack = new Stack(); Tokenizer tokenizer = new Tokenizer(expression); Token lastFunction = null; Token previousToken = null; while (tokenizer.hasNext()) { Token token = tokenizer.next(); switch (token.type) { case STRINGPARAM: stack.push(token); break; case LITERAL: case HEX_LITERAL: outputQueue.add(token); break; case VARIABLE: outputQueue.add(token); break; case FUNCTION: stack.push(token); lastFunction = token; break; case COMMA: if (previousToken != null && previousToken.type == TokenType.OPERATOR) { throw new ExpressionException("Missing parameter(s) for operator " + previousToken + " at character position " + previousToken.pos); } while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) { outputQueue.add(stack.pop()); } if (stack.isEmpty()) { if (lastFunction == null) { throw new ExpressionException("Unexpected comma at character position " + token.pos); } else { throw new ExpressionException( "Parse error for function '" + lastFunction + "' at character position " + token.pos); } } break; case OPERATOR: { if (previousToken != null && (previousToken.type == TokenType.COMMA || previousToken.type == TokenType.OPEN_PAREN)) { throw new ExpressionException( "Missing parameter(s) for operator " + token + " at character position " + token.pos); } com.udojava.evalex.Operator o1 = operators.get(token.surface); if (o1 == null) { throw new ExpressionException("Unknown operator '" + token + "' at position " + (token.pos + 1)); } shuntOperators(outputQueue, stack, o1); stack.push(token); break; } case UNARY_OPERATOR: { if (previousToken != null && previousToken.type != TokenType.OPERATOR && previousToken.type != TokenType.COMMA && previousToken.type != TokenType.OPEN_PAREN) { throw new ExpressionException( "Invalid position for unary operator " + token + " at character position " + token.pos); } com.udojava.evalex.Operator o1 = operators.get(token.surface); if (o1 == null) { throw new ExpressionException( "Unknown unary operator '" + token.surface.substring(0, token.surface.length() - 1) + "' at position " + (token.pos + 1)); } shuntOperators(outputQueue, stack, o1); stack.push(token); break; } case OPEN_PAREN: if (previousToken != null) { if (previousToken.type == TokenType.LITERAL || previousToken.type == TokenType.CLOSE_PAREN || previousToken.type == TokenType.VARIABLE || previousToken.type == TokenType.HEX_LITERAL) { // Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b) Token multiplication = new Token(); multiplication.append("*"); multiplication.type = TokenType.OPERATOR; stack.push(multiplication); } // if the ( is preceded by a valid function, then it // denotes the start of a parameter list if (previousToken.type == TokenType.FUNCTION) { outputQueue.add(token); } } stack.push(token); break; case CLOSE_PAREN: if (previousToken != null && previousToken.type == TokenType.OPERATOR) { throw new ExpressionException("Missing parameter(s) for operator " + previousToken + " at character position " + previousToken.pos); } while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) { outputQueue.add(stack.pop()); } if (stack.isEmpty()) { throw new ExpressionException("Mismatched parentheses"); } stack.pop(); if (!stack.isEmpty() && stack.peek().type == TokenType.FUNCTION) { outputQueue.add(stack.pop()); } } previousToken = token; } while (!stack.isEmpty()) { Token element = stack.pop(); if (element.type == TokenType.OPEN_PAREN || element.type == TokenType.CLOSE_PAREN) { throw new ExpressionException("Mismatched parentheses"); } outputQueue.add(element); } return outputQueue; } private void shuntOperators(List outputQueue, Stack stack, com.udojava.evalex.Operator o1) { Expression.Token nextToken = stack.isEmpty() ? null : stack.peek(); while (nextToken != null && (nextToken.type == Expression.TokenType.OPERATOR || nextToken.type == Expression.TokenType.UNARY_OPERATOR) && ((o1.isLeftAssoc() && o1.getPrecedence() <= operators.get(nextToken.surface).getPrecedence()) || (o1.getPrecedence() < operators.get(nextToken.surface).getPrecedence()))) { outputQueue.add(stack.pop()); nextToken = stack.isEmpty() ? null : stack.peek(); } } /** * Evaluates the expression. * * @return The result of the expression. Trailing zeros are stripped. */ public BigDecimal eval() { return eval(true); } /** * Evaluates the expression. * * @param stripTrailingZeros * If set to true trailing zeros in the result are * stripped. * * @return The result of the expression. */ public BigDecimal eval(boolean stripTrailingZeros) { Stack stack = new Stack(); for (final Token token : getRPN()) { switch (token.type) { case UNARY_OPERATOR: { final LazyNumber value = stack.pop(); LazyNumber result = new LazyNumber() { public BigDecimal eval() { return operators.get(token.surface).eval(value.eval(), null); } @Override public String getString() { return String.valueOf(operators.get(token.surface).eval(value.eval(), null)); } }; stack.push(result); break; } case OPERATOR: final LazyNumber v1 = stack.pop(); final LazyNumber v2 = stack.pop(); LazyNumber result = new LazyNumber() { public BigDecimal eval() { return operators.get(token.surface).eval(v2.eval(), v1.eval()); } public String getString() { return String.valueOf(operators.get(token.surface).eval(v2.eval(), v1.eval())); } }; stack.push(result); break; case VARIABLE: if (!variables.containsKey(token.surface)) { throw new ExpressionException("Unknown operator or function: " + token); } stack.push(new LazyNumber() { public BigDecimal eval() { LazyNumber lazyVariable = variables.get(token.surface); BigDecimal value = lazyVariable == null ? null : lazyVariable.eval(); return value == null ? null : value.round(mc); } public String getString() { return token.surface; } }); break; case FUNCTION: com.udojava.evalex.LazyFunction f = functions.get(token.surface.toUpperCase(Locale.ROOT)); ArrayList p = new ArrayList(!f.numParamsVaries() ? f.getNumParams() : 0); // pop parameters off the stack until we hit the start of // this function's parameter list while (!stack.isEmpty() && stack.peek() != PARAMS_START) { p.add(0, stack.pop()); } if (stack.peek() == PARAMS_START) { stack.pop(); } LazyNumber fResult = f.lazyEval(p); stack.push(fResult); break; case OPEN_PAREN: stack.push(PARAMS_START); break; case LITERAL: stack.push(new LazyNumber() { public BigDecimal eval() { if (token.surface.equalsIgnoreCase("NULL")) { return null; } return new BigDecimal(token.surface, mc); } public String getString() { return String.valueOf(new BigDecimal(token.surface, mc)); } }); break; case STRINGPARAM: stack.push(new LazyNumber() { public BigDecimal eval() { return null; } public String getString() { return token.surface; } }); break; case HEX_LITERAL: stack.push(new LazyNumber() { public BigDecimal eval() { return new BigDecimal(new BigInteger(token.surface.substring(2), 16), mc); } public String getString() { return new BigInteger(token.surface.substring(2), 16).toString(); } }); break; default: throw new ExpressionException( "Unexpected token '" + token.surface + "' at character position " + token.pos); } } BigDecimal result = stack.pop().eval(); return result == null ? null : stripTrailingZeros ? result.stripTrailingZeros() : result; } /** * Sets the precision for expression evaluation. * * @param precision * The new precision. * * @return The expression, allows to chain methods. */ public Expression setPrecision(int precision) { this.mc = new MathContext(precision); return this; } /** * Sets the rounding mode for expression evaluation. * * @param roundingMode * The new rounding mode. * @return The expression, allows to chain methods. */ public Expression setRoundingMode(RoundingMode roundingMode) { this.mc = new MathContext(mc.getPrecision(), roundingMode); return this; } /** * Sets the characters other than letters and digits that are valid as the * first character of a variable. * * @param chars * The new set of variable characters. * @return The expression, allows to chain methods. */ public Expression setFirstVariableCharacters(String chars) { this.firstVarChars = chars; return this; } /** * Sets the characters other than letters and digits that are valid as the * second and subsequent characters of a variable. * * @param chars * The new set of variable characters. * @return The expression, allows to chain methods. */ public Expression setVariableCharacters(String chars) { this.varChars = chars; return this; } /** * Adds an operator to the list of supported operators. * * @param operator * The operator to add. * @return The previous operator with that name, or null if * there was none. */ public com.udojava.evalex.Operator addOperator(com.udojava.evalex.Operator operator) { String key = operator.getOper(); if (operator instanceof UnaryOperator) { key += "u"; } return operators.put(key, 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 com.udojava.evalex.Function addFunction(com.udojava.evalex.Function function) { return (com.udojava.evalex.Function) functions.put(function.getName(), function); } /** * Adds a lazy function 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 com.udojava.evalex.LazyFunction addLazyFunction(com.udojava.evalex.LazyFunction 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 Expression setVariable(String variable, BigDecimal value) { variables.put(variable, CreateLazyNumber(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 Expression setVariable(String variable, String value) { if (isNumber(value)) variables.put(variable, CreateLazyNumber(new BigDecimal(value, mc))); else if (value.equalsIgnoreCase("null")) { variables.put(variable, null); } else { final String expStr = value; variables.put(variable, new LazyNumber() { private final Map outerVariables = variables; private final Map outerFunctions = functions; private final Map outerOperators = operators; private final String innerExpressionString = expStr; private final MathContext inneMc = mc; @Override public String getString() { return innerExpressionString; } @Override public BigDecimal eval() { Expression innerE = new Expression(innerExpressionString, inneMc); innerE.variables = outerVariables; innerE.functions = outerFunctions; innerE.operators = outerOperators; BigDecimal val = innerE.eval(); return val; } }); rpn = null; } return this; } /** * Creates a new inner expression for nested expression. * * @param expression * The string expression. * @return The inner Expression instance. */ private Expression createEmbeddedExpression(final String expression) { final Map outerVariables = variables; final Map outerFunctions = functions; final Map outerOperators = operators; final MathContext inneMc = mc; Expression exp = new Expression(expression, inneMc); exp.variables = outerVariables; exp.functions = outerFunctions; exp.operators = outerOperators; return exp; } /** * Sets a variable value. * * @param variable * The variable to set. * @param value * The variable value. * @return The expression, allows to chain methods. */ public Expression 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 Expression 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 Expression 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 Expression 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 getExpressionTokenizer() { final String expression = this.expression; return new Tokenizer(expression); } /** * Cached access to the RPN notation of this expression, ensures only one * calculation of the RPN per expression instance. If no cached instance * exists, a new one will be created and put to the cache. * * @return The cached RPN instance. */ private List getRPN() { if (rpn == null) { rpn = shuntingYard(this.expression); validate(rpn); } return rpn; } /** * Check that the expression has enough numbers and variables to fit the * requirements of the operators and functions, also check for only 1 result * stored at the end of the evaluation. */ private void validate(List rpn) { /*- * Thanks to Norman Ramsey: * http://http://stackoverflow.com/questions/789847/postfix-notation-validation */ // each push on to this stack is a new function scope, with the value of // each // layer on the stack being the count of the number of parameters in // that scope Stack stack = new Stack(); // push the 'global' scope stack.push(0); for (final Token token : rpn) { switch (token.type) { case UNARY_OPERATOR: if (stack.peek() < 1) { throw new ExpressionException("Missing parameter(s) for operator " + token); } break; case OPERATOR: if (stack.peek() < 2) { throw new ExpressionException("Missing parameter(s) for operator " + token); } // pop the operator's 2 parameters and add the result stack.set(stack.size() - 1, stack.peek() - 2 + 1); break; case FUNCTION: com.udojava.evalex.LazyFunction f = functions.get(token.surface.toUpperCase(Locale.ROOT)); if (f == null) { throw new ExpressionException("Unknown function '" + token + "' at position " + (token.pos + 1)); } int numParams = stack.pop(); if (!f.numParamsVaries() && numParams != f.getNumParams()) { throw new ExpressionException( "Function " + token + " expected " + f.getNumParams() + " parameters, got " + numParams); } if (stack.size() <= 0) { throw new ExpressionException("Too many function calls, maximum scope exceeded"); } // push the result of the function stack.set(stack.size() - 1, stack.peek() + 1); break; case OPEN_PAREN: stack.push(0); break; default: stack.set(stack.size() - 1, stack.peek() + 1); } } if (stack.size() > 1) { throw new ExpressionException("Too many unhandled function parameter lists"); } else if (stack.peek() > 1) { throw new ExpressionException("Too many numbers or variables"); } else if (stack.peek() < 1) { throw new ExpressionException("Empty expression"); } } /** * Get a string representation of the RPN (Reverse Polish Notation) for this * expression. * * @return A string with the RPN representation for this expression. */ public String toRPN() { StringBuilder result = new StringBuilder(); for (Token t : getRPN()) { if (result.length() != 0) result.append(" "); if (t.type == TokenType.VARIABLE && variables.containsKey(t.surface)) { LazyNumber innerVariable = variables.get(t.surface); String innerExp = innerVariable.getString(); if (isNumber(innerExp)) { // if it is a number, then we don't // expan in the RPN result.append(t.toString()); } else { // expand the nested variable to its RPN representation Expression exp = createEmbeddedExpression(innerExp); String nestedExpRpn = exp.toRPN(); result.append(nestedExpRpn); } } else { result.append(t.toString()); } } return result.toString(); } /** * Exposing declared variables in the expression. * * @return All declared variables. */ public Set getDeclaredVariables() { return Collections.unmodifiableSet(variables.keySet()); } /** * Exposing declared operators in the expression. * * @return All declared operators. */ public Set getDeclaredOperators() { return Collections.unmodifiableSet(operators.keySet()); } /** * Exposing declared functions. * * @return All declared functions. */ public Set getDeclaredFunctions() { return Collections.unmodifiableSet(functions.keySet()); } /** * @return The original expression string */ public String getExpression() { return expression; } /** * Returns a list of the variables in the expression. * * @return A list of the variable names in this expression. */ public List getUsedVariables() { List result = new ArrayList(); Tokenizer tokenizer = new Tokenizer(expression); while (tokenizer.hasNext()) { Token nextToken = tokenizer.next(); String token = nextToken.toString(); if (nextToken.type != TokenType.VARIABLE || token.equals("PI") || token.equals("e") || token.equals("TRUE") || token.equals("FALSE")) { continue; } result.add(token); } return result; } /** * The original expression used to construct this expression, without * variables substituted. */ public String getOriginalExpression() { return this.originalExpression; } /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Expression that = (Expression) o; if (this.expression == null) { return that.expression == null; } else { return this.expression.equals(that.expression); } } /** {@inheritDoc} */ @Override public int hashCode() { return this.expression == null ? 0 : this.expression.hashCode(); } /** {@inheritDoc} */ @Override public String toString() { return this.expression; } /** * Checks whether the expression is a boolean expression. An expression is * considered a boolean expression, if the last operator or function is * boolean. The IF function is handled special. If the third parameter is * boolean, then the IF is also considered boolean, else non-boolean. * * @return true if the last operator/function was a boolean. */ public boolean isBoolean() { List rpn = getRPN(); if (rpn.size() > 0) { for (int i = rpn.size() - 1; i >= 0; i--) { Token t = rpn.get(i); /* * The IF function is handled special. If the third parameter is * boolean, then the IF is also considered a boolean. Just skip * the IF function to check the second parameter. */ if (t.surface.equals("IF")) continue; if (t.type == TokenType.FUNCTION) { return functions.get(t.surface).isBooleanFunction(); } else if (t.type == TokenType.OPERATOR) { return operators.get(t.surface).isBooleanOperator(); } } } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy