JFormula

http://www.japisoft.com/formula

v 3.6

Formula is a library for evaluating various mathematical expressions. User can evaluate instantly a string setting variables, adding its own functions or operators.

Main features :

I. Usage

a. Simple usage

Here a straightforward way for evaluating an expression :

  FormulaFactory mFac = FormulaFactory.getInstance();
  Formula mForm = mFac.getFormula( YOUR_EXPRESSION );   
  Variant v = mForm.evaluate();


YOUR_EXPRESSION is a mathematical expression. It accepts a set of operators : +, -, *, /, %, ^ where
'%' is for the modulo operator and '^' for the power operator. Any parenthesis level is supported, the expression
contains both variables and functions. In this last case, user can handle some delegates for resolving symbols or
functions dynamically.

JFormula can work with java.lang.BigDecimal for high precision computing. Such option is available setting the HighPrecisionMode property inside the Formula instance or by getting a FormulaFactory instance with
FormulaFactory.getInstance( true )
. Note that some operators cannot works for this mode.

The expression contains double, string, boolean or list value. The double value supports the following format :
"figure* . figure * E[+,-] figure*". Figure is a number between 0 and 9. '*' means any numbers. So,
it is valid to write '.43' rather than '0.43'.

'[ expression ']' is for the absolute value. Thus, [ 1 - 2 ] = 1

The result of an expression evaluation is a string, a boolean, a double, a bigdecimal or a list. Each result is embedded inside a Variant. A list is a set of value for sample (1,2,"ok") is a list. A list is stored inside a java.util.Vector collection. A list contains a set of Variant.

Operators :

Type
Operator
Example
Numerical operators
+ - * / : Basic operators
% : Modulo operators (not in high precision)
^ : Power operators (not in high precision)
(-1 + 50*2 ) / ( 2^4 )
Boolean operators
~, xor : operators
&&, and : And operators
||, or : Or operators
!, not : Not operators
< : less operator
> : great operator
<= : less or equal operator
>= : great or equal operator
==, equals : equal operators
!=, <> : not equal operators


!(A && (B < 10))  |  NOT ( A XOR ( B equals C ) )
A != 2 || B > 2
"string1" == "string2"
A or B
A or ( B <> C )
String operators
== : 2 strings equals
!= : 2 strings not equals
<> : 2 strings not equals
< : The first string less lexically than
the second one
> : The first string great lexically than
the second one
<= :The first string less or equals lexically than
the second one
>= : The first string great or equals lexically than
the second one
+ : Concat string
"string1" == "string2"  : false
"string1" + "a" : "string1a"
"abc" > "aaa" : true
"zyx" < "bcd" : false



List operators
+ : Concat two lists
- : Substract a list to another one
in : Test if an element is inside a list
(1,2)+(3,4) = (1,2,3,4)
(1,2) + 3 = (1,2,3)
3+(1,2)=(1,2,3)
(1,2,3,4)-(3,4)=(1,2)
(1,2,3,4)-3=(1,2,4)
2 in (1,2,3)=true
4 in (1,2,3)=false
Other operators
= : set a variable operator
[] : absolute value
² : power 2 operator
% : Percent operators
A = [ 2 - A ] * 2

10%=0.1
Conditional operators
if then
if then else
if ( A > 2 ) then "Ok"
if ( A <=2 ) THEN B=3 else B=4

You can access to the inner evaluated tree by calling the parse method which returns an AbstractNode root.

User can access to any symbol value by calling getValueForSymbol which returns a Variant. If the symbol name
is not known an exception SymbolResolverException will be thrown.

Note : When using a "IF condition THEN expression1 / ELSE expression2" expression the evaluate method returns the last evaluated expression expression1 or expression2 depending on the value of condition. If you use an "IF condition THEN expression" and if the condition is false, the evaluate method will return a special Variant having the hasNoResult method to true.

Multiple expressions :

