Home > C#, Jace.NET > Jace.NET – Just another calculation engine for .NET

Jace.NET – Just another calculation engine for .NET

I work as a .NET technical architect in the financial industry and I get in touch with all sorts of mathematical formulas representing insurance or banking products. Most of the time, these formulas are hard coded by programmers and are thus not very flexible to be change dynamically at runtime by end-users.

This inspired me to start developing a calculation engine for the .NET platform that can execute any mathematical formula stored as a string both with or without variables. I wiped the dust of my university book: “Modern compiler implementations” and started the development a couple of weeks ago. I decided to name the calculation engine Jace.NET: Just another calculation engine for .NET.

Jace.NET is designed to run on .NET 4.0, Windows Phone 7, Windows Phone 8 and Windows RT.

How to use it

Jace.NET can be used in a couple of ways:

By directly executing a given mathematical function using the provided variables:

Dictionary<string, double> variables = new Dictionary<string, double>();
variables.Add("var1", 2.5);
variables.Add("var2", 3.4);

CalculationEngine engine = new CalculationEngine();
double result = engine.Calculate("var1*var2", variables);

By building a .NET Func accepting a dictionary as input containing the values for each variable:

CalculationEngine engine = new CalculationEngine();
Func<Dictionary<string, double>, double> function = engine.Build("var1+2/(3*otherVariable)");

Dictionary<string, double> variables = new Dictionary<string, double>();
variables.Add("var1", 2);
variables.Add("otherVariable", 4.2);

double result = function(variables);

By building a typed .NET Func:

CalculationEngine engine = new CalculationEngine();
Func<int, double, double> function = (Func<int, double, double>)engine.Function("var1+2/(3*otherVariable)")
    .Parameter("var1", DataType.Integer)
    .Parameter("otherVariable", DataType.FloatingPoint)
    .Result(DataType.FloatingPoint)
    .Build();

double result = function(2, 4.2);

Architecture

Jace.NET has an architecture similar to the one of modern compilers: interpretation and execution are performed in a number of steps. Each step focuses on one aspect of the parsing and interpretation of the formula. This keeps the overall complexity manageable.

clip_image002

The process starts with the tokenizing phase. During this phase the input string is converted into the various allowed tokens: integers, doubles, operations and variables. If a part of the input string contains text that does not match with any type of token, an exception is thrown and Jace will halt.

When tokenizing is successfully finished, an abstract syntax tree (AST) is constructed. This abstract syntax tree is a tree like data model that unambiguously represents the mathematical formula in memory. All mathematical precedence rules are taking into account when constructing the abstract syntax tree. Jace uses an algorithm inspired by the shunting-yard algorithm of Dijkstra to create this AST.

clip_image004

After AST creation, the optimizer will try to simplify the abstract syntax tree: if a part of the formula does not depend on variables but solely on constants. This part of the tree is already calculated and replaced by a constant in the tree.

clip_image006

The final phase is the OpCode generation. During this phase, a .NET dynamic method is created and the necessary MSIL is generated to execute the formula. This dynamic method is cached in memory. If the same formula is executed again in the future with other values for the variables. The interpretation steps are skipped and the dynamic method is directly executed. If the formulas of the calculations are frequently reoccurring, Jace.NET has near compiled code performance.

GitHub

If you are interested in the source code, please have a look at:

http://github.com/pieterderycke/Jace

