The com.singularsys.extensions.field package provides a type system for Jep, allowing use of new datatype like rational numbers and matrices.
New datatypes can be added to the Jep system by implementing the
FieldI interface.
This defines methods such as add(Object l, Object r)
which the field must implement.
Different fields can be mixed together using to produce a complex type system.
In general each method takes one or two Objects as input and returns an Object.
It should test its arguments to see if they are of the correct type and
if they are the method should return the result.
If the argument are not of a type the field can work with then
the methods
should return null
. In rare circumstances they can also
throw an EvaluationException. A typical implementation for add
would be
public Object add(Object l, Object r) throws EvaluationException { if(l instanceof Double && r instanceof Double) { double ld = ((Double) l).doubleValue(); double rd = ((Double) r).doubleValue(); return Double.valueOf(ld+rd); } return null; }
An implementation should implement:
add(l,r)
, sub(l,r)
, mul(l,r)
, div(l,r)
,
pow(l,r)
, mod(l,r)
, neg(l)
eq(l,r)
, ne(l,r)
, lt(l,r)
, gt(l,r)
,
le(l,r)
, ge(l,r)
and(l,r)
, or(l,r)
, not(l)
.
getZero()
and getOne()
Often some of these methods will just return null
.
The arithmetic methods all return Object, and comparison and logical methods
return Boolean values.
Fields can work with Jep using the
FieldOperatorTable.
This links evaluation of expression containing the standard operators +, -, *, /
etc.
with the corresponding method in a field.
A field is specified in the FieldOperatorTable's constructor and this table is then
used in the Jep constructor.
For example to set up arithmetic on doubles would be
DoubleField df = new DoubleField(); FieldOperatorTable fot = new FieldOperatorTable(df); Jep jep = new Jep(fot); Node n = jep.parse("1.0 + 2.0 * 3.0"); Object res = jep.evaluate(n);
It is typical to mix a few different fields together. For example the DoubleField has null
implementations for the logical operators. The BooleanField provides implementations for these
and the two can be combined using a
FieldCollection.
Fields can be added to the collection using the
addField(FieldI field)
method or the
FieldCollection(FieldI... fields)
constructor. The order in which fields are added is important,
During evaluation the it will test the result from the first field, if that is null it will then try the remaining fields in turn.
The FieldCollection
implements FieldI
so can be used in the
FieldOperatorTable
.
An implementation which combine the DoubleField and BooleanField might be
DoubleField df = new DoubleField(); BooleanField bf = new BooleanField(); FieldCollection fc = new FieldCollection(); fc.addField(df); fc.addField(bf); FieldOperatorTable fot = new FieldOperatorTable(fc); Jep jep = new Jep(fot); Node n = jep.parse("1.0 < 2.0 && 2.0 < 3.0"); Object res = jep.evaluate(n);
For some Fields it may also be necessary to provide a implementation of a
NumberFactory.
The main method, createNumber(String value)
, is used by the parser
to converter numerical strings in the input into an appropriate type.
For example an implementation
which uses the arbitrary-precision BigInteger type would need the
BigIntegerNumberFactory
.
BigIntegerField bif = new BigIntegerField(); BigIntegerNumberFactory binf = new BigIntegerNumberFactory(); FieldOperatorTable fot = new FieldOperatorTable(bif); Jep jep = new Jep(fot,binf); Node n = jep.parse("1000^5"); Object res = jep.evaluate(n);
The com.singularsys.extensions.field.implementations provide a number of standard implementations.
Double
s.
Integer
s,
requires an
IntegerNumberFactory.
Integer
s and the new
addExact(int,int) etc.
of java.lang.Math methods of Java 1.8. These throw ArithmeticException if results overflow. The Jep implementation wrap these exceptions in
EvaluationExceptions.
BigInteger
s,
requires a
BigIntegerNumberFactory.
BigDecimal
s. Can be set to use a specific NumberContext
to set the level of accuracy required.
Requires a
BigDecNumberFactory.
BigIntegers
,
and implemented using the
Rational data-type.
Requires a
RationalNumberFactory
FieldDecorators
allow a form of type casting with Field elements, allowing different
types to be mixed.
The above fields only work on their corresponding types, so a DoubleField
will only accept Double
arguments and only produce Double
results.
A FieldDecerator
can be wrapped around a field converting the input and output of each
method.
For example the
NumberToDoubleDecorator
converts Integers and other Number types to Doubles. This can be with a DoubleField
and allows Integer
s and Double
s to be added together.
A typical setup might be
// Can add int+int IntegerField intf = new IntegerField(); // Can add double + double DoubleField df = new DoubleField(); // A decorator applied to the DoubleField // Can add double+double and int+double NumberToDoubleDecorator ndd = new NumberToDoubleDecorator(df); // Can add int+int, double+double and int+double FieldCollection fc = new FieldCollection(intf,ndd); FieldOperatorTable fot = new FieldOperatorTable(fc); IntDoubleNumberFactory idnf = new IntDoubleNumberFactory(); Jep jep = new Jep(fot,idnf); // Adding integer + integer result in an integer Object res = jep.evaluate(jep.parse("2+3")); assertTrue(res instanceof Integer); // Adding double + double result in a double res = jep.evaluate(jep.parse("2.5+3.5")); assertTrue(res instanceof Double); // Adding an integer + double result in a double res = jep.evaluate(jep.parse("2+3.5")); assertTrue(res instanceof Integer);
The above example uses an
IntDoubleNumberFactory
which can parse numbers as either Integer
s or Double
s. During evaluation the
IntergerField will first be used to try and evaluate the result, if this fails then
the decorator will convert both arguments to doubles and use the
DoubleField to do the evaluation.
The abstract base class FieldDecorators define two abstract methods convertInput(Object in) and convertOutput(Object out) which must be implemented by sub-classes to convert the inputs and outputs to the correct type of a field. A field will be specified in its constructor.
Four decorators are provided
Three base classes are provided to make it simpler to construct fields.
The
AbstractComparativeField
replaces the six comparative operations eq, ne, lt, le, gt, ge
by a single
Integer cmp(Object l,Object r)
which returns -1, 0, +1 depending on the comparison.
The GenericField<E>
reduces the need for type casts.
Rather than writing methods like Object add(Object l,Object r)
with type casting code
a single method E cast(Object l)
takes care of type casting
and methods with a more specific type E addG(E l, E r)
method are used for evaluation.
The GenericPowerField<E>
extends the GenericField
and adds a method for calculating integer
powers of a field element.
A field for String could be implemented in a number of ways. The basic method requires all methods to be implemented
// just need to implement additive and equality operations public class StringField implements FieldI { private static final long serialVersionUID = 1L; // If both arguments are string then concatenate the results // otherwise return null @Override public Object add(Object l, Object r) throws EvaluationException { if(l instanceof String && r instanceof String) return ((String) l)+((String)r); return null; } // Test equality of two strings, // returns true is equal, false if not equal or null if not strings @Override public Boolean eq(Object l, Object r) throws EvaluationException { if(l instanceof String && r instanceof String) { String ls = (String) l; String rs = (String) r; boolean res = ls.equals(rs); return res; } return null; } // Other comparison operations need to be implemented public Boolean ne(Object l, Object r) { .... } public Boolean gt(Object l, Object r) { .... } public Boolean ge(Object l, Object r) { .... } public Boolean lt(Object l, Object r) { .... } public Boolean le(Object l, Object r) { .... } // Null results for other operators public Object sub(Object l, Object r) { return null; } public Object neg(Object l) { return null; } public Object mul(Object l, Object r) { return null; } public Object div(Object l, Object r) { return null; } public Object mod(Object l, Object r) { return null; } public Object pow(Object l, Object r) { return null; } public Boolean and(Object l, Object r) { return null; } public Boolean or(Object l, Object r) { return null; } public Boolean not(Object l) { return null; } }
An alternative is to extend the abstract base class
AbstractComparativeField
fields. Here the single cmp
method needs to be implemented
rather than the six comparisons.
public class StringField2 extends AbstractComparativeField { private static final long serialVersionUID = 1L; // If both arguments are string then concatenate the results // otherwise return null @Override public Object add(Object l, Object r) throws EvaluationException { if(l instanceof String && r instanceof String) return ((String) l)+((String)r); return null; } // Compare two string, // returns -1, 0 or 1 depending on the comparisons @Override public Integer cmp(Object l, Object r) throws EvaluationException { if(l instanceof String && r instanceof String) { String ls = (String) l; String rs = (String) r; int res = ls.compareTo(rs); return res; } return null; } // Null results for other fields public Object sub(Object l, Object r) { return null; } public Object neg(Object l) { return null; } public Object mul(Object l, Object r) { return null; } public Object div(Object l, Object r) { return null; } public Object mod(Object l, Object r) { return null; } public Object pow(Object l, Object r) { return null; } public Boolean and(Object l, Object r) { return null; } public Boolean or(Object l, Object r) { return null; } public Boolean not(Object l) { return null; } }
This base class is not always appropriate. For a double's there are problems with comparing NaN's and the individual lt, gt, le, lt methods need to be implemented.
Another option would be to use the generic base class GenericField<String>.
public class StringField3 extends GenericField<String> { private static final long serialVersionUID = 1L; // Cast the argument to a String or return null @Override public String cast(Object l) throws EvaluationException { if(l instanceof String) return ((String) l); return null; } // No need for casting in individual operations @Override public String addG(String l, String r) throws EvaluationException { return l + r; } // A single method for all comparison operations @Override public Integer cmpG(String l, String r) throws EvaluationException { return l.compareTo(r); } // other methods just return null public String subG(String l, String r) { return null; } public String negG(String l) { return null; } public String mulG(String l, String r) { return null; } public String divG(String l, String r) { return null; } public String modG(String l, String r) { return null; } public String powG(String l, String r) { return null; } // no need to implement comparison operations le(l,r) etc. // or logical operations and(l,r), or(l,r), not(l) }
The optional com.singularsys.extensions.field.functions provides binomial and factorial functions which can work with any field.
The FieldConsole is a console application which allow different fields to be used.
The ExtSpeedTest compare the speed of evaluation using different fields. Using fields is slightly slower than a Standard Jep implementation.
The com.singularsys.exttests.field package has a number of test cases for checking various implementations of Fields.
The com.singularsys.extensions.matrix
package offers a framework for working with
vectors and matrices (Documentation, Javadoc).