C# 逆波兰表达式解析公式

你是否遇到这样的需求:如何计算“1.0+3/2-tan(45)/(1+1)+Abs(-10)-floor(2.55)” 这一串字符串的值?

 

思路:

定义:

逆波兰表达式又叫做后缀表达式。在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,这种表示法也称为中缀表示。波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的方法,按此方法,每一运算符都置于其运算对象之后,故称为后缀表示。

人类最熟悉的一种表达式1+2,(1+2)*3,3+42+4等都是中缀表示法。对于人们来说,也是最直观的一种求值方式,先算括号里的,然后算乘除,最后算加减,但是,计算机处理中缀表达式却并不方便。

然后我们还需明确一些概念,下面通过我们最熟悉的中缀表达式画出一棵语法树来直观认识一下前后缀表达式的生成。以A+B*(C-D)-E*F为例:

C# 逆波兰表达式解析公式_第1张图片

从上面可以发现,其实问题就2个:

1. 将人类常用的中缀表达式转换为后缀表达式

2. 计算后缀表达式。

转换为后缀表达式

这里我们要用到堆栈Stack, 详细概念可以点这里。

1.数字直接入栈
2.运算符要与栈顶元素比较
 ①栈为空直接入栈
 ②运算符优先级大于栈顶元素优先级则直接入栈
 ③小于或等于则出栈入列,再与栈顶元素进行比较,直到运算符优先级小于栈顶元
    素优先级后,操作符再入栈
