我在一篇隨筆“畫函數圖形的C#程式,兼論一個病態函數”中提到:
這個畫函數圖形的C#程式有一個嚴重的缺點,就是函數運算式是直接寫的來源程式中的,不能象SciLab和Matlab那樣互動式地輸入。不知道用 System.Reflection.Emit.ILGenerator 類能不能動態地產生使用者輸入的函數運算式?
“空間/IV”在該隨筆的評論中指出:
關於動態地產生使用者輸入的函數運算式, 看看下面這個文章說不定有協助:
http://community.csdn.net/Expert/topic/4169/4169185.xml
經研究,我寫了一個動態地產生使用者輸入的函數運算式的類(class Expression),運算式使用 C# 文法,可帶一個的自變數(x),其自變數和值均為“double”類型。下面是測試程式的運行結果:
C> ExpressionTest
Usage: ExpressionTest expression [ parameters ... ]
C> ExpressionTest Math.PI*Math.E 0
f(x): Math.PI*Math.E
f(0) = 8.53973422267357
C> ExpressionTest Math.Pow(2,x) 0 10 49 50 1024 -1 -1024
f(x): Math.Pow(2,x)
f(0) = 1
f(10) = 1024
f(49) = 562949953421312
f(50) = 1.12589990684262E+15
f(1024) = 正無窮大
f(-1) = 0.5
f(-1024) = 5.562684646268E-309
C> ExpressionTest "double u = Math.PI - x; double pi2 = Math.PI * Math.PI; return 3 * x * x + Math.Log(u * u) / pi2 / pi2 + 1;" 3.13 3.14 3.15 3.16 3.1416
f(x): double u = Math.PI - x; double pi2 = Math.PI * Math.PI; return 3 * x * x + Math.Log(u * u) / pi2 / pi2 + 1;
f(3.13) = 30.2991811562164
f(3.14) = 30.44652582187
f(3.15) = 30.6693849404716
f(3.16) = 30.8747746902426
f(3.1416) = 30.3662371931734
其中最後一個例子就是我在隨筆“畫函數圖形的C#程式,兼論一個病態函數”的下列函數的計算結果:
實際上這個病態函數是《C數值演算法(第二版)》第三章“插入法和外推法”中提到的:
---------------------------------------------------------------------------
可以很容易地構造一些病態函數使插入法失敗。例如,考慮函數
f(x) = 3 * x2 + π-4 * ln[(π-x)2] + 1
它除了 x = π 之外都有定義,而 x = π 時無定義,其它情況,值有正有負。而這函數在任何基於數值 x = 3.13, 3.14, 3.15, 3.16 的插值法,都肯定在 x = 3.1416 處得到一個錯誤的解,儘管通過這五個點所畫的曲線確實相當平滑!(用計算機試試看。)
---------------------------------------------------------------------------
可以看出,而這函數在任何基於數值 x = 3.13, 3.14, 3.15, 3.16 的插值法,在 x = 3.1416 處得到的解肯定在 30.44652582187 和 30.6693849404716 之間,但實際的解應該是 30.3662371931734,所以說作者斷言在該處肯定會得到一個錯誤的解。
下面就是來源程式: // ExpressionTest.cs - 動態產生數學運算式並計算其值的測試程式
// 編譯方法: csc ExpressionTest.cs Expression.cs
using System;
using Skyiv.Util;
namespace Skyiv.Test
{
class ExpressionTest
{
static void Main(string [] args)
{
try
{
if (args.Length > 0)
{
Console.WriteLine("f(x): {0}", args[0]);
Expression expression = new Expression(args[0]);
for (int i = 1; i < args.Length; i++)
{
double x = double.Parse(args[i]);
Console.WriteLine("f({0}) = {1}", x, expression.Compute(x));
}
}
else Console.WriteLine("Usage: ExpressionTest expression [ parameters ]");
}
catch (Exception ex)
{
Console.WriteLine("錯誤: " + ex.Message);
}
}
}
}
// Expression.cs - 動態產生數學運算式並計算其值
// 運算式使用 C# 文法,可帶一個的自變數(x)。
// 運算式的自變數和值均為(double)類型。
// 使用舉例:
// Expression expression = new Expression("Math.Sin(x)");
// Console.WriteLine(expression.Compute(Math.PI / 2));
// expression = new Expression("double u = Math.PI - x;" +
// "double pi2 = Math.PI * Math.PI;" +
// "return 3 * x * x + Math.Log(u * u) / pi2 / pi2 + 1;");
// Console.WriteLine(expression.Compute(0));
using System;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Reflection;
using System.Text;
namespace Skyiv.Util
{
sealed class Expression
{
object instance;
MethodInfo method;
public Expression(string expression)
{
if (expression.IndexOf("return") < 0) expression = "return " + expression + ";";
string className = "Expression";
string methodName = "Compute";
CompilerParameters p = new CompilerParameters();
p.GenerateInMemory = true;
CompilerResults cr = new CSharpCodeProvider().CompileAssemblyFromSource(p, string.
Format("using System;sealed class {0}{{public double {1}(double x){{{2}}}}}",
className, methodName, expression));
if(cr.Errors.Count > 0)
{
string msg = "Expression(\"" + expression + "\"): \n";
foreach (CompilerError err in cr.Errors) msg += err.ToString() + "\n";
throw new Exception(msg);
}
instance = cr.CompiledAssembly.CreateInstance(className);
method = instance.GetType().GetMethod(methodName);
}
public double Compute(double x)
{
return (double)method.Invoke(instance, new object [] { x });
}
}
}
在這裡向 CSDN 論壇的“LoveCherry(論成敗,人生豪邁;大不了,重頭再來!^_^) ”表示感謝,我的程式就是在他的程式的基礎上發展而來的。