在 C# 中进行科学计算,以任何字符串数学表达式实现

作者:微信公众号:【架构师老卢】
8-9 17:42
33

概述:为了增强 C# 中的科学计算能力,我实现了一个赋值器,该赋值器能够以出色的性能计算任何数学字符串表达式。它还支持自定义变量和函数。可以在 GitHub 上找到名为 MathEvaluator 的 .NET 库及其文档。在 C# 语言中快速计算字符串数学表达式的技术要实现数学表达式的高性能评估,需要利用现代 .NET 功能和高效的算法。关键策略包括最小化内存分配、避免正则表达式以及减少复杂数据结构的开销。内存分配为了在分析数学表达式期间最小化内存分配,计算器使用 .这种结构允许有效地操作子字符串,而无需额外的内存分配,从而显着提高性能。这就是此库面向 .NET Standard 2.1 或更高版

为了增强 C# 中的科学计算能力,我实现了一个赋值器,该赋值器能够以出色的性能计算任何数学字符串表达式。它还支持自定义变量和函数。

可以在 GitHub 上找到名为 MathEvaluator 的 .NET 库及其文档。

在 C# 语言中快速计算字符串数学表达式的技术

要实现数学表达式的高性能评估,需要利用现代 .NET 功能和高效的算法。关键策略包括最小化内存分配、避免正则表达式以及减少复杂数据结构的开销。

内存分配

为了在分析数学表达式期间最小化内存分配,计算器使用 .这种结构允许有效地操作子字符串,而无需额外的内存分配,从而显着提高性能。这就是此库面向 .NET Standard 2.1 或更高版本的原因。

避免使用正则表达式

虽然功能强大,但正则表达式可能会带来巨大的开销。赋值器不使用正则表达式进行分析,而是采用递归方法调用来根据运算符优先级和规则计算数学运算。这种递归方法简化了解析逻辑,并消除了与数学表达式计算中常用的堆栈或队列数据结构相关的开销。

静态方法

通过使用静态方法,评估器可以通过编译优化从提高的性能中受益。静态方法通常速度更快,因为它们不需要调用类的实例,从而减少了与实例方法调用相关的开销。

高效搜索

为了有效地搜索和管理变量和函数,赋值器使用前缀树,也称为 trie。trie 允许按键(名称)进行快速搜索,并且在处理大量变量和函数时非常有效。这种结构支持快速查找和添加,使其成为使用自定义变量和函数扩展数学上下文的理想选择。

性能比较

例如,让我们比较一下计算数学表达式的性能:

22888.32 * 30 / 323.34 / .5 - -1 / (2 + 22888.32) * 4 - 6

以下是与 NCalc 库的性能比较:

使用字符串扩展的示例:

    "22888.32 * 30 / 323.34 / .5 - -1 / (2 + 22888.32) * 4 - 6".Evaluate();

    "22888.32 * 30 / 323.34 / .5 - -1 / (2 + 22888.32) * 4 - 6".EvaluateDecimal();

    "$22,888.32 * 30 / 323.34 / .5 - - 1 / (2 + $22,888.32) * 4 - 6".Evaluate(new CultureInfo("en-US"));

    "22’888.32 CHF * 30 / 323.34 / .5 - - 1 / (2 + 22’888.32 CHF) * 4 - 6".EvaluateDecimal(new CultureInfo("de-CH"));

    "ln[1/-0.5 + √(1/(0.5^2) + 1)]".Evaluate(new ScientificMathContext());
    
    "ln[1/-0,5 + √(1/(0,5^2) + 1)]".Evaluate(new ScientificMathContext(), new CultureInfo("fr"));
    
    "ln[1/-0.5 + √(1/(0.5^2) + 1)]".EvaluateDecimal(new ScientificMathContext());
    
    "ln[1/-0,5 + √(1/(0,5^2) + 1)]".EvaluateDecimal(new ScientificMathContext(), new CultureInfo("fr"));
    
    "4 % 3".Evaluate(new ProgrammingMathContext());
    
    "4 mod 3".EvaluateDecimal(new ScientificMathContext());

使用静态方法的示例:

    MathEvaluator.Evaluate("22888.32 * 30 / 323.34 / .5 - -1 / (2 + 22888.32) * 4 - 6");

    MathEvaluator.EvaluateDecimal("22888.32 * 30 / 323.34 / .5 - -1 / (2 + 22888.32) * 4 - 6");

    MathEvaluator.Evaluate("$22,888.32 * 30 / 323.34 / .5 - - 1 / (2 + $22,888.32) * 4 - 6", new CultureInfo("en-US"));

    MathEvaluator.EvaluateDecimal("22’888.32 CHF * 30 / 323.34 / .5 - - 1 / (2 + 22’888.32 CHF) * 4 - 6", new CultureInfo("de-CH"));
    
    MathEvaluator.Evaluate("ln[1/-0.5 + √(1/(0.5^2) + 1)]", new ScientificMathContext());
    
    MathEvaluator.Evaluate("ln[1/-0,5 + √(1/(0,5^2) + 1)]", new ScientificMathContext(), new CultureInfo("fr"));
    
    MathEvaluator.EvaluateDecimal("ln[1/-0.5 + √(1/(0.5^2) + 1)]", new ScientificMathContext());
    
    MathEvaluator.EvaluateDecimal("ln[1/-0,5 + √(1/(0,5^2) + 1)]", new ScientificMathContext(), new CultureInfo("fr"));
    
    MathEvaluator.Evaluate("4 % 3", new ProgrammingMathContext());
    
    MathEvaluator.EvaluateDecimal("4 mod 3", new ScientificMathContext());