When using several expressions the evaluate method returns the last expression value.

- Several lines :

Sample:
A=1
B=A+1
A + B
Formula f = new Formula( "A=1\nB=A+1\nA+B" );
Variant res = f.evaluate();
double r = res.getDoubleValue(); // 3
f.getValueForSymbol( "A" ).getDoubleValue() // 1
f.getValueForSymbol( "B" ).getDoubleValue() // 2

Formula manages several lines expression. Each line is a sub-expression that can update the
symbol table. When a sub-expression update the symbol table, JFormula calls setValueForSymbol with
the new symbol result. For our sample, JFormula will call while evaluating
setSymbolValue( "A", new Variant( "1" ) )
setSymbolValue( "B", new Variant( "2" ) )

- The ';' separator :

Multiple expressions can be used on one line using the ';' operator.

Sample:
A=1;B=A+1;A+B

Formula f = new Formula( "A=1;B=A+1;A+B" );
Variant res = f.evaluate();
double r = res.getDoubleValue(); // 3
f.getValueForSymbol( "A" ).getDoubleValue() // 1
f.getValueForSymbol( "B" ).getDoubleValue() // 2

b. Resolvers

A resolver is a delegate for computing a function or a variable. The first case is done by the FunctionResolver interface, the
second one is realized by the SymbolResolver interface.

Here a sample for usage :


public class CustomResolver implements SymbolResolver,FunctionResolver {

    /** Resolver for a symbol value */
    public Variant getValue( String symbol ) {
    if ( "PI".equals( symbol ) )
        return new Variant( Math.PI );
    else {
        throw new SymbolResolverException( "Unknown " + symbol );
    }
    }

    /** Resolver for a "sumi function" */
    public Variant getValue( String function, ListOfArgument args ) {
    if ( "sumi".equals( function ) ) {
       for ( int i = 0; i < args.getArgumentCount(); i++ ) {
           Variant mV = args.getArgumentAt( i );
           if ( mV.isDouble() ) {
          int a = (int)mV.getDoubleValue();
          return new Variant( (double)( ( a * ( a + 1 ) ) / 2 ) );
           }
       }
    }
    throw new FunctionResolverException( "Unknown " + function );
    }  
}



In this sample, we support for the PI symbol and the "sumi" function.  Here an expression using both this
support : "2 + cos( 2 * PI ) + sumi( 3 )".

Note that if you can't evaluate a symbol, you should throw SymbolResolverException.
This is the same case for a function by throwing a FunctionResolverException. It can be a bad idea to return
always the same value like '0' or '1' for unknown functions or symbols.

To declare your delegate use the addFunctionResolver and the addSymbolResolvermethods from the Formula objet.

  FormulaFactory mFac = FormulaFactory.getInstance();
  Formula mForm = mFac.getFormula( "2 + cos( 2 * PI ) + sumi( 3 )" );
  CustomResolver mResolver = new CustomResolver();
  mForm.addSymbolResolver( mResolver );
  mForm.addFunctionResolver( mResolver );
  mForm.evaluate();

c. Symbol table

A symbol table is a global table for retreiving a variable value. It means you reset some variable values
before calling an expression evaluation.

  FormulaFactory mFac = FormulaFactory.getInstance();
  Formula mForm = mFac.getFormula( "a + b + c" );
  mForm.setSymbolValue( "a", 10 );
  mForm.setSymbolValue( "b", 20 );
  mForm.setSymbolValue( "c", 30 );
  mForm.evaluate();

In this last example, we reset three symbols "a'", "b", and "c". You can remove a symbol by the removeSymbolValue.

The FormulaContext property (new Formula( another context ) or setShareFormulaContext ) lets you build a share symbol table or a share resolvers. Then the evaluation of your expression will follow this algorithm :

1. Find a symbol for the symbol table
2. Asks for a resolver for the symbol value
3. Asks for a parent the step 1 and 2.

