公式翻译

本文介绍了一种用于解析和计算数学公式的算法实现,该算法能够处理包含多种运算符和函数的复杂表达式,并支持变量替换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 又是一个寒冷的夜晚,我坐在床头,这个时候我只有两件事情能做,一是看《圣经》,一是看《数据结构》,但明显前者对我来说比后者难得多,一来我不是信徒,二来内容是在枯燥,缺乏创造性,但更关键的是:其实我没有《圣经》……所以翻起了那本准天书——《Data Structures and Algorithms with Object-Oriented Design Patterns in C++》,开始神游……
 
想到自己在三年多以前,那时候刚毕业,经常往学校里跑,当时还在给老师写一个换热器计算的应用程序,(BTW:我是动力工程系的毕业生)换热器计算中有个叫“导热系数”的变量十分关键,但也十分难以获得,传热学几乎是一门算不准的学科,能保证你的计算误差在20%之内就非常优秀了,所以我们大量借鉴了经验拟合公式,这种公式是基于大量实验数据,再通过一些软件工具来拟合得出的大致准确的计算方法,其实Excel就有这个拟合功能,具体我这里就不岔开了,那么拟合出来的公式大概像下面这个样子:
 
R = 2.665*A^3 - 1.772*A^2 + 3.123*A + 1.231
 
其中R为我们所需要的结果,A为变量,在换热计算领域,温度经常作为变量,“^”符号代表幂运算,“A^3”表示“A*A*A”,上面这个算式用C++语言来表达就是:
 
R = 2.665*pow(A,3) - 1.772*pow(A,2) + 3.123*A + 1.231;
 
可实际情况可能还要复杂,可能会运用到对数运算ln(A),还可能嵌套几层括号,工科中能遇到的公式是数不清的,所以当时我设计换热器计算程序的时候,就考虑到一个“可扩展性”,企图把公式以文本形式记录在文件中,或者存储在数据库中,而不是写死在程序里,但当时我做不到,学院里有个老师也遇到这个问题,他也觉得是个难题,我还为此请教过一些做计算机软件开发的朋友(当时我还不是软件人),他们都认为不容易,其中一个还说:“这个工程相当于做个微型编译器了。”于是后来就只能把公式写死在程序中……直到今天想起来,发现其实也没有想象中的难,于是开始比比划划,把这个程序写了下来。
 
先从一个简单算式入手:
 
4+3*2-5*3+6
 
大家都知道,四则运算的规则是先乘除后加减,所以我们也有我们的拆分规则,而不是从头到尾算,加减运算优先级最低,可认为结构最松散,所以先拆加减,遇到加减号之后就把算式从加减号之处断开,分为两部分,前一部分和后一部分各自递归,直到没法再分,这是我一开始的方法,如下所示:
 
4+3*2-5*3+6 => 4 + 3*2-5*3+6,4不可再分,把3*2-5*3+6投入递归
3*2-5*3+6 => 3*2 - 5*3+6,暂时也认为乘法不可再分,把5*3+6投入递归
5*3+6 => 5*3 + 6,完成加法减法分割
 
计算:
5*3+6 = 21
3*2-21 = -15
4+(-15) = -11
 
答案是:-11
 
明显答案是错误的,4+3*2-5*3+6,小学生都知道应该答案是1,为什么出错?因为上述的二项拆分法将算式实际上变成了下面的算式:
 
4+(3*2-(5*3+6))
 
这是肯定不行的,所以二项拆分法是不行的,我将它改为多项拆分法,以下是改进描述:
 
4+3*2-5*3+6 => 4 + 3*2-5*3+6,temp=4,将第一个加减号前的算式投入递归,用一个临时变量temp来记录其计算返回值,剩下的继续拆分而不投入递归
3*2-5*3+6 => 3*2 - 5*3+6,temp=4 + 3*2=10,将第二个加减号前,第一个加减号后的算式投入递归,将其返回值根据前一个符号加入到temp中(前一个符号为+的时候),或从temp中减去(前一个符号为-的时候),剩下的继续拆
5*3+6 => 5*3 + 6,temp=10 - 5*3=-5,步骤和上面一样,不用说了
6 => temp=-5 + 6=1,最后结果是1,正确。
 
