Math Parser .NET
一个非常易于使用的.NET数学解析器库。
引言
前段时间,我在 CodeProject 上写了一篇文章,名为 TokenIcer。TokenIcer 是一个程序,可以根据在程序中创建的正则表达式规则,自动在 C# 或 VB.NET 中创建词法分析器。
自从我写了那篇文章以来,我收到了一些非常好的反馈,我想更进一步。我决定使用 TokenIcer 来创建一个数学方程式解析器。这个数学解析器在此文章中呈现,名为 Math Parser .NET。它是一个 .NET 类库项目,可以与您自己的程序一起使用。
背景
已经存在好几个数学解析器。我的数学解析器的目标是使其尽可能简单易用。我最大的抱怨之一是,当一个好的库因为其自身的特性列表而掩盖了其简单性时,它就会“变坏”。我认为我的数学解析器在保持基本功能和用法简单的同时,很好地完成了它的工作。
Using the Code
在深入介绍如何使用该库之前,我想首先介绍一下该库的内部工作原理。该库本身首先由一个词法分析器组成。我使用 TokenIcer 自动创建了词法分析器。词法分析器将采用这样的输入字符串
1 + 2 * 3 + (9 / 3)
并将其转换为枚举的标记,如下所示
{Integer}{WhiteSpace}{Add}{WhiteSpace}{Integer}{WhiteSpace}{Multiply}
{WhiteSpace}{Integer}{WhiteSpace}{Add}{WhiteSpace}{LParen}
{Integer}{WhiteSpace}{Divide}{WhiteSpace}{Integer}{RParen}
一旦没有更多的标记需要处理,词法分析器将返回一个空标记。
接下来的任务是创建一个循环,遍历每个标记,直到收到空标记。解析器接收到的输入将采用所谓的“中缀表示法”。中缀表示法意味着数字由数学运算符分隔。就像上面的例子一样,1 和 2 由一个加号分隔。2 和 3 由一个乘号分隔。此外,数学运算符具有特定的优先级顺序。在上面的例子中,9 / 3 应该首先计算,因为它在括号中。2 * 3 应该其次计算,因为乘法和除法的优先级高于加法。为了更容易地解析数学表达式,我将方程式从中缀表示法转换为逆波兰表示法。在逆波兰表示法(或 RPN)中,数学符号位于数字**之后**。因此,在转换之后,上面的示例输入将看起来类似于这样
1 2 3 * 9 3 / + +
一旦方程式采用逆波兰表示法,只需从左到右读取并进行实际的数学运算即可。
要在您自己的程序中使用 MathNetLib 库,您所要做的就是添加对 MathNetLib DLL 文件的引用。之后,您需要实例化一个 MathNetLib.Parser
类的新实例。有三种主要的方程求解方法。第一种叫做 SimplifyInt()
。 SimplifyInt()
将接受一个数学方程,求解它,并返回一个整数答案。此方法有两个重载。这是第一个也是最简单的示例
static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
int retval;
try {
retval = parser.SimplifyInt("3.2 + 7.6");
} catch (Exception ex) {
throw ex;
}
}
此示例将返回一个 11 的值。关于 SimplifyInt()
需要记住的是,它将始终返回一个整数答案,并且默认情况下,它将四舍五入该答案。 SimplifyInt()
有一个重载方法签名,它还允许您指定如何处理答案的小数部分(如果有)。这是一个示例
static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
int retval;
try {
retval = parser.SimplifyInt("3.2 + 7.6",
MathParserNet.Parser.RoundingMethods.Truncate);
} catch (Exception ex) {
throw ex;
}
}
RoundingMethods
是一个枚举,具有以下值和描述
Round
-- 这是默认值。将小数四舍五入到最接近的整数。如果小数点后的数字是 5 或更高,它将向上舍入整数。如果小数点后的数字是 4 或更低,它将向下舍入整数。RoundDown
-- 无论小数点后的数字是多少,这都将始终向下舍入整数。RoundUp
-- 无论小数点后的数字是多少,这都将始终向上舍入整数。Truncate
-- 这将完全不进行任何舍入,而只是“截断”小数点和其后的任何数字。
第二个主要求解方程的方法叫做 SimplifyDouble()
。这个方法,像 SimplifyInt()
一样,将求解方程,但将始终返回一个 double
类型的答案。这是一个该方法实际应用的例子
static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
double retval;
try {
retval = parser.SimplifyDouble("3.2 + 7.6");
} catch (Exception ex) {
throw ex;
}
}
这将返回 10.8 的答案。
求解数学方程的第三个也是最后一个版本简单地称为 Simplify()
。 Simplify()
将求解一个方程,然后自动确定它是一个整数还是一个浮点数答案。 Simplify()
返回一个 MathParserNet.SimplificationReturnValue
对象。您可以检查此对象的 ReturnType
属性以确定答案是浮点数还是整数。这是一个使用示例
static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
MathParserNet.SimplificationReturnValue retval;
try {
retval = parser.Simplify("3.2 + 7.2");
} catch (Exception ex)
throw ex;
}
if (retval.ReturnType == MathParserNet.SimplificationReturnValue.ReturnTypes.Float)
{
Console.WriteLine("The answer is a Floating point number!");
Console.WriteLine(retval.DoubleValue);
}
if (retval.ReturnType == MathParserNet.SimplificationReturnValue.ReturnTypes.Integer)
{
Console.WriteLine("The answer is an Integer!");
Console.WriteLine(retval.IntValue);
}
无论何时使用任何简化函数,都应将其封装在 try/catch
块中。MathParserNet 实现了五个异常。以下是每个异常的描述
MismatchedParenthesisException
-- 如果您尝试解决的方程式中开括号多于闭括号,反之亦然,则会抛出此异常。NoSuchFunctionException
-- 如果您尝试使用尚未定义的函数,则会抛出此异常。函数将在下面描述。NoSuchVariableException
-- 如果您尝试使用尚未定义的变量,则会抛出此异常。变量将在下面描述。VariableAlreadyDefinedException
-- 如果您尝试定义一个已在前面定义的变量,则会抛出此异常。变量将在下面描述。CouldNotParseExpressionException
-- 如果在尝试解析表达式时出现任何其他问题,则会抛出此异常。将空方程式传递给任何简化函数都会导致此异常。
解析器可以解析以下内容
- () -- 括号
- + -- 加号 (3 + 2)
- - -- 减号 (3 - 2)
- * -- 乘号 (3 * 2)
- / -- 除号 (3 / 2)
- % -- 模数符号 (3 % 2)(除两个数,但返回余数)
- ^ -- 指数符号 (3 ^ 2)(3 的平方)
- ABS -- 函数返回一个数的绝对值 (ABS(-3))
- SIN -- 返回一个数的正弦 (SIN(3.14))
- COS -- 返回一个数的余弦 (COS(3.14))
- TAN -- 返回一个数的正切 (TAN(3.14))
- LOG -- 返回一个数的以 10 为底的对数
- LOGN -- 返回一个数的自然对数
- func<name> -- 调用用户定义函数(参见下面的“函数”)
变量
MathParserNet 支持使用变量。您可以使用 AddVariable()
方法在 MathParserNet 中定义变量。这是一个向解析器添加三个变量的示例
static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
try {
parser.AddVariable("PI", 3.14159265);
parser.AddVariable("Three", 3);
parser.AddVariable("HalfPI", "PI / 2");
parser.SimplifyDouble("HalfPI * Three + 7");
} catch (Exception ex) {
throw ex;
}
}
变量有点像文字处理器中的剪切和粘贴。当解析器看到一个变量时,它会查找该值并将变量名替换为实际值。所以 HalfPI
实际上变成了 "3.14159265 / 2",传递给 SimplifyDouble()
方法的方程实际上变成了 "3.14159265 / 3 + 7"。
已定义的变量也可以通过调用 RemoveVariable()
方法删除。您只需将变量名作为参数传递给 RemoveVariable()
,变量就会被删除。请记住,任何使用已删除变量定义的变量都会变得无效。例如,在上面的代码中,如果我删除 PI
变量,那么 HalfPI
变量也将变得无效。此外,您还可以通过调用 RemoveAllVariables()
方法删除**所有**已创建的变量。
另一件需要记住的事情是变量名**是**区分大小写的。PI 与 Pi、pI 或 pi 不同。
函数
除了变量,MathParserNet 还支持函数。您可以定义一个函数来做任何您想做的事情。这是一个示例
static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
try {
parser.AddFunction("GetSlope",
new MathParserNet.FunctionArgumentList {"x1", "x2",
"y1", "y2"}, "(y1-y2)/(x1-x2)");
parser.SimplifyDouble("3 * funcGetSlope(3.2 * (9/3), 4.5, 6, 9.2)");
} catch (Exception ex) {
throw ex;
}
}
此示例创建了一个名为 GetSlope
的函数。该函数接受四个参数 (x1,x2,y1,y2)。函数必须至少接受一个参数,并且可以定义为接受任意数量的参数。但请记住,函数参数名**不能**与已定义的变量名相同。要调用函数,您必须在函数名前加上“func”(当然不带引号)。需要记住的另一件事是,与变量一样,函数也区分大小写(“func”前缀也区分大小写)。
函数,就像变量一样,也可以通过调用 RemoveFunction()
方法来取消定义。与变量一样,您只需将函数名作为参数传递给 RemoveFunction()
。此外,您还可以通过调用 RemoveAllFunctions()
方法删除**所有**已创建的函数。
委托函数
也许 MathParserNet 最酷,可以说也是最方便的功能是能够在任何 .NET 语言中创建自己的自定义函数,并将其用作数学解析器中的自定义函数。这是一个示例,展示了我们的 GetSlope
函数作为委托函数
static void Main()
{
MathParserNet.Parser parser = new MathParserNet.Parser();
int myDouble;
double area;
double slope;
try {
parser.RegisterCustomFunction("GetSlope", GetSlope);
parser.RegisterCustomFunction("Doubler", Doubler);
parser.RegisterCustomFunction("AreaCircle", AreaCircle);
area = parser.SimplifyInt("AreaCircle(3.7)");
myDouble = parser.SimplifyDouble("Doubler(7)");
slope = parser.SimplifyDouble("GetSlope(-3.2, 12, 8, 13)");
} catch (Exception ex) {
throw ex;
}
}
static int Doubler(int num)
{
return num * 2;
}
static double AreaCircle(double radius)
{
return Math.PI * (radius * radius);
}
static double GetSlope(double x1,double x2,double y1, double y2)
{
return (y1-y2)/(x1-x2);
}
委托函数最多可以有 4 个参数。返回类型**必须**与参数相同,并且参数必须都是相同类型。参数可以是 int
、double
或 object
。整数和双精度类型不言自明。如果声明 object
类型的参数,则对象将是 MathParserNet.SimplificationReturnValue
类型。这样您的函数就可以同时接受整数和双精度参数。此外,您的返回类型必须是 object
,但该对象可以是整数或双精度。数学解析器将处理其他一切!
与变量和函数一样,委托函数可以通过使用 UnregisterCustomFunction()
方法来删除。您只需将函数名作为参数传递。此外,您还可以通过调用 UnregisterAllCustomFunctions()
方法删除**所有**已定义的委托函数。
杂项方法
还有一个 Reset()
方法。通过调用此方法,解析器将删除**所有**变量、**所有**函数和**所有**已创建的委托函数。这就像从一个全新的、刚刚实例化的 Parser
类开始。
最后一个要提及的小功能是 ToFraction()
函数。每当函数返回 SimplificationReturnValue
时,该对象都带有一个名为 ToFraction()
的方法,它会将返回的值转换为分数。例如,如果您的 SimplificationReturnValue
对象的 DoubleValue
为 0.25,那么调用 ToFraction()
将返回 "1/4"。我将把这个留给您去尝试。
附加功能
我包含了一个单元测试项目,用于测试数学解析库。此外,还有一个演示 Windows Forms 项目,展示了 MathParserNET 的许多(如果不是全部)功能。如果任何人有任何问题或反馈,我总是很乐意听取!
历史
- 2011年10月30日 -- 发布版本 1.1。此版本在出现错误时抛出异常而不是返回值。此外,此版本引入了委托方法。并且,已创建
SimplifyInt
和SimplifyDouble
方法。此外,我为数学库创建了一个真正的单元测试项目和一个演示应用程序。感谢所有反馈,请大家继续提供! - 2011年10月26日 -- 初始版本发布。