3.操作符是 ( 则无条件入栈
4.操作符为 ),则依次出栈入列,直到匹配到第一个(为止,此操作符直接舍弃,(直接出栈舍弃

代码实现如下,首先定义操作数类型,因为我们不仅想计算简单的加减乘除,还想实现一些简单的函数和逻辑运算。

    /// 
    /// 操作数类型
    /// 
    public enum Type
    {
        /// 
        /// 函数
        /// 
        FUNC = 1,

        /// 
        /// 日期
        /// 
        DATE = 2,

        /// 
        /// 数字
        /// 
        NUMBER = 3,

        /// 
        /// 布尔
        /// TODO 
        /// 
        BOOLEAN = 4,

        /// 
        /// 字符串
        /// 
        STRING = 5
    }

然后我们定义一个操作数类:

    /// 
    /// 操作数类
    /// 
    public class Operand
    {
        public Operand(Type type, object value)
        {
            this.Type = type;
            this.Value = value;
        }

        public Operand(string opd, object value)
        {
            this.Type = ConvertOperand(opd);
            this.Value = value;
        }
       
        /// 
        /// 操作数类型
        /// 
        public Type Type { get; set; }

        /// 
        /// 关键字
        /// 
        public string Key { get; set; }

        /// 
        /// 操作数值
        /// 
        public object Value { get; set; }

        /// 
        /// 转换操作数到指定的类型
        /// 
        /// 操作数
        /// 返回对应的操作数类型
        public static Type ConvertOperand(string opd)
        {
            if (opd.IndexOf("(") > -1)
            {
                return Type.FUNC;
            }
            else if (IsNumber(opd))
            {
                return Type.NUMBER;
            }
            else if (IsDate(opd))
            {
                return Type.DATE;
            }
            else
            {
                return Type.STRING;
            }
        }

        /// 
        /// 判断对象是否为数字
        /// 
        /// 对象值
        /// 是返回真,否返回假
        public static bool IsNumber(object value)
        {
            return Regex.IsMatch(value.ToString(), @"^[+-]?\d*[.]?\d*$");
            // return double.TryParse(value.ToString(), out _); 用正则表达式快一点
        }

        /// 
        /// 判断对象是否为日期
        /// 
        /// 对象值
        /// 是返回真,否返回假
        public static bool IsDate(object value)
        {
            return DateTime.TryParse(value.ToString(), out _);
        }
    }

然后定义运算符的枚举,从小到大优先级递减:

    /// 
    /// 运算符类型(从上到下优先级依次递减),数值越大,优先级越低
    /// 
    public enum OptType
    {
        #region 最高优先级操作符

        /// 
        /// 左括号:(,left bracket
        /// 
        LB = 1,

        /// 
        /// 右括号),right bracket
        /// 
        RB = 2,

        /// 
        /// 逻辑非,!,NOT
        /// 
        NOT = 3,

        /// 
        /// 正号,+,positive sign
        /// 
        PS = 5,

        /// 
        /// 负号,-,negative sign
        /// 
        NS = 7,

        #endregion

        #region 函数

        /// 
        /// 正切,tan
        /// 
        TAN = 10,

        /// 
        /// 正切,tan
        /// 
        COT = 11,

        /// 
        /// 反正切,atan
        /// 
        ATAN = 12,

        /// 
        /// 正弦
        /// 
        SIN = 13,

        /// 
        /// 余弦
        /// 
        COS = 14,

        /// 
        /// 绝对值
        /// 
        Abs = 15,
        
        /// 
        /// 开平方
        /// 
        Sqrt = 16,

        /// 
        /// 向下取整
        /// 
        Floor = 17,

        /// 
        /// 向上取整
        /// 
        Ceiling = 18,

        /// 
        /// 四舍五入2位小数
        /// 
        Round = 19,

        /// 
        /// 平方
        /// 
        Pow = 20,

        /// 
        /// 立方
        /// 
        Cube = 21,

        /// 
        /// 自然对数
        /// 
        Ln = 22,

        /// 
        /// 10为底的对数
        /// 
        Log = 23,

        #endregion

        #region 其他操作符

        /// 
        /// 乘,*,multiplication
        /// 
        MUL = 130,

        /// 
        /// 除,/,division
        /// 
        DIV = 131,

        /// 
        /// 余,%,modulus
        /// 
        MOD = 132,

        /// 
        /// 加,+,Addition
        /// 
        ADD = 140,

        /// 
        /// 减,-,subtraction
        /// 
        SUB = 141,

        /// 
        /// 小于,less than
        /// 
        LT = 150,

        /// 
        /// 小于或等于,less than or equal to
        /// 
        LE = 151,

        /// 
        /// 大于,>,greater than
        /// 
        GT = 152,

        /// 
        /// 大于或等于,>=,greater than or equal to
        /// 
        GE = 153,

        /// 
        /// 等于,=,equal to
        /// 
        ET = 160,

        /// 
        /// 不等于,unequal to
        /// 
        UT = 161,

        /// 
        /// 逻辑与,&,AND
        /// 
        AND = 170,

        /// 
        /// 逻辑或,|,OR
        /// 
        OR = 171,

        /// 
        /// 逗号,comma
        /// 
        CA = 180,

        /// 
        /// 结束符号 @
        /// 
        END = 255,

        /// 
        /// 错误符号
        /// 
        ERR = 256

        #endregion
        
    }

再定义一个运算符类,用来把输入字符串中的运算符提取出来:

    /// 
    /// 运算符类
    /// 
    public class Operator
    {
        public Operator(OptType type, string value)
        {
            this.Type = type;
            this.Value = value;
        }

        /// 
        /// 运算符类型
        /// 
        public OptType Type { get; set; }

        /// 
        /// 运算符值
        /// 
        public string Value { get; set; }

        /// 
        /// 对于>或者<运算符,判断实际是否为>=,<>、<=,并调整当前运算符位置
        /// 
        /// 当前运算符
        /// 当前表达式
        /// 当前运算符位置
        /// 返回调整后的运算符
        public static string AdjustOperator(string currentOpt, string currentExp, ref int currentOptPos)
        {
            #region 操作符 <>

            if (currentOpt == "<")
            {
                if (currentExp.Substring(currentOptPos, 2) == "<=")
                {
                    currentOptPos++;
                    return "<=";
                }

                if (currentExp.Substring(currentOptPos, 2) != "<>") return "<";
                currentOptPos++;
                return "<>";
            }
            else if (currentOpt == ">")
            {
                if (currentExp.Substring(currentOptPos, 2) == ">=")
                {
                    currentOptPos++;
                    return ">=";
                }

                return ">";
            }

            #endregion
            
            var firstChar = false; // currentOpt是否和函数的首字母匹配
            var funcs = new List
            {
                "tan","cot", "atan", "sin", "cos",
                "abs", "sqrt",
                "floor", "ceiling",
                "round","pow","cube","ln","log"
            };

            foreach (var func in funcs)
            {
                var first = func.Substring(0, 1);
                if (currentExp.StartsWith(first, StringComparison.CurrentCultureIgnoreCase))
                    firstChar = true;

                if (!currentExp.StartsWith(func, StringComparison.CurrentCultureIgnoreCase)) 
                    continue;

                currentOptPos += func.Length - 1;
                return func;
            }

            return firstChar ? "error" : currentOpt;
        }
        
        /// 
        /// 转换运算符到指定的类型
        /// 
        /// 运算符
        /// 返回指定的运算符类型
        public static OptType ConvertOperator(string opt)
        {
            opt = opt.ToLower(); // svn add 307

            switch (opt)
            {
                case "~": return OptType.NS;
                case "!": return OptType.NOT;
                case "+": return OptType.ADD;
                case "-": return OptType.SUB;
                case "*": return OptType.MUL;
                case "/": return OptType.DIV;
                case "%": return OptType.MOD;
                case "<": return OptType.LT;
                case ">": return OptType.GT;
                case "<=": return OptType.LE;
                case ">=": return OptType.GE;
                case "<>": return OptType.UT;
                case "=": return OptType.ET;
                case "&": return OptType.AND;
                case "|": return OptType.OR;
                case ",": return OptType.CA;
                case "@": return OptType.END;
                case "tan": return OptType.TAN;
                case "cot": return OptType.COT;
                case "atan": return OptType.ATAN;
                case "sin": return OptType.SIN; // svn add 307
                case "cos": return OptType.COS; 
                case "abs": return OptType.Abs;
                case "sqrt": return OptType.Sqrt;
                case "floor": return OptType.Floor;
                case "ceiling": return OptType.Ceiling;
                case "round": return OptType.Round;
                case "pow": return OptType.Pow;
                case "cube": return OptType.Cube;
                case "ln": return OptType.Ln;
                case "log": return OptType.Log;

                default: return OptType.ERR;
            }
        }
        
        /// 
        /// 运算符优先级比较
        /// 
        /// 运算符类型A
        /// 运算符类型B
        /// A与B相比,-1,低;0,相等;1,高
        public static int ComparePriority(OptType optA, OptType optB)
        {
            if (optA == optB)
            {
                //A、B优先级相等
                return 0;
            }

            //乘,除,余(*,/,%)
            if ((optA >= OptType.MUL && optA <= OptType.MOD) &&
                (optB >= OptType.MUL && optB <= OptType.MOD))
            {
                return 0;
            }
            //加,减(+,-)
            if ((optA >= OptType.ADD && optA <= OptType.SUB) &&
                (optB >= OptType.ADD && optB <= OptType.SUB))
            {
                return 0;
            }
            //小于,小于或等于,大于,大于或等于(<,<=,>,>=)
            if ((optA >= OptType.LT && optA <= OptType.GE) &&
                (optB >= OptType.LT && optB <= OptType.GE))
            {
                return 0;
            }
            //等于,不等于(=,<>)
            if ((optA >= OptType.ET && optA <= OptType.UT) &&
                (optB >= OptType.ET && optB <= OptType.UT))
            {
                return 0;
            }
            //函数  
            if (optA >= OptType.TAN && optA < OptType.MUL && optB >= OptType.TAN && optB < OptType.MUL)
            {
                return 0;
            }

            if (optA < optB)
            {
                //A优先级高于B
                return 1;
            }

            //A优先级低于B
            return -1;
        }
    }

接下来就是重点:如何将字符串转换为后缀表达式:

首先定义一个解析类,然后添加一个堆栈和一个运算符字符串list(后续所有代码都在在此类之下):

public class RPN
{
        /// 
        /// 最终逆波兰式堆栈
        /// 
        public Stack Tokens { get; } = new Stack();

        /// 
        /// 允许使用的运算符
        /// 
        public readonly List m_Operators = new List(new[]
        {
            "(", "tan", ")", "atan", "cot", "sin", "cos", "abs", "sqrt", "floor", "ceiling",
            "round", "pow","cube","ln","log",
            "~", "!", "*", "/", "%", "+", "-", "<", ">", "=", "&", "|", ",", "@"
        });
} 
  

添加2个容错函数,用来检查括号是否匹配,查找操作符等:

        /// 
        /// 检查表达式中特殊符号(双引号、单引号、井号、左右括号)是否匹配      
        /// 
        /// 
        /// 
        private static bool IsMatching(string exp)
        {
            string opt = "";    //临时存储 " ' # (

            for (int i = 0; i < exp.Length; i++)
            {
                string chr = exp.Substring(i, 1);   //读取每个字符
                if ("\"'#".Contains(chr))   //当前字符是双引号、单引号、井号的一种
                {
                    if (opt.Contains(chr))  //之前已经读到过该字符
                    {
                        opt = opt.Remove(opt.IndexOf(chr), 1);  //移除之前读到的该字符,即匹配的字符
                    }
                    else
                    {
                        opt += chr;     //第一次读到该字符时,存储
                    }
                }
                else if ("()".Contains(chr))    //左右括号
                {
                    if (chr == "(")
                    {
                        opt += chr;
                    }
                    else if (chr == ")")
                    {
                        if (opt.Contains("("))
                        {
                            opt = opt.Remove(opt.IndexOf("("), 1);
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }
            return (opt == "");
        }

        /// 
        /// 从表达式中查找运算符位置
        /// 
        /// 表达式
        /// 要查找的运算符
        /// 返回运算符位置
        private int FindOperator(string exp, string findOpt)
        {
            var opt = "";
            for (var i = 0; i < exp.Length; i++)
            {
                var chr = exp.Substring(i, 1);
                if ("\"'#".Contains(chr))//忽略双引号、单引号、井号中的运算符
                {
                    if (opt.Contains(chr))
                    {
                        opt = opt.Remove(opt.IndexOf(chr), 1);
                    }
                    else
                    {
                        opt += chr;
                    }
                }

                if (opt != "") 
                    continue;

                if (findOpt != "")
                {
                    if (findOpt == chr)
                    {
                        return i;
                    }
                }
                else
                {
                    //modify svn 307 忽略大小写
                    if (m_Operators.Exists(x => x.IndexOf(chr, StringComparison.CurrentCultureIgnoreCase) > -1))
                        return i;
                }
            }
            return -1;
        }

接下来解析字符串,转换为后缀表达式。但是在正式转换前还需要做一些容错提高代码的健壮性:

其实也就是检查括号是否配对、中文符号转换为西文符号

        public bool Parse(string exp)
        {
            #region 容错

            Tokens.Clear();//清空语法单元堆栈
            if (exp.Trim() == "")//表达式不能为空
                return false;
            else if (!IsMatching(exp))//括号、引号、单引号等必须配对
                return false;

            exp = exp.Replace("(", "(");
            exp = exp.Replace(")", ")");
            exp = exp.Replace("!", "!");
            exp = exp.Replace("\\", "/");

            #endregion
        }

在容错后,再将圆周率Π用字符串替换的形式弄进去,因为担心未来有的函数名字中包含PI两个字符从而导致bug,所有我写代码要求PI必须全是大写,而其他函数不区分大小写(tan/Tan, Abs/abs)。

exp = exp.Replace("PI", $"({Math.PI})");

接下来要考虑的问题是:减号“-”和负号“-”是相同的,但是减号是二元运算符、正负号是一元运算符。如果程序中将两者等量齐观,则后面转换后缀表达式和计算都带来不必要的麻烦。所以之前定义运算符的时候,就把加减、正负这两组运算做了区分。此处我们要添加一个函数,就是把正负号+-替换为特殊符号和普通的加减号作为区分:

        /// 
        /// 将正负号替换为 # @
        /// 
        /// 
        /// 
        public string ReplaceNegative(string exp)
        {
            var len = exp.Length;
            var expT = exp.ToArray();
            var symbols = new[] {'+', '-', '*', '/', '^', '(', ',', '<', '>', '=' };

            for (var i = len - 1; i > -1; i--)
            {
                if (expT[i] == '-' && i > 1)
                    if (symbols.Contains(exp[i - 1]))
                        expT[i] = '~';

                if (expT[i] == '-' && i == 1) 
                    expT[i] = '~';


                if (expT[i] == '+' && i > 1)
                    if (symbols.Contains(exp[i - 1]))
                        expT[i] = '#';

                if (expT[i] == '+' && i == 1)
                    expT[i] = '#';
            }

            exp = string.Join("", expT).Replace("#", "");

            return exp;
        }

我这里偷个懒,直接把正号给省略掉了。

最后就是转换后缀表达式了:

            exp = ReplaceNegative(exp);

            #region 解析

            var nums = new Stack();         //操作数堆栈
            var operators = new Stack();      //运算符堆栈
            var optType = OptType.ERR;             //运算符类型
            var curNum = "";                            //当前操作数
            var curOpt = "";                            //当前运算符
            var curPos = 0;                             //当前位置
            var funcCount = 0;                          //函数数量

            curPos = FindOperator(exp, "");

            exp += "@"; //结束操作符
            while (true)
            {
                curPos = FindOperator(exp, "");

                curNum = exp.Substring(0, curPos).Trim();
                curOpt = exp.Substring(curPos, 1);

                //存储当前操作数到操作数堆栈
                if (curNum != "")
                    nums.Push(new Operand(curNum, curNum));

                //若当前运算符为结束运算符,则停止循环
                if (curOpt == "@")
                    break;

                //若当前运算符为左括号,则直接存入堆栈。
                if (curOpt == "(")
                {
                    operators.Push(new Operator(OptType.LB, "("));
                    exp = exp.Substring(curPos + 1).Trim();
                    continue;
                }

                //若当前运算符为右括号,则依次弹出运算符堆栈中的运算符并存入到操作数堆栈,直到遇到左括号为止,此时抛弃该左括号.
                if (curOpt == ")")
                {
                    while (operators.Count > 0)
                    {
                        if (operators.Peek().Type != OptType.LB)
                        {
                            nums.Push(operators.Pop());
                        }
                        else
                        {
                            operators.Pop();
                            break;
                        }
                    }
                    exp = exp.Substring(curPos + 1).Trim();
                    continue;
                }

                //调整运算符 获得完整函数名
                curOpt = Operator.AdjustOperator(curOpt, exp, ref curPos);

                optType = Operator.ConvertOperator(curOpt);

                //若运算符堆栈为空,或者若运算符堆栈栈顶为左括号,则将当前运算符直接存入运算符堆栈.
                if (operators.Count == 0 || operators.Peek().Type == OptType.LB)
                {
                    operators.Push(new Operator(optType, curOpt));
                    exp = exp.Substring(curPos + 1).Trim();
                    continue;
                }

                //若当前运算符优先级大于运算符栈顶的运算符,则将当前运算符直接存入运算符堆栈顶部.
                if (Operator.ComparePriority(optType, operators.Peek().Type) > 0)
                {
                    operators.Push(new Operator(optType, curOpt));
                }
                else
                {
                    //若当前运算符若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,
                    //直至运算符栈栈顶运算符低于(不包括等于)该运算符优先级,
                    //或运算符栈栈顶运算符为左括号
                    //并将当前运算符压入运算符堆栈。
                    while (operators.Count > 0)
                    {
                        if (Operator.ComparePriority(optType, operators.Peek().Type) <= 0 && 
                            operators.Peek().Type != OptType.LB)
                        {
                            nums.Push(operators.Pop());

                            if (operators.Count != 0) 
                                continue;

                            operators.Push(new Operator(optType, curOpt));
                            break;
                        }
                        else
                        {
                            operators.Push(new Operator(optType, curOpt));
                            break;
                        }
                    }

                }
                exp = exp.Substring(curPos + 1).Trim();
            }
            //转换完成,若运算符堆栈中尚有运算符时,
            //则依序取出运算符到操作数堆栈,直到运算符堆栈为空
            while (operators.Count > 0)
            {
                nums.Push(operators.Pop());
            }
            //调整操作数栈中对象的顺序并输出到最终栈
            while (nums.Count > 0)
            {
                Tokens.Push(nums.Pop());
            }

            return true;

            #endregion 
  

计算后缀表达式

基于前面定义的类和转换得到的堆栈,计算后缀表达式其实挺简单。下面代码中还有很多函数没有添加、或与非逻辑运算也没有写,看官可以自己试试加上去

        /// 
        /// 对逆波兰表达式求值
        /// 
        /// 
        public object Evaluate()
        {
            /*
              逆波兰表达式求值算法:
              1、循环扫描语法单元的项目。
              2、如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
              3、如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
              4、如果扫描的项目是一个一元运算符,则对栈的最顶上操作数执行该运算。
              5、将运算结果重新压入堆栈。
              6、重复步骤2-5,堆栈中即为结果值。
            */

            try
            {
                if (Tokens.Count == 0) return null;

                object value = null;
                var opds = new Stack();
                var pars = new Stack();
                Operand opdA;
                Operand opdB;
                foreach (var item in Tokens)
                {
                    if (item is Operand operand)
                    {
                        //如果为操作数则压入操作数堆栈
                        opds.Push(operand);
                    }
                    else
                    {
                        switch (((Operator)item).Type)
                        {
                            #region 其他

                            case OptType.LB:
                                continue;
                            case OptType.RB:
                                continue;
                            case OptType.PS:
                                continue;

                            #endregion

                            #region ~ Negtive 取负数

                            case OptType.NS:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        -1.0 * double.Parse(opdA.Value.ToString())));
                                }
                                else
                                {
                                    throw new Exception("Negative运算的操作数必须为数字");
                                }
                                break;

                            #endregion

                            #region 乘,*,multiplication
                            case OptType.MUL:
                                opdA = opds.Pop();
                                opdB = opds.Pop();
                                if (Operand.IsNumber(opdA.Value) && Operand.IsNumber(opdB.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER, double.Parse(opdB.Value.ToString()) * double.Parse(opdA.Value.ToString())));
                                }
                                else
                                {
                                    throw new Exception("乘运算的两个操作数必须均为数字");
                                }
                                break;
                            #endregion

                            #region 除,/,division
                            case OptType.DIV:
                                opdA = opds.Pop();
                                opdB = opds.Pop();
                                if (Operand.IsNumber(opdA.Value) && Operand.IsNumber(opdB.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER, double.Parse(opdB.Value.ToString()) / double.Parse(opdA.Value.ToString())));
                                }
                                else
                                {
                                    throw new Exception("除运算的两个操作数必须均为数字");
                                }
                                break;
                            #endregion

                            #region 余,%,modulus
                            case OptType.MOD:
                                opdA = opds.Pop();
                                opdB = opds.Pop();
                                if (Operand.IsNumber(opdA.Value) && Operand.IsNumber(opdB.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER, double.Parse(opdB.Value.ToString()) % double.Parse(opdA.Value.ToString())));
                                }
                                else
                                {
                                    throw new Exception("余运算的两个操作数必须均为数字");
                                }
                                break;
                            #endregion

                            #region 加,+,Addition

                            case OptType.ADD:
                                opdA = opds.Pop();
                                if (opds.Count > 0)
                                {
                                    opdB = opds.Pop();
                                    if (Operand.IsNumber(opdA.Value) && Operand.IsNumber(opdB.Value))
                                        opds.Push(new Operand(Type.NUMBER, double.Parse(opdB.Value.ToString()) + double.Parse(opdA.Value.ToString())));
                                    else
                                        throw new Exception("加运算的两个操作数必须均为数字");
                                    break;
                                }
                                throw new Exception("加运算的操作数必须均为数字");
                                
                            #endregion

                            #region 减,-,subtraction
                            case OptType.SUB:
                                opdA = opds.Pop();
                                if (opds.Count > 0)
                                {
                                    opdB = opds.Pop();
                                    if (Operand.IsNumber(opdA.Value) && Operand.IsNumber(opdB.Value))
                                        opds.Push(new Operand(Type.NUMBER,
                                            double.Parse(opdB.Value.ToString()) - double.Parse(opdA.Value.ToString())));
                                    else
                                        throw new Exception("减运算的两个操作数必须均为数字");
                                    break;
                                }
                                throw new Exception("减运算的操作数必须均为数字");

                            #endregion

                            #region 逻辑运算符 true 1 false -1

                            case OptType.NOT: // 非
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                    opds.Push(
                                        new Operand(Type.NUMBER,
                                            double.Parse($"{opdA.Value}") > 0 ? -1 : 1)
                                    );
                                else
                                    throw new Exception("加运算的两个操作数必须均为数字");
                                break;

                            case OptType.LT:
                                break;
                            case OptType.LE:
                                break;
                            case OptType.GT:
                                break;
                            case OptType.GE:
                                break;
                            case OptType.ET:
                                break;
                            case OptType.UT:
                                break;
                            case OptType.AND:
                                break;
                            case OptType.OR:
                                break;
                            case OptType.CA:
                                break;

                            #endregion

                            #region 正切,tan,subtraction

                            case OptType.TAN:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER, Math.Tan(double.Parse(opdA.Value.ToString()) * Math.PI / 180)));
                                }
                                else
                                {
                                    throw new Exception("正切运算的1个操作数必须均为角度数字");
                                }
                                break;

                            #endregion

                            #region 余切,cot

                            case OptType.COT:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        1.0 / Math.Tan(double.Parse(opdA.Value.ToString()) * Math.PI / 180)));
                                }
                                else
                                {
                                    throw new Exception("余切运算的1个操作数必须均为角度数字");
                                }
                                break;

                            #endregion

                            #region 反正切,atan,subtraction
                            case OptType.ATAN:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER, Math.Atan(double.Parse(opdA.Value.ToString()))));
                                }
                                else
                                {
                                    throw new Exception("反正切运算的1个操作数必须均为数字");
                                }
                                break;
                            #endregion

                            #region 正弦,sin 角度

                            case OptType.SIN:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Sin(double.Parse(opdA.Value.ToString()) * Math.PI / 180)));
                                }
                                else
                                {
                                    throw new Exception("正弦运算的1个操作数必须均为角度数字");
                                }
                                break;

                            #endregion

                            #region 余弦,cos 角度

                            case OptType.COS:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Cos(double.Parse(opdA.Value.ToString()) * Math.PI / 180)));
                                }
                                else
                                {
                                    throw new Exception("正弦运算的1个操作数必须均为角度数字");
                                }
                                break;

                            #endregion

                            #region Abs

                            case OptType.Abs:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Abs(double.Parse(opdA.Value.ToString()))));
                                }
                                else
                                {
                                    throw new Exception("abs运算的1个操作数必须为数字");
                                }
                                break;

                            #endregion

                            #region Floor

                            case OptType.Floor:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Floor(double.Parse(opdA.Value.ToString()))));
                                }
                                else
                                {
                                    throw new Exception("Floor运算的1个操作数必须为数字");
                                }
                                break;

                            #endregion

                            #region Ceiling

                            case OptType.Ceiling:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Ceiling(double.Parse(opdA.Value.ToString()))));
                                }
                                else
                                {
                                    throw new Exception("Ceiling运算的1个操作数必须为数字");
                                }
                                break;

                            #endregion

                            #region 开平方 Sqrt
                                
                            case OptType.Sqrt:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Sqrt(double.Parse(opdA.Value.ToString()))));
                                }
                                else
                                {
                                    throw new Exception("sqrt运算的1个操作数必须为非负数");
                                }
                                break;

                            #endregion

                            #region 四舍五入

                            case OptType.Round:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Round(double.Parse(opdA.Value.ToString()), 2)));
                                }
                                else
                                {
                                    throw new Exception("四舍五入运算的1个操作数必须是数字");
                                }
                                break;

                            #endregion

                            #region 幂

                            case OptType.Pow:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Pow(double.Parse(opdA.Value.ToString()), 2)));
                                }
                                else
                                {
                                    throw new Exception("幂运算的1个操作数必须是数字");
                                }
                                break;

                            case OptType.Cube:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Pow(double.Parse(opdA.Value.ToString()), 3)));
                                }
                                else
                                {
                                    throw new Exception("幂运算的1个操作数必须是数字");
                                }
                                break;

                            #endregion

                            #region 对数

                            case OptType.Ln:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Log(double.Parse(opdA.Value.ToString()))));
                                }
                                else
                                    throw new Exception("对数运算的1个操作数必须是数字");
                                break;

                            case OptType.Log:
                                opdA = opds.Pop();
                                if (Operand.IsNumber(opdA.Value))
                                {
                                    opds.Push(new Operand(Type.NUMBER,
                                        Math.Log10(double.Parse(opdA.Value.ToString()))));
                                }
                                else
                                    throw new Exception("对数运算的1个操作数必须是数字");
                                break;

                                #endregion

                            case OptType.END:
                                break;
                            case OptType.ERR:
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                }

                if (opds.Count == 1)
                    value = opds.Pop().Value;

                return value;
            }
            catch
            {
                return null;
            }
            
        } 
  

 

 

你可能感兴趣的:(C#)