Skip to content

Instantly share code, notes, and snippets.

@glombard
Created October 3, 2016 21:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glombard/7d287c6c4cc86b44da87dd036674c675 to your computer and use it in GitHub Desktop.
Save glombard/7d287c6c4cc86b44da87dd036674c675 to your computer and use it in GitHub Desktop.
Expression Evaluator from: http://tech.dolhub.com/code/MathEval
// Created by Lawrence PC Dol. Released into the public domain.
// http://tech.dolhub.com
//
// Contributions by Carlos Gómez of Asturias, Spain, in the area of unary operators
// and right-to-left evaluations proved invaluable to implementing these features.
// Thanks Carlos!
//
// Source is licensed for any use, provided this copyright notice is retained.
// No warranty for any purpose whatsoever is implied or expressed. The author
// is not liable for any losses of any kind, direct or indirect, which result
// from the use of this software.
//package <your-package-here>;
import java.math.*;
import java.util.*;
/**
* Math Evaluator. Provides the ability to evaluate a String math expression, with support for pureFunctions, variables and
* standard math constants.
* <p>
* Supported Operators:
* <pre>
* Operator Precedence Unary Binding Description
* --------- ----------- -------------- ------------------------------------------------
* '=' 99 / 99 RIGHT_SIDE Simple assignment (internal, used for the final operation)
* '^' 80 / 81 NO_SIDE Power
* '±' 60 / 60 RIGHT_SIDE Unary negation (internal, substituted for '-')
* '*' 40 / 40 NO_SIDE Multiple (conventional computer notation)
* '×' 40 / 40 NO_SIDE Multiple (because it's a Unicode world out there)
* '·' 40 / 40 NO_SIDE Multiple (because it's a Unicode world out there)
* '(' 40 / 40 NO_SIDE Multiply (implicit due to brackets, e.g "(a)(b)")
* '/' 40 / 40 NO_SIDE Divide (conventional computer notation)
* '÷' 40 / 40 NO_SIDE Divide (because it's a Unicode world out there)
* '%' 40 / 40 NO_SIDE Remainder
* '+' 20 / 20 NO_SIDE Add/unary-positive
* '-' 20 / 20 NO_SIDE Subtract/unary-negative
* </pre>
* <p>
* Predefined Constants:
* <pre>
* Name Description
* -------------------- ----------------------------------------------------------------
* E The double value that is closer than any other to e, the base of the natural logarithms (2.718281828459045).
* Euler Euler's Constant (0.577215664901533).
* LN2 Log of 2 base e (0.693147180559945).
* LN10 Log of 10 base e (2.302585092994046).
* LOG2E Log of e base 2 (1.442695040888963).
* LOG10E Log of e base 10 (0.434294481903252).
* PHI The golden ratio (1.618033988749895).
* PI The double value that is closer than any other to pi, the ratio of the circumference of a circle to its diameter (3.141592653589793).
* </pre>
* <p>
* Supported Functions (see java.Math for detail and parameters):
* <ul>
* <li>abs
* <li>acos
* <li>asin
* <li>atan
* <li>cbrt
* <li>ceil
* <li>cos
* <li>cosh
* <li>exp
* <li>expm1
* <li>floor
* <li>log
* <li>log10
* <li>log1p
* <li>max
* <li>min
* <li>random
* <li>round
* <li>roundHE (maps to Math.rint)
* <li>signum
* <li>sin
* <li>sinh
* <li>sqrt
* <li>tan
* <li>tanh
* <li>toDegrees
* <li>toRadians
* <li>ulp
* </ul>
* <p>
* Threading Design : [x] Single Threaded [ ] Threadsafe [ ] Immutable [ ] Isolated
*
* @author Lawrence Dol
* @since Build 2008.0426.1016
*/
public class MathEval
extends Object
{
// *************************************************************************************************
// INSTANCE PROPERTIES
// *************************************************************************************************
private Operator[] operators; // operators in effect for this parser
private final SortedMap<String,Double> constants; // external constants
private final SortedMap<String,Double> variables; // external variables
private final SortedMap<String,FunctionHandler> pureFunctions; // external pureFunctions
private final SortedMap<String,FunctionHandler> impureFunctions; // external pureFunctions
private boolean relaxed; // allow variables to be undefined
private String separators; // cache of the operators, used for separators for getVariablesWithin()
private String expression; // expression being evaluated
private int offset; // used when returning from a higher precedence sub-expression evaluation
private boolean isConstant; // last expression evaluated is constant
// *************************************************************************************************
// INSTANCE CREATE/DELETE
// *************************************************************************************************
/**
* Create a math evaluator.
*/
public MathEval() {
super();
operators=new Operator[256];
DefaultImpl.registerOperators(this);
constants=new TreeMap<String,Double>(String.CASE_INSENSITIVE_ORDER);
variables=new TreeMap<String,Double>(String.CASE_INSENSITIVE_ORDER);
setConstant("E" ,Math.E);
setConstant("Euler" ,0.577215664901533D);
setConstant("LN2" ,0.693147180559945D);
setConstant("LN10" ,2.302585092994046D);
setConstant("LOG2E" ,1.442695040888963D);
setConstant("LOG10E",0.434294481903252D);
setConstant("PHI" ,1.618033988749895D);
setConstant("PI" ,Math.PI);
pureFunctions=new TreeMap<String,FunctionHandler>(String.CASE_INSENSITIVE_ORDER);
impureFunctions=new TreeMap<String,FunctionHandler>(String.CASE_INSENSITIVE_ORDER);
DefaultImpl.registerFunctions(this);
relaxed=false;
separators=null;
offset=0;
isConstant=false;
}
/**
* Create a math evaluator with the same constants, variables, function handlers and relaxation setting as the supplied evaluator.
*/
public MathEval(MathEval oth) {
super();
operators=oth.operators;
constants=new TreeMap<String,Double>(String.CASE_INSENSITIVE_ORDER);
constants.putAll(oth.constants);
variables=new TreeMap<String,Double>(String.CASE_INSENSITIVE_ORDER);
variables.putAll(oth.variables);
pureFunctions=new TreeMap<String,FunctionHandler>(String.CASE_INSENSITIVE_ORDER);
impureFunctions=new TreeMap<String,FunctionHandler>(String.CASE_INSENSITIVE_ORDER);
pureFunctions.putAll(oth.pureFunctions);
impureFunctions.putAll(oth.impureFunctions);
relaxed=oth.relaxed;
separators=oth.separators;
offset=0;
isConstant=false;
}
// *************************************************************************************************
// INSTANCE METHODS - ACCESSORS
// *************************************************************************************************
/** Set a named constant (constant names are not case-sensitive). Constants are like variables but are not cleared by clear(). Variables of the same name have precedence over constants. */
public double getConstant(String nam) {
Double val=constants.get(nam);
return (val==null ? 0 : val.doubleValue());
}
/** Gets an unmodifiable iterable of the constants in this evaluator. */
public Iterable<Map.Entry<String,Double>> getConstants() {
return Collections.unmodifiableMap(constants).entrySet();
}
/** Set a named constant (constants names are not case-sensitive). Constants are like variables but are not cleared by clear(). Variables of the same name have precedence over constants. */
public MathEval setConstant(String nam, double val) {
return setConstant(nam,Double.valueOf(val));
}
/** Set a named constant (constants names are not case-sensitive). Constants are like variables but are not cleared by clear(). Variables of the same name have precedence over constants. */
public MathEval setConstant(String nam, Double val) {
if(constants.get(nam)!=null) { throw new IllegalArgumentException("Constants may not be redefined"); }
validateName(nam);
constants.put(nam,val);
return this;
}
/**
* Set a custom operator, replacing any existing operator with the same symbol. Operators cannot be removed, only replaced.
*/
public MathEval setOperator(Operator opr) {
if(opr.symbol>=operators.length) { // extend the array if necessary
Operator[] noa=new Operator[opr.symbol+(opr.symbol%255)+1]; // use allocation pages of 256
System.arraycopy(operators,0,noa,0,operators.length);
operators=noa;
}
operators[opr.symbol]=opr;
return this;
}
/**
* Set a pure function handler for the specific named function, replacing any existing handler for the given name; if the handler is null the function handler is removed.
* <p>
* Pure functions have results which depend purely on their arguments; given constant arguments they will have a constant result. Impure functions are rare.
*/
public MathEval setFunctionHandler(String nam, FunctionHandler hdl) {
return setFunctionHandler(nam,hdl,false);
}
/**
* Set a function handler for the specific named function optionally tagging the function as impure, replacing any existing handler for the given name; if the handler is null the function handler is removed.
* <p>
* Pure functions have results which depend purely on their arguments; given constant arguments they will have a constant result. Impure functions are rare.
*/
public MathEval setFunctionHandler(String nam, FunctionHandler hdl, boolean impure) {
validateName(nam);
if (hdl==null) { pureFunctions.remove(nam); impureFunctions.remove(nam); }
else if(impure ) { pureFunctions.remove(nam); impureFunctions.put (nam,hdl); }
else { pureFunctions.put (nam,hdl); impureFunctions.remove(nam); }
return this;
}
/** Set a named variable (variables names are not case-sensitive). */
public double getVariable(String nam) {
Double val=variables.get(nam);
return (val==null ? 0 : val.doubleValue());
}
/** Gets an unmodifiable iterable of the variables in this evaluator. */
public Iterable<Map.Entry<String,Double>> getVariables() {
return Collections.unmodifiableMap(variables).entrySet();
}
/** Set a named variable (variables names are not case-sensitive). */
public MathEval setVariable(String nam, double val) {
return setVariable(nam,Double.valueOf(val));
}
/** Set a named variable (variables names are not case-sensitive). If the value is null, the variable is removed. */
public MathEval setVariable(String nam, Double val) {
validateName(nam);
if(val==null) { variables.remove(nam); }
else { variables.put(nam,val); }
return this;
}
/** Clear all variables (constants are not affected). */
public MathEval clear() {
variables.clear();
return this;
}
/** Clear all variables prefixed by the supplied string followed by a dot, such that they match "Prefix.xxx". */
public MathEval clear(String pfx) {
variables.subMap((pfx+"."),(pfx+"."+Character.MAX_VALUE)).clear();
return this;
}
/** Get whether a variable which is used in an expression is required to be explicitly set. If not explicitly set, the value 0.0 is assumed. */
public boolean getVariableRequired() {
return relaxed;
}
/** Set whether a variable which is used in an expression is required to be explicitly set. If not explicitly set, the value 0.0 is assumed. */
public MathEval setVariableRequired(boolean val) {
relaxed=(!val);
return this;
}
private void validateName(String nam) {
if(!Character.isLetter(nam.charAt(0)) ) { throw new IllegalArgumentException("Names for constants, variables and functions must start with a letter" ); }
if(nam.indexOf('(')!=-1 || nam.indexOf(')')!=-1) { throw new IllegalArgumentException("Names for constants, variables and functions may not contain a parenthesis"); }
}
// *************************************************************************************************
// INSTANCE METHODS - PUBLIC API
// *************************************************************************************************
/**
* Evaluate this expression.
*/
public double evaluate(String exp) throws NumberFormatException, ArithmeticException {
expression=exp;
isConstant=true;
offset=0;
return _evaluate(0,(exp.length()-1));
}
/**
* Return whether the previous expression evaluated was constant (i.e. contained no variables).
* This is useful when optimizing to store the result instead of repeatedly evaluating a constant expression like "2+2".
*/
public boolean previousExpressionConstant() {
return isConstant;
}
/** Return a set of the variables in the supplied expression. Note: Substitutions which are in the constant table are not included. */
public Set<String> getVariablesWithin(String exp) {
Set<String> all=new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
String add=null;
if(separators==null) {
StringBuilder sep=new StringBuilder(10);
for(char chr=0; chr<operators.length; chr++) {
if(operators[chr]!=null && !operators[chr].internal) { sep.append(chr); }
}
sep.append("()");
separators=sep.toString();
}
for(StringTokenizer tkz=new StringTokenizer(exp,separators,true); tkz.hasMoreTokens(); ) {
String tkn=tkz.nextToken().trim();
if (tkn.length()!=0 && Character.isLetter(tkn.charAt(0))) { add=tkn; }
else if(tkn.length()==1 && tkn.charAt(0)=='(' ) { add=null; }
else if(add!=null && !constants.containsKey(add) ) { all.add(add); }
}
if(add!=null && !constants.containsKey(add)) { all.add(add); }
return all;
}
// *************************************************************************************************
// INSTANCE METHODS - PRIVATE IMPLEMENTATION
// *************************************************************************************************
/**
* Evaluate a complete (sub-)expression.
* @param beg Inclusive begin offset for subexpression.
* @param end Inclusive end offset for subexpression.
*/
private double _evaluate(int beg, int end) throws NumberFormatException, ArithmeticException {
return _evaluate(beg,end,0.0,OPERAND,getOperator('='));
}
/**
* Evaluate the next operand of an expression.
* @param beg Inclusive begin offset for subexpression.
* @param end Inclusive end offset for subexpression.
* @param pnd Pending operator (operator previous to this subexpression).
* @param lft Left-value with which to initialize this subexpression.
* @param cur Current operator (the operator for this subexpression).
*/
private double _evaluate(int beg, int end, double lft, Operator pnd, Operator cur) throws NumberFormatException, ArithmeticException {
Operator nxt=OPERAND; // next operator
int ofs; // current expression offset
for(ofs=beg; (ofs=skipWhitespace(expression,ofs,end))<=end; ofs++) {
boolean fnc=false;
double rgt=Double.NaN; // next operand (right-value) to process
for(beg=ofs; ofs<=end; ofs++) {
char chr=expression.charAt(ofs);
if((nxt=getOperator(chr))!=OPERAND) {
if(nxt.internal) { nxt=OPERAND; } // must kill operator to prevent spurious "Expression ends with a blank sub-expression" at end of function
else { break; }
}
else if(chr==')' || chr==',') { // end of subexpression or function argument.
break;
}
}
EvaluateOperand: {
char ch0=expression.charAt(beg);
boolean alp=Character.isLetter(ch0);
if(cur.unary!=LEFT_SIDE) {
if(ch0=='+') { continue; } // unary '+': no-op; i.e. +(-1) == -1
if(ch0=='-') { nxt=getOperator('±'); } // unary '-': right-binding, high precedence operation (different from subtract)
}
if(beg==ofs && (cur.unary==LEFT_SIDE || nxt.unary==RIGHT_SIDE)) {
rgt=Double.NaN; // left-binding unary operator; right value will not be used and should be blank
}
else if(ch0=='(') {
rgt=_evaluate(beg+1,end);
ofs=skipWhitespace(expression,offset+1,end); // skip past ')' and any following whitespace
nxt=(ofs<=end ? getOperator(expression.charAt(ofs)) : OPERAND); // modify next operator
}
else if(alp && nxt.symbol=='(') {
rgt=doFunction(beg,end);
ofs=skipWhitespace(expression,offset+1,end); // skip past ')' and any following whitespace
nxt=(ofs<=end ? getOperator(expression.charAt(ofs)) : OPERAND); // modify next operator
}
else if(alp) {
rgt=doNamedVal(beg,(ofs-1));
}
else {
try {
if(stringOfsEq(expression,beg,"0x")) { rgt=(double)Long.parseLong(expression.substring(beg+2,ofs).trim(),16); }
else { rgt=Double.parseDouble (expression.substring(beg ,ofs).trim() ); }
}
catch(NumberFormatException thr) {
throw exception(beg,"Invalid numeric value \""+expression.substring(beg,ofs).trim()+"\"");
}
}
}
if(opPrecedence(cur,LEFT_SIDE)<opPrecedence(nxt,RIGHT_SIDE)) { // correct even for last (non-operator) character, since non-operators have the artificial "precedence" zero
rgt=_evaluate((ofs+1),end,rgt,cur,nxt); // from after operator to end of current subexpression
ofs=offset; // modify offset to after subexpression
nxt=(ofs<=end ? getOperator(expression.charAt(ofs)) : OPERAND); // modify next operator
}
lft=doOperation(beg,lft,cur,rgt);
cur=nxt;
if(opPrecedence(pnd,LEFT_SIDE)>=opPrecedence(cur,RIGHT_SIDE)) { break; }
if(cur.symbol=='(') { ofs--; } // operator omitted for implicit multiplication of subexpression
}
if(ofs>end && cur!=OPERAND) {
if(cur.unary==LEFT_SIDE) { lft=doOperation(beg,lft,cur,Double.NaN); }
else { throw exception(ofs,"Expression ends with a blank operand after operator '"+nxt.symbol+"'"); }
}
offset=ofs;
return lft;
}
private Operator getOperator(char chr) {
if(chr<operators.length) {
Operator opr=operators[chr];
if(opr!=null) { return opr; }
}
return OPERAND;
}
private int opPrecedence(Operator opr, int sid) {
if (opr==null) { return Integer.MIN_VALUE; } // not an operator
else if(opr.unary==NO_SIDE || opr.unary!=sid) { return (sid==LEFT_SIDE ? opr.precedenceL : opr.precedenceR); } // operator is binary or is unary and bound to the operand on the other side
else { return Integer.MAX_VALUE; } // operator is unary and associates with the operand on this side
}
private double doOperation(int beg, double lft, Operator opr, double rgt) {
if(opr.unary!=RIGHT_SIDE && Double.isNaN(lft)) { throw exception(beg,"Mathematical NaN detected in right-operand"); }
if(opr.unary!=LEFT_SIDE && Double.isNaN(rgt)) { throw exception(beg,"Mathematical NaN detected in left-operand" ); }
try { return opr.handler.evaluateOperator(lft,opr.symbol,rgt); }
catch(ArithmeticException thr) {
throw exception(beg,"Mathematical expression \""+expression+"\" failed to evaluate",thr);
}
catch(UnsupportedOperationException thr) {
int tmp=beg;
while(tmp>0 && getOperator(expression.charAt(tmp))==null) { tmp--; } // set up for offset of the offending operator
throw exception(tmp,"Operator \""+opr.symbol+"\" not handled by math engine (Programmer error: The list of operators is inconsistent within the engine)");
}
}
private double doFunction(int beg, int end) {
int argbeg;
for(argbeg=beg; argbeg<=end && expression.charAt(argbeg)!='('; argbeg++) {;}
String fncnam =expression.substring (beg,argbeg).trim();
ArgParser fncargs=new ArgParser(argbeg,end);
FunctionHandler fnchdl =null;
try {
if((fnchdl=pureFunctions.get(fncnam))!=null) {
return fnchdl.evaluateFunction(fncnam,fncargs);
}
else if((fnchdl=impureFunctions.get(fncnam))!=null) {
isConstant=false; // impure functions cannot be guaranteed to be constant
return fnchdl.evaluateFunction(fncnam,fncargs);
}
fncargs=null; // suppress check for too many fncargs
}
catch(ArithmeticException thr) { fncargs=null; throw thr; }
catch(NoSuchMethodError thr) { fncargs=null; throw exception(beg,"Function not supported in this JVM: \""+fncnam+"\""); }
catch(UnsupportedOperationException thr) { fncargs=null; throw exception(beg,thr.getMessage()); }
catch(Throwable thr) { fncargs=null; throw exception(beg,"Unexpected exception parsing function arguments",thr); }
finally {
if(fncargs!=null) {
if(fncargs.hasNext()) { throw exception(fncargs.getIndex(),"Function has too many arguments"); }
offset=fncargs.getIndex();
}
}
throw exception(beg,"Function \""+fncnam+"\" not recognized");
}
private double doNamedVal(int beg, int end) {
while(beg<end && Character.isWhitespace(expression.charAt(end))) { end--; } // since a letter triggers a named value, this can never reduce to beg==end
String nam=expression.substring(beg,(end+1));
Double val;
if ((val=constants.get(nam))!=null) { return val.doubleValue(); }
else if((val=variables.get(nam))!=null) { isConstant=false; return val.doubleValue(); }
else if(relaxed ) { isConstant=false; return 0.0; }
throw exception(beg,"Unrecognized constant or variable \""+nam+"\"");
}
private ArithmeticException exception(int ofs, String txt) {
return new ArithmeticException(txt+" at offset "+ofs+" in expression \""+expression+"\"");
}
private ArithmeticException exception(int ofs, String txt, Throwable thr) {
return new ArithmeticException(txt+" at offset "+ofs+" in expression \""+expression+"\""+" (Cause: "+(thr.getMessage()!=null ? thr.getMessage() : thr.toString())+")");
}
private boolean stringOfsEq(String str, int ofs, String val) {
return str.regionMatches(true,ofs,val,0,val.length());
}
private int skipWhitespace(String exp, int ofs, int end) {
while(ofs<=end && Character.isWhitespace(exp.charAt(ofs))) { ofs++; }
return ofs;
}
// *************************************************************************************************
// INSTANCE INNER CLASSES - FUNCTION ARGUMENT PARSER
// *************************************************************************************************
/**
* An abstract parser for function arguments.
*/
public final class ArgParser
{
final int exEnd;
int index;
ArgParser(int excstr, int excend) {
exEnd=excend;
index=(excstr+1);
index=skipWhitespace(expression,index,exEnd-1);
}
/**
* Parse the next argument, throwing an exception if there are no more arguments.
* @throws ArithmeticException If there are no more arguments.
*/
public double next() {
if(!hasNext()) { throw exception(index,"Function has too few arguments"); }
return _next();
}
/**
* Parse the next argument, returning the supplied default if there are no more arguments.
*/
public double next(double dft) {
if(!hasNext()) { return dft; }
return _next();
}
private double _next() {
if(expression.charAt(index)==',') { index++; }
double ret=_evaluate(index,exEnd);
index=offset;
return ret;
}
/** Test whether there is another argument to parse. */
public boolean hasNext() {
return (expression.charAt(index)!=')');
}
int getIndex() {
return index;
}
}
// *************************************************************************************************
// STATIC NESTED CLASSES - OPERATOR
// *************************************************************************************************
/**
* Operator Structure.
* <p>
* This class is immutable and threadsafe, but note that whether it can be used in multiple MathEval instances (as
* opposed to for multiple operators in one instance) depends on the threadsafety of the handler it contains.
*/
static public final class Operator
extends Object
{
final char symbol; // parser symbol for this operator
final int precedenceL; // precedence when on the left
final int precedenceR; // precedence when on the right
final int unary; // unary operator binding: left, right, or neither
final boolean internal; // internal pseudo operator
final OperatorHandler handler;
/**
* Create a binary operator with the same precedence on the left and right.
*/
public Operator(char sym, int prc, OperatorHandler hnd) {
this(sym,prc,prc,NO_SIDE,false,hnd);
}
/**
* Create an operator which may have different left and right precedence and/or may be unary.
* <p>
* Using different precedence for one side allows affinity binding such that consecutive operators are evaluated left to right.
* <p>
* Marking an operator as unary binds the precedence for the specified side such that it always has maximum precedence when considered from the opposite side.
*/
public Operator(char sym, int prclft, int prcrgt, int unibnd, OperatorHandler hnd) {
this(sym,prclft,prcrgt,unibnd,false,hnd);
if(prclft<0 || prclft>99) { throw new IllegalArgumentException("Operator precendence must be 0 - 99"); }
if(prcrgt<0 || prcrgt>99) { throw new IllegalArgumentException("Operator precendence must be 0 - 99"); }
if(handler==null) { throw new IllegalArgumentException("Operator handler is required"); }
}
Operator(char sym, int prclft, int prcrgt, int unibnd, boolean intern, OperatorHandler hnd) {
symbol=sym;
precedenceL=prclft;
precedenceR=prcrgt;
unary=unibnd;
internal=intern;
handler=hnd;
}
public String toString() {
return ("MathOperator['"+symbol+"']");
}
}
// *************************************************************************************************
// STATIC NESTED CLASSES - OPERATION EVALUATOR INTERFACE
// *************************************************************************************************
static public interface OperatorHandler
{
public double evaluateOperator(double lft, char opr, double rgt) throws ArithmeticException;
}
// *************************************************************************************************
// STATIC NESTED CLASSES - FUNCTION EVALUATOR INTERFACE
// *************************************************************************************************
static public interface FunctionHandler
{
public double evaluateFunction(String fncnam, ArgParser fncargs) throws ArithmeticException;
}
// *************************************************************************************************
// STATIC NESTED CLASSES - DEFAULT OPERATOR/FUNCTION IMPLEMENTATION
// *************************************************************************************************
/**
* An implementation of the default supported operations and functions.
*/
static class DefaultImpl
extends Object
implements OperatorHandler, FunctionHandler
{
private DefaultImpl() {
}
// To add/remove operators change evaluateOperator() and registration
public double evaluateOperator(double lft, char opr, double rgt) {
switch(opr) {
case '=' : return rgt; // simple assignment, used as the final operation, must be maximum precedence
case '^' : return Math.pow(lft,rgt); // power
case '±' : return -rgt; // unary negation
case '*' : return lft*rgt; // multiply (classical)
case '×' : return lft*rgt; // multiply (because it's a Unicode world out there)
case '·' : return lft*rgt; // multiply (because it's a Unicode world out there)
case '(' : return lft*rgt; // multiply (implicit due to brackets, e.g "(a)(b)")
case '/' : return lft/rgt; // divide (classical computing)
case '÷' : return lft/rgt; // divide (because it's a Unicode world out there)
case '%' : return lft%rgt; // remainder
case '+' : return lft+rgt; // add/unary-positive
case '-' : return lft-rgt; // subtract/unary-negative
default : throw new UnsupportedOperationException("MathEval internal operator setup is incorrect - internal operator \""+opr+"\" not handled");
}
}
// To add/remove functions change evaluateOperator() and registration
public double evaluateFunction(String fncnam, ArgParser fncargs) throws ArithmeticException {
switch(Character.toLowerCase(fncnam.charAt(0))) {
case 'a' : {
if(fncnam.equalsIgnoreCase("abs" )) { return Math.abs (fncargs.next()); }
if(fncnam.equalsIgnoreCase("acos" )) { return Math.acos (fncargs.next()); }
if(fncnam.equalsIgnoreCase("asin" )) { return Math.asin (fncargs.next()); }
if(fncnam.equalsIgnoreCase("atan" )) { return Math.atan (fncargs.next()); }
} break;
case 'c': {
if(fncnam.equalsIgnoreCase("cbrt" )) { return Math.cbrt (fncargs.next()); }
if(fncnam.equalsIgnoreCase("ceil" )) { return Math.ceil (fncargs.next()); }
if(fncnam.equalsIgnoreCase("cos" )) { return Math.cos (fncargs.next()); }
if(fncnam.equalsIgnoreCase("cosh" )) { return Math.cosh (fncargs.next()); }
} break;
case 'e': {
if(fncnam.equalsIgnoreCase("exp" )) { return Math.exp (fncargs.next()); }
if(fncnam.equalsIgnoreCase("expm1" )) { return Math.expm1 (fncargs.next()); }
} break;
case 'f': {
if(fncnam.equalsIgnoreCase("floor" )) { return Math.floor (fncargs.next()); }
} break;
case 'g': {
// if(fncnam.equalsIgnoreCase("getExponent" )) { return Math.getExponent(fncargs.next()); } needs Java 6
} break;
case 'l': {
if(fncnam.equalsIgnoreCase("log" )) { return Math.log (fncargs.next()); }
if(fncnam.equalsIgnoreCase("log10" )) { return Math.log10 (fncargs.next()); }
if(fncnam.equalsIgnoreCase("log1p" )) { return Math.log1p (fncargs.next()); }
} break;
case 'm': {
if(fncnam.equalsIgnoreCase("max" )) { return Math.max (fncargs.next(),fncargs.next()); }
if(fncnam.equalsIgnoreCase("min" )) { return Math.min (fncargs.next(),fncargs.next()); }
} break;
case 'n': {
// if(fncnam.equalsIgnoreCase("nextUp" )) { return Math.nextUp (fncargs.next()); } needs Java 6
} break;
case 'r': {
if(fncnam.equalsIgnoreCase("random" )) { return Math.random (); } // impure
if(fncnam.equalsIgnoreCase("round" )) { return Math.round (fncargs.next()); }
if(fncnam.equalsIgnoreCase("roundHE" )) { return Math.rint (fncargs.next()); } // round half-even
} break;
case 's': {
if(fncnam.equalsIgnoreCase("signum" )) { return Math.signum (fncargs.next()); }
if(fncnam.equalsIgnoreCase("sin" )) { return Math.sin (fncargs.next()); }
if(fncnam.equalsIgnoreCase("sinh" )) { return Math.sinh (fncargs.next()); }
if(fncnam.equalsIgnoreCase("sqrt" )) { return Math.sqrt (fncargs.next()); }
} break;
case 't': {
if(fncnam.equalsIgnoreCase("tan" )) { return Math.tan (fncargs.next()); }
if(fncnam.equalsIgnoreCase("tanh" )) { return Math.tanh (fncargs.next()); }
if(fncnam.equalsIgnoreCase("toDegrees" )) { return Math.toDegrees (fncargs.next()); }
if(fncnam.equalsIgnoreCase("toRadians" )) { return Math.toRadians (fncargs.next()); }
} break;
case 'u': {
if(fncnam.equalsIgnoreCase("ulp" )) { return Math.ulp (fncargs.next()); }
} break;
// no default
}
throw new UnsupportedOperationException("MathEval internal function setup is incorrect - internal function \""+fncnam+"\" not handled");
}
static final DefaultImpl INSTANCE=new DefaultImpl();
static private final Operator OPR_EQU =new Operator('=',99,99,RIGHT_SIDE,true ,DefaultImpl.INSTANCE); // simple assignment, used as the final operation, must be maximum precedence
static private final Operator OPR_PWR =new Operator('^',80,81,NO_SIDE ,false,DefaultImpl.INSTANCE); // power
static private final Operator OPR_NEG =new Operator('±',60,60,RIGHT_SIDE,true ,DefaultImpl.INSTANCE); // unary negation
static private final Operator OPR_MLT1=new Operator('*',40 ,DefaultImpl.INSTANCE); // multiply (classical)
static private final Operator OPR_MLT2=new Operator('×',40 ,DefaultImpl.INSTANCE); // multiply (because it's a Unicode world out there)
static private final Operator OPR_MLT3=new Operator('·',40 ,DefaultImpl.INSTANCE); // multiply (because it's a Unicode world out there)
static private final Operator OPR_BKT =new Operator('(',40 ,DefaultImpl.INSTANCE); // multiply (implicit due to brackets, e.g "(a)(b)")
static private final Operator OPR_DIV1=new Operator('/',40 ,DefaultImpl.INSTANCE); // divide (classical computing)
static private final Operator OPR_DIV2=new Operator('÷',40 ,DefaultImpl.INSTANCE); // divide (because it's a Unicode world out there)
static private final Operator OPR_MOD =new Operator('%',40 ,DefaultImpl.INSTANCE); // remainder
static private final Operator OPR_ADD =new Operator('+',20 ,DefaultImpl.INSTANCE); // add/unary-positive
static private final Operator OPR_SUB =new Operator('-',20 ,DefaultImpl.INSTANCE); // subtract/unary-negative
// To add/remove operators change evaluateOperator() and registration
static void registerOperators(MathEval tgt) {
tgt.setOperator(OPR_EQU );
tgt.setOperator(OPR_PWR );
tgt.setOperator(OPR_NEG );
tgt.setOperator(OPR_MLT1);
tgt.setOperator(OPR_MLT2);
tgt.setOperator(OPR_MLT3);
tgt.setOperator(OPR_BKT );
tgt.setOperator(OPR_DIV1);
tgt.setOperator(OPR_DIV2);
tgt.setOperator(OPR_MOD );
tgt.setOperator(OPR_ADD );
tgt.setOperator(OPR_SUB );
}
// To add/remove functions change evaluateOperator() and registration
static void registerFunctions(MathEval tgt) {
tgt.setFunctionHandler("abs" ,INSTANCE);
tgt.setFunctionHandler("acos" ,INSTANCE);
tgt.setFunctionHandler("asin" ,INSTANCE);
tgt.setFunctionHandler("atan" ,INSTANCE);
tgt.setFunctionHandler("cbrt" ,INSTANCE);
tgt.setFunctionHandler("ceil" ,INSTANCE);
tgt.setFunctionHandler("cos" ,INSTANCE);
tgt.setFunctionHandler("cosh" ,INSTANCE);
tgt.setFunctionHandler("exp" ,INSTANCE);
tgt.setFunctionHandler("expm1" ,INSTANCE);
tgt.setFunctionHandler("floor" ,INSTANCE);
//t.setFunctionHandler("getExponent" ,INSTANCE); // needs Java 6
tgt.setFunctionHandler("log" ,INSTANCE);
tgt.setFunctionHandler("log10" ,INSTANCE);
tgt.setFunctionHandler("log1p" ,INSTANCE);
tgt.setFunctionHandler("max" ,INSTANCE);
tgt.setFunctionHandler("min" ,INSTANCE);
//t.setFunctionHandler("nextUp" ,INSTANCE); // needs Java 6
tgt.setFunctionHandler("random" ,INSTANCE,true); // impure
tgt.setFunctionHandler("round" ,INSTANCE);
tgt.setFunctionHandler("roundHE" ,INSTANCE); // round half-even
tgt.setFunctionHandler("signum" ,INSTANCE);
tgt.setFunctionHandler("sin" ,INSTANCE);
tgt.setFunctionHandler("sinh" ,INSTANCE);
tgt.setFunctionHandler("sqrt" ,INSTANCE);
tgt.setFunctionHandler("tan" ,INSTANCE);
tgt.setFunctionHandler("tanh" ,INSTANCE);
tgt.setFunctionHandler("toDegrees" ,INSTANCE);
tgt.setFunctionHandler("toRadians" ,INSTANCE);
tgt.setFunctionHandler("ulp" ,INSTANCE);
}
}
// *************************************************************************************************
// STATIC PROPERTIES
// *************************************************************************************************
/** Operator/operand on on the left. */
static public final int LEFT_SIDE ='L';
/** Operator/operand on on the right. */
static public final int RIGHT_SIDE='R';
/** Operator/operand side is immaterial. */
static public final int NO_SIDE ='B';
/** Implementation for the default operators. */
static public final OperatorHandler DFT_OPERATOR_HANDLER=DefaultImpl.INSTANCE;
/** Implementation for the default function (java.lang.Math). */
static public final FunctionHandler DFT_FUNCTION_HANDLER=DefaultImpl.INSTANCE;
static private final Operator OPERAND=new Operator('\0',0,0,NO_SIDE,false,null); // special "non-operator" representing an operand character
// *************************************************************************************************
// STATIC METHODS - UTILITY
// *************************************************************************************************
} // END CLASS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment