Skip to content

Fast low memory consuming mathematical evaluation without endless string parsing! Parses string formula once and uses its object sequence in each evaluation. Moreover, provides user defined functions and variables.

License

KovtunV/NoStringEvaluating

Repository files navigation

Fast low memory consuming mathematical evaluation without endless string parsing! Parses string formula once and uses its object sequence in each evaluation. Moreover provides user defined functions and variables.
Build Status NuGet version NuGet Download Tweet

Microsoft.Extensions.DependencyInjection
Build Status NuGet version NuGet Download

Quick Links


Features

  • Fast math evaluation
  • Zero-allocation code (object pooling)
  • User defined functions
  • User defined variables with any chars
  • Mixed result type

Performance

Compared with a good solution mXparser

  • In general, x5 faster!

Testing formulas

â„– Formula
Empty
NumberOnly 3
1 3 * 9
2 3 * 9 / 456 * 32 + 12 / 17 - 3
3 3 * (9 / 456 * (32 + 12)) / 17 - 3
4 (2 + 6 - (13 * 24 + 5 / (123 - 364 + 23))) - (2 + 6 - (13 * 24 + 5 / (123 - 364 + 23))) + (2 + 6 - (13 * 24 + 5 / (123 - 364 + 23))) * 345 * ((897 - 323)/ 23)
5 Arg1 * Arg2 + Arg3 - Arg4
6 Arg1 * (Arg2 + Arg3) - Arg4 / (Arg5 - Arg6) + 45 * Arg7 + ((Arg8 * 56 + (12 + Arg9))) - Arg10
7 add(1; 2; 3)
8 add(add(5; 1) - add(5; 2; 3))
9 if(Arg1 > 0; add(56 + 9 / 12 * 123.596; or(78; 9; 5; 2; 4; 5; 8; 7); 45;5); 9) * 24 + 52 -33
10 kov(1; 2; 3) - kovt(8; 9)

1 000 000 calculations

Less is better image graph image table

Benchmark results

Both

Conclusion

As you can see this solution is faster in all cases, furthermore there isn't any garbage collection.

Benchmark code - ConsoleApp/Benchmark/BenchmarkNumberService.cs

Benchmark excel - BenchResults/Benchmark.xlsx

Quick start

Initialization

There are two ways to use evaluator:

  1. Static initialization
public void SomeMethod()
{
    var facade = NoStringEvaluator.CreateFacade();
    var evaluator = facade.Evaluator;
}
  1. DI from the package
public void ConfigureServices(IServiceCollection services)
{
    // ......
    services.AddNoStringEvaluator();
}

Usage

Add INoStringEvaluator to your controller, service, etc...

And just send string or FormulaNodes to evaluation:

public class MyService
{
    private INoStringEvaluator _noStringEvaluator;

    public MyService(INoStringEvaluator noStringEvaluator)
    {
        _noStringEvaluator = noStringEvaluator;
    }

    public double CalcNumber(string formula)
    {
        return _noStringEvaluator.CalcNumber(formula);
    }
    
    public string CalcWord(string formula)
    {
        return _noStringEvaluator.CalcWord(formula);
    }
    
    public EvaluatorValue Calc(string formula)
    {
        return _noStringEvaluator.Calc(formula);
    }
}

If you have variables, you can send IDictionary or your IVariablesContainer implementation:

public class MyService
{
    private INoStringEvaluator _noStringEvaluator;

    public MyService(INoStringEvaluator noStringEvaluator)
    {
        _noStringEvaluator = noStringEvaluator;
    }

    public double Calc(string formula, IDictionary<string, EvaluatorValue> variables)
    {
        return _noStringEvaluator.CalcNumber(formula, variables);
    }
}

User defined functions

If you need your function, just implement the interface IFunction If you want to returnt extra type, use factory.

As an argument's separator can be:

  • ;
  • ,

For instance, usage function "YouAre('Vitaly'; 26)":

public class MyFunction : IFunction
{
    public string Name { get; } = "YouAre";

    public bool CanHandleNullArguments { get; }

    public InternalEvaluatorValue Execute(List<InternalEvaluatorValue> args, ValueFactory factory)
    {
        var name = args[0].Word;
        var age = args[1];

        var ageAfterDecade = age + 10;
        var result = $"Hello, {name}. After 10 years you will be {ageAfterDecade} y.o.";

        return factory.Word.Create(result);
    }
}

And don't forget to initialize your functions via options or directly in IFunctionReader

public void SomeMethod()
{
    // NoStringEvaluator.CreateFacade(opt => opt.WithFunctionsFrom(<type from source assembly>));
    // NoStringEvaluator.CreateFacade(opt => opt.WithFunctionsFrom(<source assembly>));
    // NoStringEvaluator.CreateFacade(opt => opt.WithFunctions(new MyFunction()));

    // same with DI
    // services.AddNoStringEvaluator(opt => opt.WithFunctions(new MyFunction()));
}

Extra types

Apart from double calculations you can work with types:

  • Boolean
  • DateTime
  • String
  • List of string
  • List of double
  • Null
  • Object

Object is a special type to allow using, for example, services inside function.

public void Should_Evaluate_Service()
{
    // arrange
    var service = _serviceFactory(null);
    var args = new Dictionary<string, EvaluatorValue>
    {
        ["myService"] = new EvaluatorValue(new MyService()),
        ["myNum"] = 10
    };
    var expected = 50.5;

    // act
    var actual = service.CalcNumber("TestService(myService; myNum)", args);

    // assert
    actual.Should().BeApproximatelyNumber(expected);
}

private class ServiceFunction : IFunction
{
    public string Name { get; } = "TestService";

    public bool CanHandleNullArguments { get; }

    public InternalEvaluatorValue Execute(List<InternalEvaluatorValue> args, ValueFactory factory)
    {
        return args[0].GetObject<MyService>().GetTemperature() + args[1];
    }
}

private class MyService
{
    public double GetTemperature()
    {
        return 40.5;
    }
}

List description

You can describe a list inside the formula

Example Result
IsMember({'printer', 'computer', 'monitor'}; 'computer') 1
Unique({'NEW','OLD','NEW','HEAVEN','OLD'}) {'NEW','OLD','HEAVEN'}
Add({1, 2, 3, 10, 3}) 19

Variables

You can use two types of variables:

  • Simple variable
  • Bordered variable

Simple variable

Simple variable means that it named without unique symbols and starts with a letter. Only one extra symbol is possible, it's "_"

Some examples:

  • "25 + myArgument - 1"
  • "25 + myArg1 - 2"
  • "arg5684argArg_arg"
  • "25 + myArgument_newAge - 3"

Bordered variable

Bordered variable means that it has a tricky name with any symbols, except for square brackets.

Some examples:

  • "25 + [myVariable and some words] - 1"
  • "25 + [Provider("my provider").Month(1).Price] - 2"
  • "[myVariable ♥]"
  • "[simpleVariable]"

Needless to say, you can write simple variable with brackets as well.

Precompiled variables

There are some known variables, you shouldn't send them to Calc method.

Key word Description Value
pi Pi, Archimedes' constant or Ludolph's number 3.14159265358979323846
tau A circle constant equal to 2Ď€ 6.283185307179586476925
e Napier's constant, or Euler's number, base of Natural logarithm 2.7182818284590452354
true Boolean True True
false Boolean False False
ASC Boolean True True
DESC Boolean False False

These variables are register independent, you can write Pi, [PI], pI, True, etc...

Operators

Key word Description Example
+ Addition a + b
- Subtraction a - b
* Multiplication a * b
/ Division a / b
^ Exponentiation a^b

Boolean operators

Key word Description Example
< Lower than a < b
<= Lower or equal a <= b
> Greater than a > b
>= Greater or equal a >= b
== Equality a == b
= Equality a = b
!= Inequation a != b
<> Inequation a <> b
&& Logical conjunction (AND) a && b
|| Logical disjunction (OR) a || b
! Negation !IsNull(a)

Functions

Math

Key word Description Example
add Summation operator add(a1; a2; ...; an) can include List
multi Multiplication multi(a1; a2; ...; an) can include List
mean Mean / average value mean(a1; a2; ...; an) can include List
min Minimum function min(a; b) can include List
max Maximum function max(a; b) can include List
Rpund Rounds the designated number to the specified decimals Round(a; decimals)
ln Natural logarithm function (base e) ln(x)
log Logarithm function (base b) log(a; b)
log2 Binary logarithm function (base 2) log2(x)
log10 Common logarithm function (base 10) log10(x)
sqrt Squre root function sqrt(x)
abs Absolut value function abs(x)
sgn Signum function sgn(x)
sign Signum function sign(x)
floor Floor function floor(x)
ceil Ceiling function ceil(x)
mod Modulo function mod(a; b)
fact Factorial function fact(x)
fib Fibonacci number fib(x)
gcd Greatest common divisor gcd(a1; a2; ...; an)
lcm Least common multiple lcm(a1; a2; ...; an)

Trigonometry

Key word Description Example
sin Trigonometric sine function sin(x)
cos Trigonometric cosine function cos(x)
tg Trigonometric tangent function tg(x)
tan Trigonometric tangent function tan(x)
ctg Trigonometric cotangent function ctg(x)
cot Trigonometric cotangent function cot(x)
ctan Trigonometric cotangent function ctan(x)
sec Trigonometric secant function sec(x)
csc Trigonometric cosecant function csc(x)
cosec Trigonometric cosecant function cosec(x)
asin Inverse trigonometric sine function asin(x)
arsin Inverse trigonometric sine function arsin(x)
arcsin Inverse trigonometric sine function arcsin(x)
acos Inverse trigonometric cosine function acos(x)
arcos Inverse trigonometric cosine function arcos(x)
arccos Inverse trigonometric cosine function arccos(x)
atg Inverse trigonometric tangent function atg(x)
atan Inverse trigonometric tangent function atan(x)
arctg Inverse trigonometric tangent function arctg(x)
arctan Inverse trigonometric tangent function arctan(x)
actg Inverse trigonometric cotangent function actg(x)
acot Inverse trigonometric cotangent function acot(x)
actan Inverse trigonometric cotangent function actan(x)
arcctg Inverse trigonometric cotangent function arcctg(x)
arccot Inverse trigonometric cotangent function arccot(x)
arcctan Inverse trigonometric cotangent function arcctan(x)
sinh Hyperbolic sine function sinh(x)
cosh Hyperbolic cosine function cosh(x)
tgh Hyperbolic tangent function tgh(x)
tanh Hyperbolic tangent function tanh(x)
coth Hyperbolic cotangent function coth(x)
ctgh Hyperbolic cotangent function ctgh(x)
ctanh Hyperbolic cotangent function ctanh(x)
sech Hyperbolic secant function sech(x)
csch Hyperbolic cosecant function csch(x)
cosech Hyperbolic cosecant function cosech(x)
arcsec Inverse trigonometric secant arcsec(x)
asinh Inverse hyperbolic sine function asinh(x)
arsinh Inverse hyperbolic sine function arsinh(x)
arcsinh Inverse hyperbolic sine function arcsinh(x)
acosh Inverse hyperbolic cosine function acosh(x)
arcosh Inverse hyperbolic cosine function arcosh(x)
arccosh Inverse hyperbolic cosine function arccosh(x)
atgh Inverse hyperbolic tangent function atgh(x)
atanh Inverse hyperbolic tangent function atanh(x)
arctgh Inverse hyperbolic tangent function arctgh(x)
arctanh Inverse hyperbolic tangent function arctanh(x)
acoth Inverse hyperbolic cotangent function acoth(x)
actgh Inverse hyperbolic cotangent function actgh(x)
actanh Inverse hyperbolic cotangent function actanh(x)
arccoth Inverse hyperbolic cotangent function arccoth(x)
arcctgh Inverse hyperbolic cotangent function arcctgh(x)
arcctanh Inverse hyperbolic cotangent function arcctanh(x)
asech Inverse hyperbolic secant function asech(x)
arsech Inverse hyperbolic secant function arsech(x)
arcsech Inverse hyperbolic secant function arcsech(x)
acsch Inverse hyperbolic cosecant function acsch(x)
arcsch Inverse hyperbolic cosecant function arcsch(x)
arccsch Inverse hyperbolic cosecant function arccsch(x)
acosech Inverse hyperbolic cosecant function acosech(x)
arcosech Inverse hyperbolic cosecant function arcosech(x)
rad Degrees to radians function rad(x)
deg Radians to degrees function deg(x)
exp Exponential function exp(x)

Logic

Key word Description Example
if If function if(cond; expr-if-true; expr-if-false)
iff If function iff( cond-1; expr-1; ... ; cond-n; expr-n )
and Logical conjunction (AND) and(a1; a2; ...; an)
or Logical disjunction (OR) or(a1; a2; ...; an)
not Negation function not(x)
IsNaN Returns true if value is a Not-a-Number (NaN) isNaN(x)
IsError Returns true if this is a double.NaN IsError(ToNumber('Text'))
IsMember Checks if second argument is a member of list from first IsMember({'printer', 'computer', 'monitor'}; 'computer')
IsNumber Returns true if this is a number IsNumber(256)

Excel

I've implemented some of excel functions. If you wanna see more, just send me a message.

Key word Description Example
Count Returns a number of elements Count(a; b; ...) can include List
Len Returns the number of characters in a text string Len("my word")
Sort Sorts a List. sortType: true - asc, false - desc Sort(myList; sortType)
ToNumber Returns number from word ToNumber('03')

DateTime

Key word Description Example
DateDif Calculates the number of days, months, or years between two dates. Can be: Y, M, D DateDif(date1; date2; 'Y')
TimeDif Calculates the number of hours, minutes, or seconds between two dates. Can be: H, M, S DateDif(time1; time2; 'H')
Now Returns Datetime.Now Now()
Today Returns the current date Today()
Day Returns a day from dateTime Day(Now())
Day(Now(); 'DD')
Month Returns a month from dateTime Month(Now())
Month(Now(); 'MM')
Year Returns a year from dateTime Year(Now())
Year(Now(); 'YY')
ToDateTime Returns datetime value from string ToDateTime('8/15/2002')
WeekDay Takes a date and returns a number between 1-7 representing the day of week WeekDay(Today())
DateFormat Format dateTime to string DateFormat(Now(); 'HH:mm:ss')
AddHours Adds a number of hours to a datetime AddHours(date; 17)
AddMinutes Adds a number of minutes to a datetime AddMinutes(date; 17)
AddSeconds Adds a number of seconds to a datetime AddSeconds(date; 17)

Word

Key word Description Example
Concat Concates values Concat(56; ' myWord')
Concat(myList; myArg; 45; myList2)
Explode Returns a text list composed of the elements of a text string. Separator by default is white space " " Explode(myWord)
Explode(myWord; separator)
Implode Concatenates all members of a text list and returns a text string. separator by default is empty "" Implode(myList)
Implode(myList; separator)
Implode(myList; 5; 'my wordd'; separator) last value is separator
Left Searches a string from left to right and returns the leftmost characters of the string Left(myWord)
Left(myWord; numberOfChars)
Left(myWord; wordNeededChars)
Middle Returns any substring from the middle of a string Middle(myWord; indexStart; numberChars)
Middle(myWord; indexStart; wordEnd)
Middle(myWord; wordStart; numberChars)
Middle(myWord; wordStart; wordEnd)
Right Searches a string from right to left and returns the rightmost characters of the string Right(myWord)
Right(myWord; numberOfChars)
Right(myWord; wordNeededChars)
Lower Converts text to lowercase Lower(myWord)
Lower(myWordList)
Upper Converts text to uppercase Upper(myWord)
Upper(myWordList)
Proper Capitalizes the first letter in each word of a text Proper(myWord)
Replace Replaces characters within text Replace(myWord; oldPart; newPart)
Replace(myList; oldPart; newPart)
Text Returns text from first argument Text(26)
Unique If second parameter is true then returns only unique If second parameter is false then returns list without doubles Unique(myList)
Unique(myList; true)
IsText Returns true if this is a word IsText('my word')

Null