多项拆分法需要借助一个临时变量来记录投入递归的表达式的返回值。其实不光加减拆分有这种问题,乘除拆分也有这种问题,所以乘除的拆分和加减的拆分都使用这种办法,具体就不再赘述,还是具体看程序比较清楚。
 
四则运算之后就是幂运算,幂运算在我的表达式中用“^”符号来表示,其优先级要比四则运算高,但低于函数和括号,所以拆分了加减乘除之后就轮到它了。
 
幂运算之后就是函数运算及括号运算,由于函数和括号实际上都带了“括号”,可看作同等优先级。括号其实是比较繁琐的东西,为了解决括号问题,让我们的程序“聪明地”去辨别括号表达式,我添加了一个PD(parenthesis depth,括号深度)值,遍历表达式,遇到“(”时候PD值加1,遇到“)”时候,PD值减1,所以PD值为0的时候,表示没被括号包围,为1的时候表示被一层括号包围,为2的时候表示被两层括号包围,依此类推,当我们一个表达式遍历完毕后,如果发现PD值不为0,那说明什么?当然说明括号不匹配,表达式非法了。如果当前的PD值不为0,遇到上述的四则运算符和乘幂运算符的时候都不拆分,急着拆什么啊?当前正被括号括着呢,括号的优先级是最高的,留后面拆。下面举例说明:
 
4+3*(7+(2-5)/3)+6
 
从左到右遍历这个表达式,先对加减进行拆分,遇到第一个加号的时候,PD值为0,所以将表达式“4”投入递归,记录其返回值,而遇到第二个加号和第一个减号的时候,PD值都不为0,所以暂时都不能拆分,直到遇到第三个加号,这个时候的PD值变为0,于是对第一个加号到第三个加号中间这段,即“3*(7+(2-5)/3)”当作一个表达式投入递归,累计其返回值。这里对“3*(7+(2-5)/3)”的处理进一步讲解:
 
3*(7+(2-5)/3)
 
做法一样,也是从左到右,先对加减进行拆分,但PD值为0的时候没有遇到任何加减号,所以没有加减拆分,接下来是乘除拆分,遇到第一个乘号的时候PD值为0,拆!把拆出来的“3”投入递归,并记录返回,遗憾的是在遍历之后的表达式中发现PD值都不为0,(7+(2-5)/3)就没法拆分了,将其整块投入递归!再进一步讲解:
 
(7+(2-5)/3)
 
整个表达式被括号围着,PD值从头到尾都不为0,那么加减乘除幂都拆分不了了,所以轮到了括号拆分,左右括号一去,变成了“7+(2-5)/3”,将“7+(2-5)/3”直接投入递归,开始“轮回”吧!接下去就不用说了吧。(我也说累了)
 
那有人要问,上面的表达式中都没有变量啊,我的公式运算可是需要变量的,怎么办?我当然想到了这个问题,一个公式里最多几个变量啊?5个?10个?哇,你说什么?20个?好好好,我知道你够强的,我算26个,你总该满足了吧,我用‘A’-‘Z’来表示变量,那么遇到‘A’-‘Z’就根据传进来的参数,把它替换成数值,看一下程序什么都明白了。
 
讲得也差不多了,对一个递归函数来说,我写得未免太长,但方法大概就这样,还有程序可能或多或少还有些bug,需要完善一下。另外目前只能识别六种工程计算中最常用的函数:sin,cos,tan,exp,log和ln。
 
