|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionHave you ever wondered how Microsoft Excel solves its formulas? This here is your way to get your own, personal and fast formula interpreter. It is used in products such as:
BackgroundIf you are writing a formula interpreter, you should decide which technology you want to use. As you might expect, there are lots and lots of possibilities to do this. I decided to do it recursively, because this offers a very easy way to use delegates, translate it into unmanaged C++, or add special functionality such as differential- and integral calculations. Using the codeUsing the interpreter is very easy:
How it worksThe magic of the interpreter lies in a function called There are different types of commands which a string can contain:
/// <summary>
/// Rekursive Hauptprozedur, die die Formel ausrechnet
/// </summary>
/// <param name="Op">Der Text mit der Formel</param>
/// <returns>Das Ergebnis der
/// Formel oder 0 bei Fehlern</returns>
private double Zuteilung(string Op)
{
if (Op.Length==0) goto fehl;
if (Op.StartsWith("(") &&
GetKlammer(Op,0) ==Op.Length-1)
Op=Op.Substring(1,Op.Length-2);
if (double.TryParse(Op,
System.Globalization.NumberStyles.Float,
null,out res))
return res;
if (Op.Length==2 && Op=="PI") return Math.PI;
if (Op.Length==1 && TryVariable(Op,out res)) return res;
if (Op.Length>4 && Op.Substring(3,1)=="(")
{
pos=GetKlammer(Op,3);
if (pos!=Op.Length-1) goto next;
zc=Op.Substring(4,pos-4);
switch (Op.Substring(0,3))
{
case "sqr":return Math.Sqrt(Zuteilung(zc));
case "sin":return Math.Sin(Math.PI*Zuteilung(zc)/180);
case "cos":return Math.Cos(Math.PI*Zuteilung(zc)/180);
case "tan":return Math.Tan(Math.PI*Zuteilung(zc)/180);
case "log":return Math.Log10(Zuteilung(zc));
case "abs":return Math.Abs(Zuteilung(zc));
case "fac":return Faculty(Zuteilung(zc));
}
}
next:
pos=0;ebene=6;klammer=0;
for (int i=Op.Length-1;i>-1;i--)
{
switch (Op.Substring(i,1))
{
case "(":klammer++;break;
case ")":klammer--;break;
case "+":if(klammer==0 && ebene>0){pos=i;ebene=0;}break;
case "-":if(klammer==0 && ebene>1){pos=i;ebene=1;}break;
case "*":if(klammer==0 && ebene>2){pos=i;ebene=2;}break;
case "%":if(klammer==0 && ebene>3){pos=i;ebene=3;}break;
case "/":if(klammer==0 && ebene>4){pos=i;ebene=4;}break;
case "^":if(klammer==0 && ebene>5){pos=i;ebene=5;}break;
}
}
if (pos==0 || pos==Op.Length-1) goto fehl;
zc=Op.Substring(pos,1);
string t1,t2;
t1=Op.Substring(0,pos);
t2=Op.Substring(pos+1,Op.Length-(pos+1));
switch (zc)
{
case "+":return Zuteilung(t1)+Zuteilung(t2);
case "-":return Zuteilung(t1)-Zuteilung(t2);
case "*":return Zuteilung(t1)*Zuteilung(t2);
case "/":return Zuteilung(t1)/Zuteilung(t2);
case "%":return Math.IEEERemainder(Zuteilung(t1),Zuteilung(t2));
case "^":return Math.Pow(Zuteilung(t1),Zuteilung(t2));
}
fehl:
fehler=true;
return 0.0;
}
When analyzing operator strings, you have pay special attention: 2+3-4-2 => you have to interpret it as ((2+3)-4)-2
To do this, you have to go through the formula from the back, and give the operators signs:
Then calculate the lowest sign operator, and you get a kind of a tree. But you have to take care about the brackets => you can only interpret the outer level of the formula: 2+3-(4-2) => you can't interpret the subtraction in brackets by now
Also, there is the problem of picking the right one of the wrapping brackets: (3+2+4)+sin(x) => if you take the last bracket, it's wrong,
you have to take the one after +4.
Therefore, you have to write a function to find the matching bracket always: /// <summary>
/// Sucht zu einer geöffneten Klammer die
/// Position der entsprechend schließenden Klammer
/// </summary>
/// <param name="Op">Der zu überprüfende Text</param>
/// <param name="start">Position der
/// eröffnenden Klammer</param>
/// <returns>Position der schließenden Klammer,
/// oder START, wenn keine vorhanden</returns>
private int GetKlammer(string Op,int start)
{
int res=start;
for (int i=start;i<Op.Length;i++)
{
switch (Op.Substring(i,1))
{
case "(":klammer++;break;
case ")":klammer--;break;
}
if (klammer==0){res=i;break;}
}
return res;
}
The function increments a variable called Furthermore, you have to decide if the string is a variable and then return its value. The private string vars="";
private const string allowed="abcdefgxyz";
private double[] values=new double[10];/// <summary>
/// Prüft, ob der Text eine Variable bezeichnet
/// und schreibt den Wert in eine Variable
/// </summary>
/// <param name="Op">Der zu überprüfende Text</param>
/// <param name="wert">Adresse der Variable,
/// die den Wert erhalten soll</param>
/// <returns>TRUE, wenn eine Variable
/// gefunden wurde</returns>
private bool TryVariable(string Op,out double wert)
{
int i=vars.IndexOf(Op);
if (i!=-1)
{
wert=values[i];
return true;
}
else
{
wert=0.0;
return false;
}
}
The function is called by the main function. The rest of the For more information, see the source code itself. Points of InterestUnless I think it's very useful, this is just the beginning point of a formula interpreter. Just go forward and modify it to fit your personal needs. For example, the recursive procedure can easily be updated to use delegates, which is up to 4000 times faster and very good for iterational calculations (function plotters etc.). There is already a delegate-version working in VariPlot Reload.
If you translate VariFormel into C++ or make any other modifications, I would be glad if you would drop me a mail so I can see how VariFormel evolves. Have fun with the code :-)
|
|||||||||||||||||||||||||