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.
Microsoft.Extensions.DependencyInjection
Compared with a good solution mXparser
№ | 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 + 1) + 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) |
Less is better
As you can see this solution is faster in all cases, furthermore there isn't any garbage collection.
Benchmark code - src/ConsoleApp/Benchmark/BenchNumbers.cs
Benchmark excel - benchResults/Benchmark.xlsx
There are two ways to use evaluator:
public void SomeMethod()
{
var facade = NoStringEvaluator.CreateFacade();
var evaluator = facade.Evaluator;
}
public void ConfigureServices(IServiceCollection services)
{
// ......
services.AddNoStringEvaluator();
}
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);
}
}
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()));
}
Apart from double calculations you can work with types:
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;
}
}
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 |
You can use two types of variables:
Simple variable means that it named without unique symbols and starts with a letter. Only one extra symbol is possible, it's "_"
Some examples:
Bordered variable means that it has a tricky name with any symbols, except for square brackets.
Some examples:
Needless to say, you can write simple variable with brackets as well.
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...
Key word | Description | Example |
---|---|---|
+ | Addition | a + b |
- | Subtraction | a - b |
* | Multiplication | a * b |
/ | Division | a / b |
^ | Exponentiation | a^b |
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) |
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) |
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) |
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) |
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') |
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) |
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') |
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) |
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));
}
Method | Description |
---|---|
FormulaNodes Parse(string formula) | Return parsed formula nodes |
FormulaNodes Parse(ReadOnlySpan |
Return parsed formula nodes |
List |
Return parsed formula nodes without RPN |
Method | Description |
---|---|
void AddFunction(IFunction func, bool replace = false) | Add function |
void RemoveFunction(string functionName) | Remove function |
Method | Description |
---|---|
FormulaNodes GetFormulaNodes(string formula) | Return cached formula nodes |
Method | Description |
---|---|
CheckFormulaResult CheckSyntax(string formula) | Check syntax |
CheckFormulaResult CheckSyntax(ReadOnlySpan |
Check syntax |
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 |
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 |
Evaluate value |
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 |
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 |
List |
bool CalcBoolean(... |
EvaluatorValue Calc(... |
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) |
Static initialization if you don't have DI
Method | Description |
---|---|
static Facade CreateFacade(Action\<NoStringEvaluatorOptions> options = null) | Create evaluator facade |
Static initialization if you don't have DI
Method | Description |
---|---|
static Facade CreateFacade(Action\<NoStringEvaluatorOptions> options = null) | Create evaluator facade |