You will notice that when you evaluate something as simple as
"8 - 7.9" the result will be 0.09999999999999964 rather than 0.1.
These inaccuracies are the result of floating point arithmetic. Internally,
Jep uses the double
type to represent numbers by default. Unfortunately,
even for trivial calculations such as "8 - 7.9" the calculation
can not be performed accurately.
You will notice the same if you run the following code.
double a=8, b=7.9; System.out.println("a-b = " + (a-b)); //prints a-b = 0.09999999999999964
So this is not a Jep flaw, just a limitation of using floating point numbers. Although floating point numbers are accurate enough for many applications, these types of errors should not be ignored in applications where accuracy is critical.
By using System.out.printf results from Jep can be displayed to a given number of decimal places. For example
jep.parse("8-7.9"); double res = jep.evaluateD(); System.out.printf("%.3f",res);
Which will print the result to three decimal places: 0.100. See java.util.Formatter for details on formatting output. The java.text.NumberFormat class can also be used to format results.
Jep has a round function, which can round arguments to a given number of decimal places.
round(8-7.9,1)
will round results to one decimal place.
To avoid this problem, use the BigDecimal components as described in the BigDecimal section. They allow you to perform calculations with arbitrary accuracy unlike floating point arithmetic.
If Jep is throwing an exception "EvaluationException: Could not evaluate XXX : no
value set for the variable.
" during evaluation, then you are running in to issues with null values.
It is important to note that by default variables whose values have not been set by either
jep.addVariable(name,value);
or by parsing an expression like
XXX = 123.456;
will remain initialized to null. So when you attempt to evaluate and expression with a null variable, the evaluator will by default throw an exception when the null value is encountered.
There are a several of ways around this:
((StandardEvaluator)jep.getEvaluator()).setTrapNullValues(false);or
((FastEvaluator)jep.getEvaluator()).setTrapNullValues(false);in which case null values will be passed to the individual functions.
jep.getVariableFactory().setDefaultValue(Double.valueOf(0.0));
Object MyNULL = new Object(); // create a null value jep.addConstant("NULL",MyNULL); // define a constant in the parser jep.addVariable("x",MyNULL); // add a variable with the NULL value jep.parse("y=NULL"); // expression assigning a variable to NULL jep.parse("x==NULL"); // Compare a variable to the NULL value
null
as an argument will return null
. For instance
null+5
will be null
. The package takes existing operators and functions
and adds a wrapper class which will test if a null value is input;
if so, null is returned, otherwise the original function is used. The NullWrappedOperatorTable
and
NullWrappedFunctionTable
wrap all the existing operators and functions. To set this up use
jep.setComponent(new NullWrappedOperatorTable( (OperatorTable2) jep.getOperatorTable(),true)); jep.setComponent(new NullWrappedFunctionTable(jep.getFunctionTable())); jep.setComponent(new StandardConfigurableParser()); ((FastEvaluator) jep.getEvaluator()).setTrapNullValues(false);
The operator table also adds an additional operator,
the null safe equals <=>
which works like equals,
but if both arguments are null will return true.
This needs the StandardConfigurableParser
to for the operator to be recognized when parsing.
The logical operators work like those in SQL so null || true
is true, null || false
is null,
null && true
is null and null && false
is false. See
NullWrappedLazyLogical
for the complete truth tables.
The If
function just tests the first argument for null, returning null if it is. If the first argument is true or false it will
return the second or third argument respectively, these arguments can be null.
The names of classes have been changed in version 3.5 and they generally begin NullWrapped...
.
There can be several reasons for EvaluationExceptions to be thrown. Of course it is best to look at the message in the exception for more details.
If you are seeing
com.singularsys.jep.EvaluationException: Could not evaluate XXX : no value set for the variable.
then refer to "How should I handle NULL values?" in this FAQ.
Other evaluation exceptions are typically due functions or operators not being compatible with the data types passed in. Since Jep only checks data types during evaluation, you will not see these issues while parsing an expression.
To do this, you need to use the Configurable Parser which allows great control over the input syntax. The following code sets up the parser with the standard options but using identifiers with a dot in them as well by using the factory method com.singularsys.jep.configurableparser.matchers.IdentifierTokenMatcher.dottedIndetifierMatcher(). The final step in setting up is to remove the dot-product "." operator to prevent any ambiguity.
ConfigurableParser cp = new ConfigurableParser(); cp.addHashComments(); cp.addSlashComments(); cp.addDoubleQuoteStrings(); cp.addWhiteSpace(); cp.addExponentNumbers(); cp.addOperatorTokenMatcher(); cp.addSymbols("(",")","[","]",","); cp.setImplicitMultiplicationSymbols("(","["); // Sets it up for identifiers with dots in them. cp.addTokenMatcher(IdentifierTokenMatcher.dottedIndetifierMatcher()); cp.addSemiColonTerminator(); cp.addWhiteSpaceCommentFilter(); cp.addBracketMatcher("(",")"); cp.addFunctionMatcher("(",")",","); cp.addListMatcher("[","]",","); cp.addArrayAccessMatcher("[","]"); // Construct the Jep instance and set the parser jep = new Jep(cp); // Remove the dot operator OperatorTable2 ot = (OperatorTable2)jep.getOperatorTable(); ot.removeOperator(ot.getDot()); //notify other components of change in operator table jep.reinitializeComponents();
The memory footprint of Jep can be reduced in a number of ways.
StandardConfigurableParser
rather than the default parser.
This takes considerably less runtime space (4K) compared to 51K. NullParser
which take virtually no space.
This can be used with SerializableExpression where a compact representation of
an expression can stored on disk.FunctionTable
component rather than the StandardFunctionTable
.
Any functions needed can then be individually loaded using Jep.addFunction()
.For example you could create a Jep instance with no loaded functions with:
Jep jep1 = new Jep(new FunctionTable(), new StandardConfigurableParser());And you can create a Jep instance without parser, without functions and without the ability to print with:
Jep jep2 = new Jep(new FunctionTable(), NullParser.NULL_PARSER, PrintVisitor.NULL_PRINT_VISITOR);
The com.singularsys.jepexamples.diagnostics.ExamineSizes program gives give a detailed breakdown of memory usage.
To reduce the disk space the com/singularsys/jep/misc
,
com/singularsys/jep/walker
, com/singularsys/jep/bigdecimal
packages are all optional.
None of the classes in the com/singularsys/jepexamples
or com/singularsys/jeptest
directories are needed.
If the configurable parser is used then in the com/singularsys/jep/parser
directory the JccParser*
classes can all be skipped.
Alternatively if the standard parser is used then the com/singularsys/jep/configurableparser
package can be skipped completely.
The jep-java-3.5-min.jar
jar file is a fairly small example with a full set of functions and a working console application.
which is less that 200K in size.
The size of expressions which can be worked with is mainly limited by the stack size allocated to java. This can be increased
by using the -Xss512k
argument to the Java VM.
The ConfigurableParser
can generally parse larger expressions than the standard parser.
The
PostfixEvaluator has
been written to not make heavy use of the stack and can evaluate expressions with over a million nodes.
The DestructiveTests
application tests the size of expressions which can be parsed and evaluated.