最后但也不是最后,这个程序我还打算把它做成COM,(过一阵子做)毕竟我们这些非计算机系的工科学生可不太会用VC++,多半都在用VB啊,Delphi啊,Fortran啊……没错,就是Fortran!多古老的语言,Fortran是什么?Formula Translation!就是这篇文章的标题,也是我程序中递归函数的名字,嘿嘿!
 
// Arithmetic.cpp

#include 
"stdafx.h"
#include 
<math.h>
#include 
<string.h>

enum FORTRAN_ERROR
{
    FE_UNEXPECTED_RIGHT_PARENTHESIS
=1,  //出现不期待的')'
    FE_EXPECTING_RIGHT_PARENTHESIS,     //缺少')'
    FE_NOT_INTEGRAL_EXPRESSION,         //非完整表达式
    FE_INVALID_FUNCTION_EXPRESSION_SIN, //非法正弦表达式
    FE_INVALID_FUNCTION_EXPRESSION_COS, //非法余弦表达式
    FE_INVALID_FUNCTION_EXPRESSION_TAN, //非法正切表达式
    FE_INVALID_FUNCTION_EXPRESSION_EXP, //非法pow(e,x)表达式
    FE_INVALID_FUNCTION_EXPRESSION_LOG, //非法对数表达式
    FE_INVALID_FUNCTION_EXPRESSION_LN   //非法自然对数表达式
};

enum FORTRAN_FUNCTIONS
{
    FF_SIN
=1,
    FF_COS,
    FF_TAN,
    FF_EXP,
    FF_LOG,
    FF_LN
};

