65.9K
CodeProject 正在变化。 阅读更多。
Home

将数学方程转换为 C#

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (164投票s)

2008年11月2日

CPOL

6分钟阅读

viewsIcon

431709

downloadIcon

13983

一个神奇的工具,可以即时将 Word 方程转换为 C#!

mmlsharp/MmlSharp4.jpg

引言

很久以前,我曾在一个产品上工作,其中一部分工作涉及将数学方程转化为代码。那时,我不是被分配这个角色的人,所以我的猜测是代码是通过简单地从 Word 中提取方程并手动将其翻译成 C# 来编写的。这听起来不错,但它让我想到了:有没有办法自动化这个过程,从而消除这个不可否认的枯燥任务中的人为错误?嗯,事实证明这是可能的,而这正是本文要讨论的内容。

方程,嗯?

我猜,除了任何特殊的数学软件包(如 Matlab)之外,我们大多数开发人员都会以 Word 格式获取数学需求。例如,你可能会得到像这样的简单内容

mmlsharp/MmlSharp1.jpg

这个方程很容易编程。看,我来做:y = a*x*x + b*x + c;。然而,有时你会得到非常棘手的方程,就像下面这样

mmlsharp/MmlSharp2.jpg

上面的内容来自维基百科。总之,你应该明白了:上面的方程有点太难编程了。我的意思是,我确信如果你有无限的预算或廉价劳动力,你可以做到,但我保证你会出错,因为每次都弄对(如果你有一百个)是困难的。

所以,我的想法是:嘿,应该有一种方法可以将方程数据以某种方式结构化,然后你就可以为 C# 重构它。这就是 MathML 进入的场景。

MathML

好吧,你可能在想这个MathML怪物是什么。基本上,它是一种类似 XML 的数学标记语言。如果所有浏览器都支持它,你就会看到上面的方程使用浏览器的字符而不是位图进行渲染。但无论如何,有一个工具支持它:Word。具体来说是 Microsoft Word 2007。有一个鲜为人知的技巧可以让 Word 将方程转换为 MathML。你基本上必须找到方程选项...

并选择 MathML 选项

好吧,现在将我们的第一个方程复制到剪贴板将得到类似以下内容

mmlsharp/MmlSharp3.jpg
<mml:math>
  <mml:mi>y</mml:mi>
  <mml:mo>=</mml:mo>
  <mml:mi>a</mml:mi>
  <mml:msup>
    <mml:mrow>
      <mml:mi>x</mml:mi>
    </mml:mrow>
    <mml:mrow>
      <mml:mn>2</mml:mn>
    </mml:mrow>
  </mml:msup>
  <mml:mo>+</mml:mo>
  <mml:mi mathvariant="italic">bx</mml:mi>
  <mml:mo>+</mml:mo>
  <mml:mi>c</mml:mi>
</mml:math>

通过查看原始方程,你大概可以猜出这一切的含义。嘿,我们只是提取了方程的结构!这很酷,除了一个问题:将其转换为 C#!(否则,它就没有意义了。)

语法树

保持数据原样是没用的。有很多额外的信息(比如 'bx' 附近的斜体说明),也有信息缺失(比如 'b' 和 'x' 之间应该存在的乘号)。所以,我们对这个问题的看法是将其 XML 结构转换为更面向对象的、类似 XML 的结构。事实上,这正是程序所做的——它将 XML 元素转换为相应的 C# 类。在大多数情况下,XML 和 C# 是一对一对应的,因此一个<mi/>元素变成一个Mi类。所以太好了,我们不费吹灰之力就将 XML 变成了语法树。现在,这棵树并不完美,但它确实存在。让我们来讨论一下我们必须克服的一些棘手问题。

单/多字母变量

“sin”是表示s乘以i乘以n,还是一个名为“sin”的变量,还是Math.Sin函数?当我查看我拥有的方程时,有些使用了多个字母,有些是单个字母。如何处理这些并没有“放之四海而皆准”的解决方案。基本上,我将其设置为一个选项。