使用 MathEvaluator 类的实例的示例:

    new MathEvaluator("22888.32 * 30 / 323.34 / .5 - -1 / (2 + 22888.32) * 4 - 6").Evaluate();

    new MathEvaluator("22888.32 * 30 / 323.34 / .5 - -1 / (2 + 22888.32) * 4 - 6").EvaluateDecimal();

    new MathEvaluator("$22,888.32 * 30 / 323.34 / .5 - - 1 / (2 + $22,888.32) * 4 - 6").Evaluate(new CultureInfo("en-US"));

    new MathEvaluator("22’888.32 CHF * 30 / 323.34 / .5 - - 1 / (2 + 22’888.32 CHF) * 4 - 6").EvaluateDecimal(new CultureInfo("de-CH"));
    
    new MathEvaluator("ln[1/-0.5 + √(1/(0.5^2) + 1)]", new ScientificMathContext()).Evaluate();
    
    new MathEvaluator("ln[1/-0,5 + √(1/(0,5^2) + 1)]", new ScientificMathContext()).Evaluate(new CultureInfo("fr"));
    
    new MathEvaluator("ln[1/-0.5 + √(1/(0.5^2) + 1)]", new ScientificMathContext()).EvaluateDecimal();
    
    new MathEvaluator("ln[1/-0,5 + √(1/(0,5^2) + 1)]", new ScientificMathContext()).EvaluateDecimal(new CultureInfo("fr"));
    
    new MathEvaluator("4 % 3", new ProgrammingMathContext()).Evaluate();
    
    new MathEvaluator("4 mod 3", new ScientificMathContext()).EvaluateDecimal();

使用自定义变量或函数的示例:

    var x1 = 0.5;
    var x2 = -0.5;
    var sqrt = Math.Sqrt;
    Func<double, double> ln = Math.Log;

    var value1 = "ln(1/-x1 + sqrt(1/(x2*x2) + 1))"
        .Bind(new { x1, x2, sqrt, ln })
        .Evaluate();

    var value2 = "ln(1/-x1 + Math.Sqrt(1/(x2*x2) + 1))"
        .BindVariable(0.5, "x1")
        .BindVariable(-0.5, "x2")
        .BindFunction(Math.Sqrt)
        .BindFunction(Math.Log, "ln")
        .Evaluate();

使用自定义上下文的示例:

    var context = new MathContext();
    context.BindVariable(0.5, "x1");
    context.BindVariable(-0.5, "x2");
    context.BindFunction(Math.Sqrt);
    context.BindFunction(Math.Log, "ln");

    "ln(1/-x1 + Math.Sqrt(1/(x2*x2) + 1))"
        .SetContext(context)
        .Evaluate();

支持的数学函数、运算符和常量

MathEvaluator 库提供用于评估复杂科学表达式、编程表达式和 C# 数学表达式的默认上下文。要查看支持的函数、运算符和常量的完整列表,请参阅文档。可以通过继承它们并添加自定义运算符、常量或函数来扩展这些上下文。根据用户反馈,我可以扩展这些上下文以满足特定需求。

使用自定义数学上下文的示例:

var context = new MathContext();  
context.BindVariable(0.5, "x1");  
context.BindVariable(-0.5, "x2");  
context.BindFunction(Math.Sqrt);  
context.BindFunction(Math.Log, "ln");  
  
"ln(1/-x1 + Math.Sqrt(1/(x2*x2) + 1))"  
    .SetContext(context)  
    .Evaluate();

若要计算 C# 数学字符串表达式,请使用 .此 .NET Standard 2.1 编程数学上下文支持该类提供的所有常量和函数。

例:

"-2 * Math.Log(1/0.5f + Math.Sqrt(1/Math.Pow(0.5d, 2) + 1L)"  
    .Evaluate(new DotNetStandartMathContext());

用于数学表达式的 C# 计算器通过最小化内存分配、避免正则表达式的复杂性以及减少与堆栈或队列数据结构相关的开销,提供卓越的性能。它利用前缀树来高效搜索自定义变量和函数,使其成为 .NET 中科学计算的强大工具。该评估器遵循数学计算规则,可以以惊人的速度和效率计算复杂的表达式,通过 1,000 多项测试证明了这一点,包括复杂的数学表达式,如 或 。sin-3/cos1-3^4sin(-π/2)

未来的计划包括增加对计算 Python 表达式、Excel 公式的支持,并扩展评估器以支持 LaTeX 规范。如果你觉得这个项目有价值,请考虑在GitHub上赞助我。

相关留言评论
昵称:
邮箱:
阅读排行