unit utFormula; interface uses SysUtils, Generics.Collections; type //表达式计算器 TFormula = class private const OPE = ['+', '-', '*', '/', '(', ')']; //允许出现的运算符 OPEFORCALC = ['+', '-', '*', '/']; //参与最终计算的运算符 private ConstValue : TDictionary; //待替换的常量表 private // 判断是否是操作数 function IsOperand(ch: Char): boolean; // 返回运算符的优先级 function Priority(ch: Char): integer; // 对两个值利用运算符计算结果 function GetValue(op: Char; const val1, val2: Double): Double; // 判断是否数字 function isNumber(const s: string): boolean; // 根据原生公式串生成公式分项列表(后缀表达式), 公式操作数列表 procedure splitExp(FExp : TList; const sValue: string); //依据后缀表达式计算结果 function CalcItem(FExp : TList): Double; //将字符串转为有效双精度值 function CustomStrToDouble(const Key: string): Double; public constructor Create; destructor Destroy; override; //计算表达式 function Calc(const AFormulaStr : string) : Double; end; implementation { TFormula } function TFormula.CustomStrToDouble(const Key : string): Double; begin Result := 0; if isNumber(Key) then //如果是数值, 直接返回 Result := StrToFloat(Key) else if not ConstValue.TryGetValue(Key, Result) then //如果是常量则返回, 否则报出异常 raise Exception.Create('无法识别的数值!'); end; function TFormula.CalcItem(FExp : TList): Double; var i: integer; val: string; stack: TStack; //堆栈; d1, d2: Double; begin Result := 0; stack := TStack.create; //创建栈对象 try if FExp.Count = 1 then //只有一个数据项时, 直接转换结果 begin Result := CustomStrToDouble(FExp.Items[0]); end else begin for i := 0 to FExp.Count - 1 do //逐项计算 begin val := FExp.Items[i]; if (Length(val) = 1) and (val[1] in OPEFORCALC) then //是运算符则计算, 反之压栈 begin d2 := stack.Pop; d1 := stack.Pop; Result := GetValue(val[1], d1, d2); //根据运算符和数据项计算结果 stack.push(Result); //计算结果压栈 end else stack.push(CustomStrToDouble(val)); end; end; finally stack.Free; end; end; function TFormula.Calc(const AFormulaStr: string): Double; var FExp : TList; // 公式分项列表, 后缀表达式 用于表达式计算 begin result := 0; if sametext(AFormulaStr, '') then raise Exception.Create('无效的表达式'); FExp := TList.Create; try try splitExp(FExp , AFormulaStr); //产生后缀表达式 result := CalcItem(FExp); //计算后缀表达式 except on E: Exception do raise Exception.Create(e.Message); end; finally FExp.Free; end; end; constructor TFormula.Create; begin //创建常量表, 并初始化 ConstValue := TDictionary.Create; ConstValue.Add('∏', 3.1415926); end; destructor TFormula.Destroy; begin ConstValue.Free; //销毁常量表 inherited; end; function TFormula.GetValue(op: Char; const val1, val2: Double): Double; begin case op of '+': Result := val1 + val2; '-': Result := val1 - val2; '*': Result := val1 * val2; '/': begin if val2 = 0 then Result := 0 else Result := val1 / val2; end; end; end; function TFormula.isNumber(const s: string): boolean; var i: Extended; Code: integer; begin val(s, i, Code); Result := Code = 0; end; function TFormula.IsOperand(ch: Char): boolean; begin Result := not(ch in OPE); end; function TFormula.Priority(ch: Char): integer; begin case ch of '+': Result := 1; '-': Result := 1; '*': Result := 2; '/': Result := 2; else Result := 0; end; end; procedure TFormula.splitExp(FExp : TList; const sValue: string); function AddToExp(const Value : string): string; begin Result := ''; if Value = '' then Exit; FExp.Add(Value); end; var ch, ch1: Char; stack: TStack; // 堆栈; i: integer; sOperand: string; begin // 利用集合模拟堆栈操作,产生后缀表达式 stack := TStack.create; try sOperand := ''; for i := 1 to Length(sValue) do begin if sValue[i] = chr(32) then Continue; // 去除多余的空字符 ch := sValue[i]; if IsOperand(ch) then // 如果是操作数,直接放入B中 begin sOperand := sOperand + ch; end else begin sOperand := AddToExp(sOperand); if ch = '(' then // 如果是'(',将它放入堆栈中 begin stack.Push(ch); end else if ch = ')' then // 如果是')' begin while stack.Count > 0 do // 不停地弹出堆栈中的内容,直到遇到'(' begin ch := stack.Pop; if ch = '(' then break else begin FExp.Add(ch); end; end; end else // 既不是'(',也不是')',是其它操作符,比如+, -, *, /之类的 begin if stack.Count > 0 then begin while stack.Count > 0 do begin ch1 := stack.Pop; // 弹出栈顶元素 if Priority(ch) > Priority(ch1) then // 如果栈顶元素的优先级小于读取到的操作符 begin stack.push(ch1); // 将栈顶元素放回堆栈 stack.push(ch); // 将读取到的操作符放回堆栈 break; end else // 如果栈顶元素的优先级比较高或者两者相等时 begin FExp.Add(ch1); if stack.Count = 0 then begin stack.push(ch); // 将读取到的操作符压入堆栈中 break; end; end; end; end else stack.push(ch); // 如果堆栈为空,就把操作符放入堆栈中 end; end; end; AddToExp(sOperand); while stack.Count > 0 do AddToExp(stack.Pop); finally stack.Free; end; end; end.