//Formula Translation
double Fortran(char *formula, int len, const double *arg) throw()
{
    
if (len<=0)
    {
        printf(
"Not a integral expression. ");
        
throw(FE_NOT_INTEGRAL_EXPRESSION);
        
return 0.0;
    }

    
char *pIter; //遍历器
    int iInd; //指示器,必须<len
    int iPD = 0//花括号深度,从0开始,Parenthesis Depth

    
//先拆 + -
    
//找出第一个“+ -”拆分处
    double dResult=0;
    
bool bCal = false//表示存在 + - 拆分
    char* pHead = formula;
    
int iAddMinusFlag = 0//前一符号,0表示+,1表示减
    for(iInd=0, pIter=formula; iInd<len; iInd++, pIter++)
    {
        
if (*pIter=='('
        {
            iPD
++;
        }
        
if (*pIter==')')
        {
            
if (iPD>0
            {
                iPD
--;
            }
            
else
            {
                printf(
"Unexpected '%c' found. "')');
                
throw(FE_UNEXPECTED_RIGHT_PARENTHESIS);
                
return 0.0;
            }
        }
        
if (iPD!=0
        {
            
continue;
        }
        
        
if (*pIter=='+')
        {
            
//拆分!+号前的一部分,+号后的一部分
            printf("+ for1[%s]len1[%d] ", pHead, pIter-pHead, arg);
            bCal 
= true;
            
if (iAddMinusFlag==0
            {
                dResult 
+= Fortran(pHead, pIter-pHead, arg);
            }
            
else
            {
                dResult 
-= Fortran(pHead, pIter-pHead, arg);
            }
            
            pHead 
= pIter + 1;
            iAddMinusFlag 
= 0;
        }
        
if (*pIter=='-')
        {
            bCal 
= true;
            printf(
"- for1[%s]len1[%d] ", pHead, pIter-pHead, arg);
            
if (iAddMinusFlag==0
            {
                dResult 
+= Fortran(pHead, pIter-pHead, arg);
            }
            
else
            {
                dResult 
-= Fortran(pHead, pIter-pHead, arg);
            }
            
            pHead 
= pIter + 1;
            iAddMinusFlag 
= 1;
        }
    }
    
if (iPD!=0
    {
        printf(
"Expecting '%c'. "')');
        
throw(FE_EXPECTING_RIGHT_PARENTHESIS);
        
return 0.0;
    }
    
if (bCal) 
    {
        
if (iAddMinusFlag==0
        {
            dResult 
+= Fortran(pHead, pIter-pHead, arg);
            
return dResult;
        }
        
else
        {
            dResult 
-= Fortran(pHead, pIter-pHead, arg);
            
return dResult;
        }
    }

    
//后拆 * /
    dResult=1;
    bCal 
= false;
    pHead 
= formula;
    iAddMinusFlag 
= 0//前一符号,0表示乘,1表示除
    for(iInd=0, pIter=formula; iInd<len; iInd++, pIter++)
    {
        
if (*pIter=='('
        {
            iPD
++;
        }
        
if (*pIter==')')
        {
            iPD
--;
        }
        
if (iPD!=0
        {
            
continue;
        }
        
if (*pIter=='*')
        {
            
//拆分!*号前的一部分,*号后的一部分
            printf("* for1[%s]len1[%d] ", pHead, pIter-pHead, arg);
            bCal 
= true;
            
if (iAddMinusFlag==0
            {
                dResult 
*= Fortran(pHead, pIter-pHead, arg);
            }
            
else
            {
                dResult 
/= Fortran(pHead, pIter-pHead, arg);
            }
            
            pHead 
= pIter + 1;
            iAddMinusFlag 
= 0;

        }
        
if (*pIter=='/'
        {
            printf(
"/ for1[%s]len1[%d] ", pHead, pIter-pHead, arg);
            bCal 
= true;
            
if (iAddMinusFlag==0
            {
                dResult 
*= Fortran(pHead, pIter-pHead, arg);
            }
            
else
            {
                dResult 
/= Fortran(pHead, pIter-pHead, arg);
            }
            
            pHead 
= pIter + 1;
            iAddMinusFlag 
= 1;
        }
    }

    
if (bCal) 
    {
        
if (iAddMinusFlag==0
        {
            dResult 
*= Fortran(pHead, pIter-pHead, arg);
            
return dResult;
        }
        
else
        {
            dResult 
/= Fortran(pHead, pIter-pHead, arg);
            
return dResult;
        }
    }

    
//再拆 ^
    for(iInd=0, pIter=formula; iInd<len; iInd++, pIter++)
    {
        
if (*pIter=='('
        {
            iPD
++;
        }
        
if (*pIter==')')
        {
            iPD
--;
        }
        
if (iPD!=0
        {
            
continue;
        }
        
if (*pIter=='^')
        {
            
//拆分!^号前的一部分
            printf("^ for1[%s]len1[%d] for2[%s]len2[%d] ", formula, pIter-formula, pIter+1, len-(pIter-formula)-1);
            
return pow(Fortran(formula, pIter-formula, arg), Fortran(pIter+1, len-(pIter-formula)-1, arg));
        }
    }

    
//最后拆六大函数及括号
    FORTRAN_FUNCTIONS funtype;
    
bool bFunFlag=false;
    
char *pSubHead;
    
for(iInd=0, pIter=formula; iInd<len; iInd++, pIter++)
    {
        
if (0==memcmp(pIter, "sin"3&& (pIter-formula)<(len-5)) 
        {
            bFunFlag 
= true;
            funtype 
= FF_SIN;
            iInd
+=3;
            pIter
+=3;
        }

        
else if (0==memcmp(pIter, "cos"3&& (pIter-formula)<(len-5)) 
        {
            bFunFlag 
= true;
            funtype 
= FF_COS;
            iInd
+=3;
            pIter
+=3;
        }
        
else if (0==memcmp(pIter, "tan"3&& (pIter-formula)<(len-5)) 
        {
            bFunFlag 
= true;
            funtype 
= FF_TAN;
            iInd
+=3;
            pIter
+=3;
        }
        
else if (0==memcmp(pIter, "exp"3&& (pIter-formula)<(len-5)) 
        {
            bFunFlag 
= true;
            funtype 
= FF_EXP;
            iInd
+=3;
            pIter
+=3;
        }
        
else if (0==memcmp(pIter, "log"3&& (pIter-formula)<(len-5)) 
        {
            bFunFlag 
= true;
            funtype 
= FF_LOG;
            iInd
+=3;
            pIter
+=3;
        }
        
else if (0==memcmp(pIter, "ln"2&& (pIter-formula)<(len-4)) 
        {
            bFunFlag 
= true;
            funtype 
= FF_LN;
            iInd
+=2;
            pIter
+=2;
        }

        
if(bFunFlag)
        {
            
if (*pIter!='(' && *pIter!=' ')
            {
                printf(
"Invalid sin() expression. ");
                
throw(FE_INVALID_FUNCTION_EXPRESSION_SIN);
                
return 0.0;
            }
            
//期待'('
            while (iInd<len) 
            {
                
if (*pIter=='(')
                {
                    
if (iPD==0
                    {
                        pSubHead 
= pIter;
                    }
                    iPD
++;
                }
                
if (*pIter==')'
                {
                    iPD
--;
                    
if (iPD==0
                    {
                        
//over
                        switch(funtype) 
                        {
                        
case FF_SIN:
                            printf(
"sin for1[%s]len1[%d] ", pSubHead+1, pIter-pSubHead-1);
                            
return sin(Fortran(pSubHead+1, pIter-pSubHead-1, arg));
                            
break;
                        
case FF_COS:
                            printf(
"cos for1[%s]len1[%d] ", pSubHead+1, pIter-pSubHead-1);
                            
return cos(Fortran(pSubHead+1, pIter-pSubHead-1, arg));
                            
break;
                        
case FF_TAN:
                            printf(
"tan for1[%s]len1[%d] ", pSubHead+1, pIter-pSubHead-1);
                            
return tan(Fortran(pSubHead+1, pIter-pSubHead-1, arg));
                            
break;
                        
case FF_EXP:
                            printf(
"exp for1[%s]len1[%d] ", pSubHead+1, pIter-pSubHead-1);
                            
return exp(Fortran(pSubHead+1, pIter-pSubHead-1, arg));
                            
break;
                        
case FF_LOG:
                            printf(
"log for1[%s]len1[%d] ", pSubHead+1, pIter-pSubHead-1);
                            
return log10(Fortran(pSubHead+1, pIter-pSubHead-1, arg));
                            
break;
                        
case FF_LN:
                            printf(
"ln for1[%s]len1[%d] ", pSubHead+1, pIter-pSubHead-1);
                            
return log(Fortran(pSubHead+1, pIter-pSubHead-1, arg));
                            
break;
                        }
                    }
                }
                pIter
++;
                iInd
++;
            }
        }
        
        
if (*pIter=='(' && iPD==0
        {
            pSubHead 
= pIter;
            
while (iInd<len)  //搜索结束的')'
            {
                
if (*pIter=='('
                {
                    iPD
++;
                }
                
if (*pIter==')')
                {
                    iPD
--;
                    
if (iPD==0
                    {
                        printf(
"() for1[%s]len1[%d] ", pSubHead+1, pIter-pSubHead-1);
                        
return Fortran(pSubHead+1, pIter-pSubHead-1, arg);
                    }
                }
                pIter
++;
                iInd
++;
            }
        }
    }

    
//全找不到符号之后返回数字
    if (formula[0]>='A' && formula[0]<='Z')
    {
        
return arg[formula[0]-'A'];
    }
    
else
    {
        
return atof(formula);
    }
}

int main(int argc, char* argv[])
{
    
double args[2= {2.01.0};
    
char *formula = "sin(3.1415/2)*2+45*ln(1.7)/A-10*2+6^2+B";
    
double res;
    
try
    {
        res 
= Fortran(formula, strlen(formula), args);
    }
    
catch (FORTRAN_ERROR err) 
    {
        printf(
"Error, code = %d ", (int)err);
        
return -1;
    }
    printf(
"result:[%f] ", res);

    
return 0;
}

 祝各位圣诞节快乐!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值