JFormula


http://www.japisoft.com

Support
: http://www.japisoft.com/forum/index.php

Feedback : http://www.japisoft.com/contact.html


v 2.6

Formula is a library for evaluating mathematical expression.

Main features :
JFormula is a shareware, it is free to try for 30 days, else you must register the full version at : http://www.japisoft.com/buy.html

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.

The expression contains double, string or boolean 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 or a Boolean or a Double value.

Operators :

Numerical operators
+, -, *, /, %, ^  (-1 + 50*2 ) / ( 2^4 )
Boolean operators
~, &&, ||, !, <, >, <=, >=, ==, or, and, not     !(A && (B < 10))  |  NOT ( A XOR ( B equals C ) )
Other operators
=, []
²
A = [ 2 - A ] * 2

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

Boolean shortcut operator meaning :
You can write boolean shortcut or full operator name or mix it in your boolean expression :

!( A && B ) ~ ( A || B )  <=> NOT ( A AND B ) XOR ( A OR B )

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

Note : If you use the NOT operator rather than the '!' shortcut you must add at least one space after for avoiding conflict with kind of function name NOT(...)

Multiple expression 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" ) )

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.

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 symbol "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

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" or "Double" 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 21 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
cos
Cosine with a radian argument
exp
Compute the euler's number e raised to the power of the argument
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
sin
Sine with a radian argument
sqrt
Square root
sum
Sum the arguments
tan
tan with a radian argument
deg2rad
Convert angle from degrees to radians
rad2deg
Convert angle from radians to degrees

b. Custom 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.

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).


(c) 2002-2003-2004 JAPISoft