JFormula includes default symbols : PI, E, true, false

true and FALSE

II. Library

Formula lets you inserting your mathematical library. A library is based on the Lib interface. This interface gives you
accesses on a set of functions. Each function has a name and a set of parameters support.  A parameter is a "String"
"Boolean", a "Double", a "BigDecimal" or a "List" value, both types are inside the Variant object. These functions are independant of the Formula resolver.

It is possible to dynamically install or improve a library thanks to the LibManager. By default the LibManager will support
the "Standard" library with 24 mathematical functions.

a. Standard library

The standard library is available by the com.japisoft.formula.lib.standard.Standard class.

Here the following supported function :

Function
Role
acos
Arc cosine with a radian argument
asin
Arc sine with a radian argument
atan
Arc tan with a radian argument
avg
The average of the arguments
ceil the smallest (closest to negative infinity) double value that is greater than or equal to the argument
cos
Cosine with a radian argument
exp
Compute the euler's number e raised to the power of the argument
floor the largest (closest to positive infinity) double value that is less than or equal to the argument and is equal to a mathematical integer
int
Convert the double argument to integer
logn
Natural logarithm in n base : logn( BASE, VAL)
log 10
Natural logarithm in 10 base
log
Natural logarithm in e base
max
The maximal value of the arguments
min
The minimal value of the arguments
pow
The first argument power the second one
prod
The product of the arguments
random
A random value from 0 to 1
round The closest long to the argument
sin
Sine with a radian argument
sqrt
Square root
sum
Sum the arguments
tan
tan with a radian argument
degTorad
Convert angle from degrees to radians
radTodeg
Convert angle from radians to degrees
strlen
Compute the length of a string
strcontains
return true if the second string is includes in the first one
strget
extract the string starting from 1 to a position starting from 1 : ex strget( "abc", 1, 2 ) == "ab"

b. Custom Library

Here we add a new Function inside our standard library.

import com.japisoft.formula.*;
import com.japisoft.formula.lib.*;
import com.japisoft.formula.lib.standard.*;

/** Sample for showing the content of the default mathematical library. This
 * library is increased by a new function that compute the opposite of its first
 * argument */
public class Demo {

    static class CustomFunction extends AbstractFunction {
    public CustomFunction() {
        super( "opp", 1 );
    }
    public Variant evaluate( ListOfArgument args ) {
        return new Variant( -( getFirstArgument( args ) ) );
    }
    }

    public static void main( String[] _args) {
    Lib mLib = LibManager.getLib();
    System.out.println( "Current mathematical library :" + mLib );

    // Show all functions for the current library

    Function[] mFunctions = mLib.getFunctions();
    for ( int i = 0; i < mFunctions.length; i++ ) {
        System.out.println( "- " + mFunctions[ i ] );
    }

    // Add a new function

    ((AbstractLib)mLib).install( new CustomFunction() );

    // Evaluate it

    ListOfArgument args = new ListOfArgument();
    args.addElement( new Variant( 10.4 ) );

    System.out.println( "Evaluate new function : " + mLib.evaluate( "opp", args ) );

    }
}


In this last sample we get the current library and we list available functions. We install a new function in the standard library with the install method. Note that this last method is only available for an AbstractLib class. We test in the final step our new function by calling the evaluate method.

If your new function contains 0 or more double argument, it may be better to inherit from the AbstractFunction class. If you don't care about the number of argument you must use the ANY constant value.

Here a sample with the MaxFunction :

import com.japisoft.formula.ListOfArgument;
import com.japisoft.formula.Variant;

/**
 * Compute the max values
 * @author (c) 2002 JAPISoft
 */
public class MaxFunction extends AbstractFunction{
    public MaxFunction() {
    super( "max", ANY );
    }

