ProCalc.NET
1.2.0
dotnet add package ProCalc.NET --version 1.2.0
NuGet\Install-Package ProCalc.NET -Version 1.2.0
<PackageReference Include="ProCalc.NET" Version="1.2.0" />
<PackageVersion Include="ProCalc.NET" Version="1.2.0" />
<PackageReference Include="ProCalc.NET" />
paket add ProCalc.NET --version 1.2.0
#r "nuget: ProCalc.NET, 1.2.0"
#:package ProCalc.NET@1.2.0
#addin nuget:?package=ProCalc.NET&version=1.2.0
#tool nuget:?package=ProCalc.NET&version=1.2.0
ProCalc.NET
ProCalc.NET is a math expression language evaluation engine for the .NET platform.
The package lets you compile textual expressions into an execution tree and then run
them many times, mixing symbolic computation, exact integer arithmetic, exact
fractions, floating-point numbers, complex numbers, "big numbers" (BigNum),
vectors, matrices, lists and strings. The engine also supports:
- automatic type promotion on overflow (e.g.
Int -> LargeNumber,Int^(-n) -> IntFraction), - defining variables and user functions directly inside an expression,
- call parameters (
_0,_1, ...) – for passing data from the host into the expression, - external variables (
$name) resolved at runtime by a resolver supplied by the application, - saving and loading state (variables + user functions) to
BinaryWriter/BinaryReader.
The library is cross-platform and targets: .NET Framework 4.7.2, .NET Framework 4.8,
.NET 6, .NET 8, .NET 10.
Table of contents
- Simple usage example
- Feature list
- Defining custom functions and macros
- Parameters and variables
- Host integration – runtime parameters
1. Simple usage example
using ProCalc.NET;
using ProCalc.NET.Numerics;
var core = new ProCalcCore();
CompiledExpression expr = core.Compile("2 + 3 * 4");
BaseNumeric result = core.Execute(expr);
Console.WriteLine(result.AsString()); // "14"
Compilation is done once, and execution (Execute) can be invoked any number of times –
including with different sets of call parameters and different external variable resolvers.
2. Feature list
2.1 Supported types
| Type | .NET class | Syntax / Example expression | Description |
|---|---|---|---|
| Integer | IntNumeric |
42, -7 |
Signed 64-bit integer. |
| Integer in other base | IntNumeric |
0b1010, 0o17, 0d99, 0hFF |
Binary, octal, decimal, hexadecimal literal. |
| Floating-point | FloatNumeric |
3.14, 1.5e-3, 5f |
double; the f suffix forces float type for an integer literal. |
| Integer fraction | IntFractionNumeric |
result of 1/3, 0.25 (parsed exactly) |
Exact numerator/denominator fraction (e.g. 1/3 instead of 0.3333…). |
| Complex number | ComplexNumeric |
2 + 3i, 1.5e2i |
Real + imaginary part. |
| Big number (BigNum) | LargeNumberNumeric |
12345678901234567890L |
Arbitrary-size integer; result of automatic promotion on overflow. |
| Boolean | BoolNumeric |
true, false |
Logical constant. |
| Degrees/minutes/seconds | (DMS) | 12:30, 12:30:45.5 |
Angular syntax. |
| String | StringNumeric |
"hello", "a""b" |
A quote inside a literal is doubled. |
| List | ListNumeric |
{1, 2, 3} |
A list of values of any type. |
| Vector | VectorNumeric |
(1, 2, 3) |
A vector of scalar values. |
| Matrix | MatrixNumeric |
[1, 2; 3, 4] |
A matrix; rows separated by ;, columns by ,. |
Indexing uses the value[index, ...] syntax, e.g. "abcdef"[2] or m[1, 2].
Type promotion
The engine does not return an overflow where the result can be represented exactly:
Int + Int,Int - Int,Int * Intoutside theInt64range →LargeNumberNumeric,Int \ Int(integer division) outside the range →LargeNumberNumeric,Int ^ Intwith a negative exponent →IntFractionNumeric(when representable),Int ^ Intwith a large result →LargeNumberNumeric.
2.2 Operators
| Operator | Meaning |
|---|---|
+ |
Addition / string concatenation |
- |
Subtraction / unary minus |
* |
Multiplication |
/ |
Division (preserves exactness when possible) |
\ |
Integer division |
% |
Modulo |
^ |
Exponentiation |
<< >> |
Bitwise shift |
& \| # |
Bitwise / logical AND, OR, XOR |
! |
Logical negation (unary) |
< > <= >= == != |
Comparisons |
Operator precedence (from highest): & | # << >> > ^ > / \ % > * > + - > comparisons.
2.3 Built-in functions
The built-in function set (Arithmetics.Functions):
- Trigonometry:
sin,cos,tan,ctg,arcsin,arccos,arctan,arcctg - Arithmetic:
abs,round,trunc,frac,sqrt,sqr,ln - Strings / lists / matrices:
length,copy,pos,insert,delete,uppercase,lowercase,strtoint,inttostr - Vectors:
dot(dot product),distance,project
Example:
core.Execute(core.Compile("sin(pi / 2) + sqrt(2)^2")); // 3
core.Execute(core.Compile("uppercase(\"hello\")")); // "HELLO"
core.Execute(core.Compile("dot((1,2,3), (4,5,6))")); // 32
2.4 Built-in constants
| Constant | Value |
|---|---|
pi |
System.Math.PI |
e |
System.Math.E |
true |
logical true value |
false |
logical false value |
3. Defining custom functions and macros
ProCalc.NET lets you define user functions directly in the expression language. A definition is just an assignment: the function header is on the left-hand side, and the expression body is on the right.
var core = new ProCalcCore();
// Definition: f(x, y) = x^2 + y^2
core.Execute(core.Compile("f(x, y) = x^2 + y^2"));
// Usage:
var result = core.Execute(core.Compile("f(3, 4)"));
Console.WriteLine(result.AsString()); // "25"
Functions are stored in an internal function table and persist between Execute
calls within the same ProCalcCore instance. Registered functions (and variables)
can be persisted:
using (var fs = File.Create("state.bin"))
using (var bw = new BinaryWriter(fs))
{
core.SaveState(bw);
}
using (var fs = File.OpenRead("state.bin"))
using (var br = new BinaryReader(fs))
{
core.LoadState(br);
}
core.ClearState(); // remove all definitions
If evaluation of an expression containing a function/variable definition is interrupted by an exception, the definition is not persisted (the engine uses a pending/accept/reject mechanism).
Disabling definitions in a given call is done via the
allowControlStatements: false parameter:
core.Execute(core.Compile("f(x) = x + 1"), allowControlStatements: false);
// -> RuntimeException: control statements not allowed
This allows you to expose the engine as a safe "expression calculator" without the ability to modify its state (e.g. for expressions entered by an end user).
4. Parameters and variables
ProCalc.NET distinguishes four kinds of "named values", each with a different lifetime:
| Kind | Expression syntax | Value source | Lifetime |
|---|---|---|---|
| Built-in constant | pi, e, true |
Arithmetics |
built-in, immutable |
| Engine variable | x |
core.SetVariableValue(...) or x = ... in an expression |
persisted in ProCalcCore |
| Call parameter (runtime) | _0, _1, ... |
callParams passed from code to core.Execute(...) |
only during Execute |
| External variable | $name |
BaseExternalVariableResolver.ResolveVariable(name) |
only during Execute |
4.1 Variables inside an expression
Variable value is stored in ProCalcCore state, can be persisted and also used in later expressions or statements.
Their value can be set using a control statement.
core.Execute(core.Compile("a = 10"));
core.Execute(core.Compile("b = a * 2"));
var v = core.Execute(core.Compile("a + b")); // 30
4.2 Variables set from the host
Alternative way of setting variable value, from code.
core.SetVariableValue("radius", new FloatNumeric(2.5));
var area = core.Execute(core.Compile("pi * radius^2"));
BaseNumeric current = core.GetVariableValue("radius");
4.3 Call parameters (_0, _1, …)
Parameters named _0, _1, … are data passed from the host into the expression
during an Execute call. Inside a user function the same names refer to the
function call arguments.
var expr = core.Compile("_0 * _1 + _2");
var result = core.Execute(expr, new List<BaseNumeric>
{
new IntNumeric(3),
new IntNumeric(4),
new IntNumeric(1),
}); // 13
4.4 External variables ($name)
A variable prefixed with $ is resolved at runtime by an object inheriting from
BaseExternalVariableResolver. This lets you dynamically plug in any host data
(e.g. spreadsheet cells, form fields, database values) – without having to
pre-register them in the engine.
using ProCalc.NET.Resolvers;
using ProCalc.NET.Numerics;
class DictionaryVariableResolver : BaseExternalVariableResolver
{
private readonly IDictionary<string, BaseNumeric> values;
public DictionaryVariableResolver(IDictionary<string, BaseNumeric> values) => this.values = values;
public override BaseNumeric ResolveVariable(string externalVariableName)
=> values.TryGetValue(externalVariableName, out var v) ? v : null;
}
5. Host integration – runtime parameters
The following example shows a typical integration scenario: an expression is compiled once and then executed many times with different call parameters and with an external variable resolver supplying host data.
using System;
using System.Collections.Generic;
using ProCalc.NET;
using ProCalc.NET.Numerics;
using ProCalc.NET.Resolvers;
// 1. External variable resolver – plugs in host data.
public class HostVariableResolver : BaseExternalVariableResolver
{
private readonly Dictionary<string, BaseNumeric> data;
public HostVariableResolver(Dictionary<string, BaseNumeric> data) => this.data = data;
public override BaseNumeric ResolveVariable(string name)
=> data.TryGetValue(name, out var v) ? v : null;
}
public static class PricingEngine
{
public static void Run()
{
var core = new ProCalcCore();
// 2. Business constants as engine variables.
core.SetVariableValue("vatRate", new FloatNumeric(0.23));
// 3. User function – compiled once, used many times.
core.Execute(core.Compile("gross(net, rate) = net * (1 + rate)"));
// 4. The expression uses:
// - call parameters: _0 (net price), _1 (quantity)
// - external variable: $discount (fetched from the host on demand)
// - engine variable: vatRate
// - user function: gross(...)
CompiledExpression formula = core.Compile(
"gross(_0 * _1 * (1 - $discount), vatRate)");
// 5. The host provides runtime data per call.
var hostData = new Dictionary<string, BaseNumeric>
{
["discount"] = new FloatNumeric(0.10), // 10% discount
};
var resolver = new HostVariableResolver(hostData);
// 6. Multiple executions of the same compiled formula.
foreach (var order in new[]
{
(price: 100.0, qty: 2),
(price: 49.99, qty: 5),
(price: 250.0, qty: 1),
})
{
var args = new List<BaseNumeric>
{
new FloatNumeric(order.price),
new IntNumeric(order.qty),
};
// allowControlStatements: false -> user expressions cannot
// modify the engine state (define variables/functions).
BaseNumeric total = core.Execute(
formula,
callParams: args,
allowControlStatements: false,
externalVariableResolver: resolver);
Console.WriteLine($"{order.qty} x {order.price} => {total.AsString()}");
}
}
}
In this pattern the role of each mechanism is as follows:
Compileis invoked once – the AST is reusable.callParams(_0,_1, …) pass input data for a specific call.BaseExternalVariableResolverlets the engine reach into the host for$xxxnames, without having to copy data into the engine.SetVariableValue/ function definitions in expressions let you keep business state (rates, formulas) inside aProCalcCoreinstance.allowControlStatements: falseprotects the call against unwanted modification of the engine state (typical for expressions originating from end users).
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Framework | net472 is compatible. net48 is compatible. net481 was computed. |
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Bugfixes, calculation quality improvements, arithmetics is fully unit-tested.