【问题标题】:Is there a string math evaluator in .NET?.NET 中是否有字符串数学评估器?
【发布时间】:2010-09-26 04:27:04
【问题描述】:

如果我有一个包含有效数学表达式的字符串,例如:

String s = "1 + 2 * 7";

.NET 中是否有内置库/函数可以为我解析和评估该表达式并返回结果?在这种情况下 15.

【问题讨论】:

标签: c# .net


【解决方案1】:

对于在 Silverlight 上使用 C# 进行开发的任何人,这是我刚刚发现的一个非常巧妙的技巧,它允许通过调用 Javascript 引擎来评估表达式:

double result = (double) HtmlPage.Window.Eval("15 + 35");

【讨论】:

  • 我想知道您是否可以在其他地方引用此内容。可能不会,但会很酷。
  • 由于这会评估任意 Javascript 代码,您可能需要确保清理输入并确保您没有直接显示结果。 (我认为这将是一个在不知不觉中引入 XSS 的好方法)
  • 尝试输入带前导零的数字,结果不可靠。例如,“054 + 6”给你 50。
  • @djerry,这是因为带有前导零的数字被 JS 的求值器视为八进制,而八进制 054 等于十进制 44。
【解决方案2】:

您可以添加对 Microsoft 脚本控件库 (COM) 的引用,并使用这样的代码来计算表达式。 (也适用于 JScript。)

Dim sc As New MSScriptControl.ScriptControl()
sc.Language = "VBScript"
Dim expression As String = "1 + 2 * 7"
Dim result As Double = sc.Eval(expression)

编辑 - C# 版本。

MSScriptControl.ScriptControl sc = new MSScriptControl.ScriptControl();
sc.Language = "VBScript";
string expression = "1 + 2 * 7";
object result = sc.Eval(expression);            
MessageBox.Show(result.ToString());

编辑 - ScriptControl 是一个 COM 对象。在项目的“添加引用”对话框中选择“COM”选项卡并向下滚动到“Microsoft Script Control 1.0”并选择“确定”。

【讨论】:

  • 虽然这被标记为答案,但那是 10 年前的事了,现在 COM 已经死了。我更喜欢下面的 DataTable.Compute 答案。
【解决方案3】:

实际上有一种内置的 - 您可以使用 XPath 命名空间! 尽管它要求您重新格式化字符串以使用 XPath 表示法进行确认。我使用过这样的方法来处理简单的表达式:

    public static double Evaluate(string expression)
    {
        var xsltExpression = 
            string.Format("number({0})", 
                new Regex(@"([\+\-\*])").Replace(expression, " ${1} ")
                                        .Replace("/", " div ")
                                        .Replace("%", " mod "));

        return (double)new XPathDocument
            (new StringReader("<r/>"))
                .CreateNavigator()
                .Evaluate(xsltExpression);
    }