Key word Description Example
IfNull Returns second argument if the first is null IfNull(x,3)
NullIf Returns null if the first argument is equal to the second NullIf(x,3)
IsNull Returns null if the first argument null IsNull(x)

Options

When you use AddNoStringEvaluator or CreateFacade you can configure evaluator.

To illustrate, I change floating point from default dot to comma:

public void ConfigureServices(IServiceCollection services)
{
    // ......
    services.AddNoStringEvaluator(opt => opt.SetFloatingPointSymbol(FloatingPointSymbol.Comma));
}

Documentation

IFormulaParser

Method Description
FormulaNodes Parse(string formula) Return parsed formula nodes
FormulaNodes Parse(ReadOnlySpan formula) Return parsed formula nodes
List ParseWithoutRpn(ReadOnlySpan formula) Return parsed formula nodes without RPN

IFunctionReader

Method Description
void AddFunction(IFunction func, bool replace = false) Add function
void RemoveFunction(string functionName) Remove function

IFormulaCache

Method Description
FormulaNodes GetFormulaNodes(string formula) Return cached formula nodes

IFormulaChecker

Method Description
CheckFormulaResult CheckSyntax(string formula) Check syntax
CheckFormulaResult CheckSyntax(ReadOnlySpan formula) Check syntax

IVariablesContainer

Method Description
IVariable AddOrUpdate(string name, double value) Add or update variable
EvaluatorValue GetValue(string name) Return variable's value
bool TryGetValue(string name, out EvaluatorValue value) Return variable's value if possible

IFunction

Method Description
string Name { get; } Name of function
bool CanHandleNullArguments { get; } If false and any argument is null - function wont be executed and null will be returned
InternalEvaluatorValue Execute(List args, ValueFactory factory) Evaluate value

NoStringEvaluatorOptions

Method Description
SetWordQuotationSingleQuote() Set word quotation mark - '
SetWordQuotationDoubleQuote() Set word quotation mark - "
SetWordQuotationMark(string mark) Set word quotation mark
SetFloatingTolerance(double floatingTolerance) Set floating tolerance
SetFloatingPointSymbol(FloatingPointSymbol floatingPointSymbol) Set floating point symbol
SetThrowIfVariableNotFound(bool isThrow) Set throw if variable not found
WithFunctionsFrom(Type typeFromSourceAssembly) Add assembly to register functions
WithFunctionsFrom(Assembly sourceAssembly) Add assembly to register functions
WithoutDefaultFunctions(bool withoutDefaultFunctions = true) Remove root assembly from functions registration
WithFunctions(params IFunction[] functions) Add functions

INoStringEvaluator

Method
double CalcNumber(string formula, IVariablesContainer variables)
double CalcNumber(FormulaNodes formulaNodes, IVariablesContainer variables)
double CalcNumber(string formula, IDictionary<string, EvaluatorValue> variables)
double CalcNumber(FormulaNodes formulaNodes, IDictionary<string, EvaluatorValue> variables)
double CalcNumber(string formula)
double CalcNumber(FormulaNodes formulaNodes)
string CalcWord(...
DateTime CalcDateTime(...
List CalcWordList(...
List CalcNumberList(...
bool CalcBoolean(...
EvaluatorValue Calc(...

INoStringEvaluatorNullable

Referenced methods are same as in INoStringEvaluator

Method
double? CalcNumber(string formula, IVariablesContainer variables)
DateTime? CalcDateTime(string formula, IVariablesContainer variables)
bool? CalcBoolean(string formula, IVariablesContainer variables)

NoStringEvaluator

Static initialization if you don't have DI

Method Description
static Facade CreateFacade(Action<NoStringEvaluatorOptions> options = null) Create evaluator facade

NoStringEvaluatorNullable

Static initialization if you don't have DI

Method Description
static Facade CreateFacade(Action<NoStringEvaluatorOptions> options = null) Create evaluator facade

Thanks for contribution

:octocat: IvoTops

TODO

  • Add more functions
  • Any idea?

About

Fast low memory consuming mathematical evaluation without endless string parsing! Parses string formula once and uses its object sequence in each evaluation. Moreover, provides user defined functions and variables.

Topics

Resources

License

Stars

Watchers

Forks