Categories: C#, Jace.NET
  1. November 8, 2012 at 13:19

    Wow! “Theory of the compilers” was one of my favorite courses at University, but I never had the opportunity to go back to my “books” as I seldom have to develop things requiring such an “advanced” topic….

    I really like the idea of Dynamic Method Generation! As far as someone else would have been tempted to “interpret formula”, I am quite sure he would simply have reused the Calculation Engine of Aspose.Cells (in use by some projects at AG but for other purposes). This Engine supports almost all the Microsoft Excel’s built-in functions and formulas. Out-of-the-box, code would looks like this:

    worksheet.Cells[“A1”].PutValue(2.5);
    worksheet.Cells[“A2”].PutValue(3.5);
    worksheet.Cells[“A3”].PutValue(5);

    worksheet.Cells[“A4”].Formula = “=(A1+A3)*A2”;

    workbook.CalculateFormula();

    For sure, that could be encapsulated behind methods like “SetVariable(name, value)” and “Compute()” to improve the semantic, but you clearly offer a Roll Royce solution compare this “quick and cheap” approach.

    • pieterderycke
      November 8, 2012 at 13:42

      Thanks for the reply 🙂

      The idea was not to rely on some product like Aspose Cells, but to have a framework that only requires .NET. I also wanted code that could be ported to Windows Phone and Windows RT. I am not sure if Aspose Cells runs on these platforms?

      + it was/is a fun challenge to build such an “advanced” system 😉

  2. markus
    December 24, 2012 at 23:26

    Hi
    I’m currently working on a simple (free) Windows Phone 8 app for evaluating simple expressions like Sin(3)*(2+(2%1)/3)+Sqrt(7) (no hidden meaning here 😉

    Instead of building a good parser on my own (this is not my level of expertise to be honest) I searched for a free framework. First hit was NCalc, which is, as you mentioned, not working on WP8 though. Then I found your project – nice work! However, are you planning to provide some build-in functions, e.g. Sqrt, Pow, Sin, Cos, Tan, ASin, ACos, ATan and Log, in the near future? Otherwise I’ll try to figure something out myself 🙂

    btw: Merry Christmas!

    • pieterderycke
      December 25, 2012 at 09:01

      sin, cos and log are already supported. I will add support for the other functions in the next version. I am also working on improving the documentation for people to know what is supported in jace.

    • pieterderycke
      December 31, 2012 at 10:24

      Jace.NET 0.7.1 has been released: support has been added for the modulo operator and the following functions: asin, acos, tag, cot, atan, acot and sqrt.

      You can get it with NuGet.

      Happy coding in 2013 😉

      • markus
        December 31, 2012 at 10:38

        Thx, Great! Can’t wait to try it 🙂

  3. Nikola
    January 17, 2013 at 23:18

    Nice job indeed. Question: do you have a plan to implement custom functions and evaluation of parameters? For example: MyFunc1(P1,MyFunc2(P2)). Thanks.

  4. pieterderycke
    January 18, 2013 at 00:23

    Yes, this is planned for a next version (probably 0.8). Do you have specific suggestions or ideas you would like to have implemented in Jace.NET?

  5. Nikola
    January 18, 2013 at 14:26

    Thanks for asking. I am working on an expert system which needs to check lot of rules weighted by score points. Rules need to be loaded from external file, to easily change score and formula. I need to write lot of custom functions, and first version is implemented by using NCalc. NCalc is good but, performances are bad. I tested your tool and can confirm that Jace is faster. But requirement for custom functions and specially parameters evaluation, prevent me to switch project to work with Jace.

    • pieterderycke
      February 12, 2013 at 12:56

      v0.8 beta (can be found on the dev branch on GitHub) has now support for custom functions. You can add new functions to a CalculationEngine instance by calling the function AddFunction. AddFunction supports two parameters: a string for the function name and a .NET Func containing the implementation). There are various overloads of AddFunction for supporting Func’s with multiple parameters.

      Is this sufficient? Or do you have other proposals or remarks?

  6. Jay Braude
    April 15, 2013 at 12:05

    JACE fails on -(1+2+(3+4)) evaluating to 10 instead of -10

    I believe no handling of unary minus is provided.

    • pieterderycke
      April 16, 2013 at 09:40

      Hello Jay,

      Thanks for contacting me. Your issue is a bug in Jace, I will fix it in the next release. Until then you could write -(1+2+(3+4)) (but I am aware that it is less convenient.

    • pieterderycke
      May 17, 2013 at 21:08

      The bug has been corrected in the dev branch on GitHub. Jace.NET now correctly handles the unary minus. The fix will be released as part of Jace.NET 0.8.1.

      • markus
        May 17, 2013 at 21:15

        nice 🙂
        Do you already have any idea when 0.8.1 will be available?

      • pieterderycke
        May 18, 2013 at 06:43

        Probably somewhere during next week. But there are some other small bugs I want to fix first.

      • pieterderycke
        May 28, 2013 at 21:04

        Just to let you know that there is delay with the release of 0.8.1 due to a bug. I will postpone the release with a week.

      • pieterderycke
        June 5, 2013 at 21:50

        I am happy to announce that version 0.8.1 has been released with support for the unary minus operator. You can get the latest version using NuGet.

  7. Jason
    May 1, 2013 at 00:23

    I left this question on a separate thread, but it seems more appropriate here… is there a reason that Jace accepts only doubles as variables? I’m working on integrating Jace into a financial appliciation, but my inputs are all decimals. Thanks.

    • pieterderycke
      May 7, 2013 at 15:33

      I used a double because it takes less space in memory and it is faster for calculations. Would a double be acceptable for your application? Or do you really need support for decimals?

      Kind regards,
      Pieter

      • Jason
        May 15, 2013 at 05:33

        The calculations I’m performing with Jace.net involve money which is represented by decimals in our application. I worry about losing precision when casting to double to use the Jace calculation engine. Support for decimals would be great, especially for anyone who wants to use Jace for calculations involving currency. Pretty impressive tool regardless.

        Thanks!

  8. August 9, 2013 at 14:20

    I believe I have found a bug in the latest version of Jace, but please correct me if I am wrong.
    I’m currently validating Jace for use in a larger software project and have run across an issue. Evaluating the expression 1+2-3*4/5+6-7*8/9+0 gives 0.82 with Jace instead of 0.378 which it should be according to the rules of precedence. I have traced the issue to building the AST, where the root of the tree becomes the first subtraction instead of the last addition.
    To make a long story short, here is some code for the AstBuilder that replaces the else case in the “if (operatorStack.Count == 0)” statement. It ensures that all operators with the same or less precedence (etc) are popped instead of just the last one.

    Token operation2Token = operatorStack.Peek();
    bool isFunctionOnTopOfStack = operation2Token.TokenType == TokenType.Text;
    char operation2 = (char)operation2Token.Value;

    while (!isFunctionOnTopOfStack && ((IsLeftAssociativeOperation(operation1) && (operationPrecedence[operation1] == operationPrecedence[operation2])) ||
    (operationPrecedence[operation1] 0)
    {
    operation2Token = operatorStack.Peek();
    isFunctionOnTopOfStack = operation2Token.TokenType == TokenType.Text;
    operation2 = (char)operation2Token.Value;
    }
    else
    {
    isFunctionOnTopOfStack = true; // not really, but it exits the loop nicely
    }
    }
    operatorStack.Push(operation1Token);

    • pieterderycke
      August 9, 2013 at 14:40

      Thanks for informing me about this issue and the detailed analysis. I will investigate it this weekend and keep you informed.

    • pieterderycke
      August 13, 2013 at 08:41

      Hello Mikael,

      I have now investigated the issue in detail and you are absolutely right regarding the bug and the proposed solution! (thanks for this :-)) I have also verified with the specs of the shunting yard algorithm and I made an implementation issue by forgetting the while loop.

      I will fix it and release a version 0.8.2 of Jace. I will also update the unit tests of Jace.NET for covering this case.

      Does Jace.NET further satisfies the needs of your project?

      Kind regards,
      Pieter

  9. March 27, 2014 at 17:24

    Does it use .NET DLR?

    • pieterderycke
      March 27, 2014 at 19:23

      No, Jace does not rely on the .NET DLR.

  10. rick
    April 27, 2014 at 03:42

    1. Is there a plan to support logic operator like ‘and’ ‘or’ and relation operator like ‘>’ ‘<' '='
    2. Does Jace support decimal?

    • shilpesh
      January 19, 2018 at 05:57

      Desperately need this functionality. any plan for implementation “and”,”or”.

      • pieterderycke
        August 14, 2018 at 14:43

        I will look to introduce support for “and” and “or’

  11. April 23, 2015 at 12:16

    Hi, I’m wanting to automatically generate the list of parameters from a formula, then go find the values. Can this engine do it? eg:

    CalculationEngine engine = new CalculationEngine();
    Func<Dictionary, double> function = engine.Build(“var1+2/(3*otherVariable)”);

    // automatically generate variables here
    // Pseudo:
    for each var in engine.variables
    var.VariableValue = FindVariableValue(var.VariableName)
    next

    double result = function(engine.variables);

    The reason I want to do this is because I have a lot of formulas, and each references only a few variables. So they need to find the variables from a large database of variables, via lookup, depending on the actual formula. I’d prefer not to have to write an engine to ‘pull out’ the variable names.

    If you’re able to give me a idea of if this is possible, I’d greatly appreciate it.
    THanks

  12. January 2, 2017 at 05:17

    Is it possible to express a variable as percentage directly in the formula text e.g. var1*10%, or due to the interpreter must be reduced to var1*(10/100) ?

    Jace seems to support min() and max() functions. These are not documented in wiki. Is there a way to gather a complete list of built-in functions?

  13. Ratan Rajpuit
    March 12, 2017 at 15:44

    variable name with underscore is not working like => “NT_CUM”

  1. January 22, 2014 at 11:36

Leave a comment