【讨论】:

    【解决方案4】:

    你见过http://ncalc.codeplex.com 吗?

    它是可扩展的、快速的(例如有自己的缓存),使您能够通过处理 EvaluateFunction/EvaluateParameter 事件在运行时提供自定义函数和变量。它可以解析的示例表达式:

    Expression e = new Expression("Round(Pow(Pi, 2) + Pow([Pi2], 2) + X, 2)"); 
    
      e.Parameters["Pi2"] = new Expression("Pi * Pi"); 
      e.Parameters["X"] = 10; 
    
      e.EvaluateParameter += delegate(string name, ParameterArgs args) 
        { 
          if (name == "Pi") 
          args.Result = 3.14; 
        }; 
    
      Debug.Assert(117.07 == e.Evaluate()); 
    

    它还可以原生处理 unicode 和许多数据类型。如果您想更改语法,它会附带一个 antler 文件。还有一个 fork 支持 MEF 加载新功能。

    【讨论】:

    • 很棒的图书馆。也可在 NUGET 上使用
    • 这是我用于我的微分方程求解器的,它接受用户的输入。问题是here
    【解决方案5】:

    如果您需要非常简单的东西,您可以使用DataTable :-)

    Dim dt As New DataTable
    dt.Columns.Add("A", GetType(Integer))
    dt.Columns.Add("B", GetType(Integer))
    dt.Columns.Add("C", GetType(Integer))
    dt.Rows.Add(New Object() {12, 13, DBNull.Value})
    
    Dim boolResult As Boolean = dt.Select("A>B-2").Length > 0
    
    dt.Columns.Add("result", GetType(Integer), "A+B*2+ISNULL(C,0)")
    Dim valResult As Object = dt.Rows(0)("result")
    

    【讨论】:

      【解决方案6】:
      namespace CalcExp
      {
          internal class Program
          {
              private static void Main(string[] args)
              {
                  double res = Evaluate("4+5/2-1");
      
                  Console.WriteLine(res);
      
              }
      
              public static double Evaluate(string expression)
              {
                  var xsltExpression =
                      string.Format("number({0})",
                          new Regex(@"([\+\-\*])").Replace(expression, " ${1} ")
                                                  .Replace("/", " div ")
                                                  .Replace("%", " mod "));
      
      // ReSharper disable PossibleNullReferenceException
                  return (double)new XPathDocument
                      (new StringReader("<r/>"))
                          .CreateNavigator()
                          .Evaluate(xsltExpression);
      // ReSharper restore PossibleNullReferenceException
              }
      
          }
      }
      

      【讨论】:

      • -1:只需将其合并到@cbp 答案中即可。当我们可以有一个奇妙的答案时,需要有两个基本相同的答案是零。
      【解决方案7】:

      奇怪的是,这个著名的老问题没有一个建议内置 DataTable.Compute-“trick”的答案。在这里。

      double result = Convert.ToDouble(new DataTable().Compute("1 + 2 * 7", null));
      

      表达式中支持以下算术运算符:

      + (addition)
      - (subtraction)
      * (multiplication)
      / (division)
      % (modulus)
      

      更多信息:DataColumn.Expression@表达式语法

      【讨论】:

      • ma81xx 于 2011 年 11 月 15 日给出了答复
      • 你是对的,但那不是使用 Compute 方法。
      • 你的例子给了我 System.InvalidCastException is unhandled by user code HResult=-2147467262 我该如何解决?
      • 它对我有用,你用过这个示例代码吗?使用调试器并检查结果值,类型已提及。
      【解决方案8】:

      我也想看看 Jace (https://github.com/pieterderycke/Jace)。 Jace 是一个高性能的数学解析器和计算引擎,支持所有 .NET 风格(.NET 4.x、Windows Phone、Windows Store,...)。 Jace 也可以通过 NuGet 获得:https://www.nuget.org/packages/Jace

      【讨论】:

        【解决方案9】:

        几年前我实现了一个表达式解析器,最近在GitHubNuget:Albatross.Expression 上发布了它的一个版本。它包含一个 ExecutionContext 类,可以评估一组表达式,例如:

        • MV = 价格 * 数量;
        • 价格 =(买价 + 卖价)/2;
        • 出价 = .6;
        • 问 = .8;

        它还内置了循环引用检查,有助于避免堆栈溢出。

        【讨论】:

          【解决方案10】:

          逃离 快速轻量级表达式评估器

          https://flee.codeplex.com

          语言参考

          • 算术运算符示例:a*2 + b ^ 2 - 100 % 5
          • ComparisonOperators 示例:a 100
          • AndOrXorNotOperators 示例(逻辑):a > 100 And Not b = 100
          • ShiftOperators 示例:100 >> 2
          • 连接示例:“abc”+“def”
          • 索引示例:arr[i + 1] + 100
          • 文字
          • 投射示例:100 + cast(obj, int)
          • 条件运算符示例:If(a > 100 and b > 10, "both greater", "less")
          • InOperator 示例(列表):If(100 in (100, 200, 300, -1), "in", "not in")
          • 类型上的重载运算符

          例子:

          Imports Ciloci.Flee
          Imports Ciloci.Flee.CalcEngine
          Imports System.Math
          

              Dim ec As New Ciloci.Flee.ExpressionContext
              Dim ex As IDynamicExpression
              ec.Imports.AddType(GetType(Math))
          
              ec.Variables("a") = 10            
              ec.Variables("b") = 40               
              ex = ec.CompileDynamic("a+b")
          
              Dim evalData    
              evalData = ex.Evaluate()
              Console.WriteLine(evalData)
          

          输出:50

          【讨论】:

            【解决方案11】:

            最初我为muparser 使用了c# 包装器。这是非常快的。我知道的唯一更快的解决方案是exprtk。如果您正在寻找其他解决方案,您可以查看benchmark

            但对于 .Net,您可以使用内置支持在运行时编译代码。这个想法是有一个“模板”源文件,例如嵌入式资源,您可以在其中替换评估公式。然后将这个准备好的类源代码传递给编译器。

            基本模板可能如下所示:

            public class CSCodeEvaler
            {
                public double EvalCode()
                {
                    return last = Convert.ToDouble(%formula%);
                }
            
                public double last = 0;
                public const double pi = Math.PI;
                public const double e = Math.E;
                public double sin(double value) { return Math.Sin(value); }
                public double cos(double value) { return Math.Cos(value); }
                public double tan(double value) { return Math.Tan(value); }
                ...
            

            注意 %formula% 将放入表达式的位置。

            要编译使用类 CSharpCodeProvider。我不想在这里放完整的源代码。但是this answer 可能会有所帮助:

            加载内存中的程序集后,您可以创建类的实例并调用 EvalCode。

            【讨论】:

              【解决方案12】:

              最近我在使用 mXparser,它是一个用于 .NET 和 JAVA 的数学解析器库。 mXparser 支持基本公式以及非常花哨/复杂的公式(包括变量、函数、运算符、迭代和递归)。

              https://mxparser.codeplex.com/

              https://mathparser.org/

              几个使用示例:

              示例 1:

              Expression e = new Expression("1+2*7 + (sin(10) - 2)/3");
              double v = e.calculate();
              

              示例 2:

              Argument x = new Argument("x = 5");
              Expression e = new Expression("2*x+3", x);
              double v = e.calculate();
              

              示例 3:

              Function f = new Function("f(x,y) = sin(x) / cos(y)");
              Expression e = new Expression("f(pi, 2*pi) - 2", f);
              double v = e.calculate();
              

              最近发现 - 如果您想尝试语法(并查看高级用例),您可以下载由 mXparser 提供支持的 Scalar Calculator app

              最好的问候

              【讨论】:

              • 这是我找到的最好的库。但是,它不支持数字以外的任何数据类型!我需要 DateTime 和 String.. 你知道有什么好的选择吗?
              • @Homam 你找到字符串操作的替代方法了吗
              【解决方案13】:

              Roslyn 可用的另一个选择:

              您可以为此使用 CodeAnalysis.CSharp.Scripting 库。

              using Microsoft.CodeAnalysis.CSharp.Scripting;
              using System;
              
              namespace ExpressionParser
              {
                  class Program
                  {
                      static void Main(string[] args)
                      {
                          //Demonstrate evaluating C# code
                          var result = CSharpScript.EvaluateAsync("System.DateTime.Now.AddDays(-1) > System.DateTime.Now").Result;
                          Console.WriteLine(result.ToString());
              
                          //Demonstrate evaluating simple expressions
                          var result2 = CSharpScript.EvaluateAsync(" 5 * 7").Result;
                          Console.WriteLine(result2);
                          Console.ReadKey();
                      }
                  }
              }
              

              nuget 包:

              <package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net461" />
              <package id="Microsoft.CodeAnalysis.Common" version="1.1.1" targetFramework="net461" />
              <package id="Microsoft.CodeAnalysis.CSharp" version="1.1.1" targetFramework="net461" />
              <package id="Microsoft.CodeAnalysis.CSharp.Scripting" version="1.1.1" targetFramework="net461" />
              <package id="Microsoft.CodeAnalysis.Scripting" version="1.1.1" targetFramework="net461" />
              <package id="Microsoft.CodeAnalysis.Scripting.Common" version="1.1.1" targetFramework="net461" />
              

              【讨论】:

              • 这应该是现在的答案
              【解决方案14】:

              一个简单的数学解析器很容易构建,只需要几行代码:

              以这个灵活的例子为例:

              class RPN
              {
                  public static double Parse( Stack<string> strStk )
                  {
                      if (strStk == null || strStk.Count == 0 )
                      {
                          return 0;
                      }
                      Stack<double> numStk = new Stack<double>();
                      double result = 0;
              
                      Func<double, double> op = null;
                      while (strStk.Count > 0)
                      {
                          var s = strStk.Pop();
                          switch (s)
                          {
                              case "+":
                                  op = ( b ) => { return numStk.Pop() + b; };
                                  break;
                              case "-":
                                  op = ( b ) => { return numStk.Pop() - b; };
                                  break;
                              case "*":
                                  op = ( b ) => { return numStk.Pop() * b; };
                                  break;
                              case "/":
                                  op = ( b ) => { return numStk.Pop() / b; };
                                  break;
              
                              default:
                                  double.TryParse(s, NumberStyles.Any, out result);
                                  if (numStk.Count > 0)
                                  {
                                      result = op(result);
                                  }
                                  numStk.Push(result);
                                  break;
                          }
                      }
                      return result;
                  }
              }
              
              ....
              var str = " 100.5 + 300.5 - 100 * 10 / 100";    
              str = Regex.Replace(str, @"\s", "", RegexOptions.Multiline);
              Stack<string> strStk = new Stack<string>(
                   Regex.Split(str, @"([()*+\/-])", RegexOptions.Multiline).Reverse()
              );
              RPN.Parse(strStk);
              

              通过将一堆堆栈括起来来启用优先级就足够了,例如通过递归归档。括号之间的任何东西都放在一个新的堆栈上。 最后,您可以通过 lambdas 以清晰易读的方式支持数学运算。

              【讨论】:

              • 您可能想验证您的答案。 100.5 + 300.5 - 100 * 10 / 100 = 30.1391
              【解决方案15】:

              MathNet.Symbolics

              using System;
              using static MathNet.Symbolics.SymbolicExpression;
              using static System.Console;
              using static System.Numerics.Complex;
              using Complex = System.Numerics.Complex;
              
              namespace MathEvaluator
              {
                  class Program
                  {
                      static readonly Complex i = ImaginaryOne;
              
                      static void Main(string[] args)
                      {
                          var z = Variable("z");
                          Func<Complex, Complex> f = Parse("z * z").CompileComplex(nameof(z));
                          Complex c = 1 / 2 - i / 3;
                          WriteLine(f(c));
              
              
                          var x = Variable("x");
                          Func<double, double> g = Parse("x * x + 5 * x + 6").Compile(nameof(x));
                          double a = 1 / 3.0;
                          WriteLine(g(a));
                      }
                  }
              }
              

              别忘了加载

              <PackageReference Include="MathNet.Symbolics" Version="0.20.0" />
              

              【讨论】:

                【解决方案16】:

                您可以使用我是作者的Math-Expression-Evaluator 库。支持2.5+5.917.89-2.47+7.165/2/2+1.5*3+4.58等简单表达式,支持带括号的表达式(((9-6/2)*2-4)/2-6-1)/(2+24/(2+4))和带变量的表达式:

                var a = 6;
                var b = 4.32m;
                var c = 24.15m;
                var engine = new ExpressionEvaluator();
                engine.Evaluate("(((9-a/2)*2-b)/2-a-1)/(2+c/(2+4))", new { a, b, c});
                

                您也可以将参数作为命名变量传递:

                dynamic dynamicEngine = new ExpressionEvaluator();
                
                var a = 6;
                var b = 4.5m;
                var c = 2.6m;
                
                dynamicEngine.Evaluate("(c+b)*a", a: 6, b: 4.5, c: 2.6);
                

                它支持 .Net Standard 2.0,因此可以从 .Net Core 以及 .Net Full Framework 项目中使用,并且没有任何外部依赖项。

                【讨论】:

                  【解决方案17】:

                  没有内置的解决方案,但有一些简单的方法可以让它发挥作用。

                  现在至少有两个很好的新解决方案:使用符号代数AngouriMath 或通用算法库Towel

                  安古里数学

                  你可以的

                  using AngouriMath;
                  Entity expr = "1 + 2 + sqrt(2)";
                  var answer = (double)expr.EvalNumerical();
                  

                  (默认情况下它以高精度计算,可能也有用)

                  或者编译一下

                  Entity expr = "1 + 2 + sqrt(2) + x + y";
                  Func<double, double, double> someFunc = expr.Compile<double, double, double>("x", "y");
                  Console.WriteLine(someFunc(3, 5));
                  

                  这样它就可以在时间关键的代码中使用。

                  毛巾

                  在这里你可以做

                  using Towel.Mathematics;
                  var expression = Symbolics.Parse<double>("(2 + 2 * 2 - (2 ^ 4)) / 2");
                  Console.WriteLine(expression.Simplify());
                  

                  这会直接将您的表达式计算为double

                  安装

                  两者都可以通过 Nuget 安装:AngouriMathTowel

                  【讨论】:

                    【解决方案18】:

                    【讨论】:

                      猜你喜欢
                      • 2023-01-22
                      • 2012-09-16
                      • 2012-05-29
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多