乘号(×)

如果你写ab,它可能表示a乘以b。如果是这样,你需要找到所有省略乘号的位置。有趣的是,不同的数学编辑软件包(我用 MathML 和 Word 测试过)在乘号方面使用了不同的 Unicode 符号。结果是,找出乘号丢失的位置非常困难。

希腊字母转罗马字母

有些人反对在 C# 代码中使用希腊常数。嘿,我用 UTF-8 编码,所以我可以包含任何内容,包括日文字符和其他有趣的 Unicode 符号。这会搞乱 IntelliSense,因为你的键盘可能没有希腊键——除非你住在希腊。此外,这是快速破坏可维护性的一种方式。因此,我必须添加的一个功能是将希腊字母转换为罗马字母描述,这样 Δ 就会变成 Delta,依此类推。实际上,Delta 是一个特例,因为我们非常习惯于将其附加到变量上(例如,写 ΔV)。因此,我为 Δ 添加了一个特殊规则,使其在所有其他变量都是单字母的情况下也能保持附加。

正确处理 e、π 和 exp

基本上,字母 pi(π)可以只是一个变量,也可以表示Math.PI。字母 e 也是如此——它可以是Math.E,在大多数情况下也是如此。另一个更棘手的替换是 exp 到Math.Exp。所有这三者的支持都必须添加。

幂函数内联

大多数人都知道x*xMath.Pow(x, 2.0)快,尤其是在处理整数时。内联 X 的幂及以上是程序中的一个选项。我见过一些文章(找不到链接),其中有人声称如果你避免使用Math.Pow方式,就会丢失精度。我不太确定。

运算归约

我被告知,有些表达式的输出在其构成运算方面效率不高。例如,a*x*x+b*x+c不如x*(a*x+b)+c高效,因为它有更多的乘法。因此,我解决方案的未来目标之一是尝试优化这些场景。不过,这会降低它们的可读性!

在从 XML 转换为 C# 的过程中还有许多其他问题,但主要思想保持不变:在每种可能的 MathML 元素上正确实现 Visitor 模式,删除不必要的信息并提供缺失的信息。让我们看一些例子。

示例

好吧,我打赌你迫不及待想看到一个实际的例子。让我们从之前的那个开始

mmlsharp/MmlSharp1.jpg

这是我们得到的输出

y=a*x*x+b*x+c;

我省略了程序也创建的变量的初始化步骤。

让我们看看更复杂的方程。这是它,以防你忘记了

mmlsharp/MmlSharp2.jpg

敢猜猜我们工具的输出是什么吗?

p = rho*R*T + (B_0*R*T-A_0-((C_0) / (T*T))+((E_0) / (Math.Pow(T, 4))))*rho*rho +
    (b*R*T-a-((d) / (T)))*Math.Pow(rho, 3) +
    alpha*(a+((d) / (t)))*Math.Pow(rho, 6) +
    ((c*Math.Pow(rho, 3)) / (T*T))*(1+gamma*rho*rho)*Math.Exp(-gamma*rho*rho);

我最初的输出使用了希腊字母(提醒:C# 支持它们)。然而,由于编码的原因,我让我的工具将它们更改为罗马字母版本,从而展示了另一个功能。

好的,让我们再做一个例子来确保——这次带一个平方根。这是方程

mmlsharp/MmlSharp5.jpg

我关闭了此方程的幂函数内联——我们不希望根号表达式被评估两次。这是输出

a = 0.42748 * ((Math.Pow((R*T_c), 2)) / (P_c)) * 
  Math.Pow((1 + m * (1 - Math.Sqrt(T_r))), 2);

这很棒,不是吗?如果你有一天拿到一份包含数百页公式的文件,那么你可以通过快速地将它们编码来让你的客户惊喜。

结论

我希望你喜欢这个工具。也许你甚至会觉得它很有用。我最近使用 F# 从头开始重新设计了该工具,你可以在这里找到最新版本。

© . All rights reserved.