    public Variant evaluate( ListOfArgument args ) {
    double max = Double.MIN_VALUE;
    for ( int i = 0; i < args.getArgumentCount(); i++ ) {
        if ( args.getArgumentAt( i ).isDouble() ) {
        max = Math.max( max, args.getArgumentAt( i ).getDoubleValue() );
        }
    }
    return new Variant( max );
    }

    // true if there's at least one double argument
    public boolean matchArgument( ListOfArgument args ) {
    return hasDoubleArgument( args );
    }
}


The evaluate method enumerates each argument and computes the max value. The matchArgument method has a role to declare invalid a "max" function with no double value (empty or with string).

III. Operators

JFormula lets the user inserting its own operator or overriding a default one. This is possible with the OperatorFactory available inside the Formula object.

There's three kinds of operators :
Here the default operators for the default precision mode

Operator type
Class
Unary operators
ABSOperator ([A-B])
MINUSOperator (-A)
NOTOperator (!A)
PERCENTOperator (A%)
Binary operators
ADDOperator (A+B)
ANDOperator (A&&B)
ASSIGNOperator (A=B)
DIVOperator (A/B)
EQOperator (A==B)
GREATEQOperator (A>=B)
GREATOperator (A>B)
IFOperator (IF THEN)
INOperator (A IN B)
LESSEQOperator (A <= B)
LESSOperator (A<B)
MINUSOperator (A-B)
MODOperator (A%B)
MULOperator (A*B)
NOTEQOperator (A!=B)
OROperator (A||B)
POWEROperator (A^B)
XOROperator (A~B)
Ternary operators
ELSEOperator (IF THEN ELSE)

Each operator has a default name available from the constant NAME (like NOTOperator.NAME). This is this name you must use if you wish to override an operator.

a. Overriding an operator

Here a sample for overriding the '-' for working also with string arguments

/** Here a new MINUS operator */
class NewMinusBinaryOperators implements BinaryOperator {
        public Object eval(OperatorContext context) throws EvaluateException {
            Object o = context.getValue1();
            if ( o instanceof String ) {
                String s1 = (String)o;
                String s2 = (String)context.getValue2();
                int i = 0;
                for ( i = 0; i < Math.min( s1.length(), s2.length() ) && s1.charAt( i ) == s2.charAt( i ); i++ ) {}
                if ( i > 0 )
                    return s1.substring( i );
            }
            // Here a trivial and not optimized way to return the default operator value
            MINUSOperator delegate = new MINUSOperator();
            return delegate.eval( context );
        }
    }


The eval method returns the operator result. This method takes one OperatorContext argument. At this step the values for the operands are not evaluated and the user must call getValue1, getValue2 or getValue3 for the right operand. The first operand being available with the getValue1, the second by the getValue2 and the last by the getValue3. Note that calling getValuex is time consumming so you must avoid calling it several times.

We can override our Minus operator calling :

Formula f...
f.getOperatorFactory().setBinaryOperator( MINUSOperator.NAME, new NewMinusBinaryOperators() );


Note that there's two factories for the operators. One for the standard precision mode and one for the high precision mode. In the high precision mode, the operator waits for java.lang.BigDecimal value rather than java.lang.Double value. When switching to the high precision mode a new factory is used so if you wish to override an operator, you must know in which mode you are and update the factory AFTER choosing your usage mode.

b. Creating your operator

Here a sample adding the 'zzz' binary operator

class ZZZBinaryOperator implements BinaryOperator {
        public Object eval(OperatorContext context) throws EvaluateException {
            Double d1 = (Double)context.getValue1();
            Double d2 = (Double)context.getValue2();
            return new Double( ( d1.doubleValue() + d2.doubleValue() ) / 2.0 );
        }
}


Formula f...
f.getOperatorFactory().setBinaryOperator( "zzz", new ZZZBinaryOperator() );

Now we can evaluate 1 zzz 2 = 1.5

Note : You can't create a ternary operator like IF THEN ELSE. For this case you must create a function.


© JAPISoft