在公司实习,老大给留了个练习:

                  要求实现一个简易计算器(不需要界面)

                  能进行四则运算和括号运算(四则运算倒是好搞,但是加括号有点难度)

 

于是就在网上参考了几篇(下面代码是参考一位大佬的,但是忘了那篇博客的链接,如果大佬认出来了请联系我下)

几乎都是用栈实现的中缀表达式转换成后缀表达式

但是在上面大佬的代码中,老大给我指出了几个问题:

1.不能进行负号运算

2.大数据运算结果为科学计数法

3.小数运算有问题

所以在之前的基础上,我又做了一些处理,下面上代码:

import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.Stack;

/**
 *
 * @版权 : Copyright (c) 2017-2018 *********公司技术开发部
 * @author: gaozhenchao
 * @E-mail: 1226046769@qq.com
 * @版本: 1.0
 * @创建日期: 2019年1月14日 上午9:19:49
 * @ClassName CalculateUtil
 * @类描述-Description: TODO(这里用一句话描述这个方法的作用)
 * @修改记录:
 * @版本: 1.0
 */
public class SimpleCalcularor {
	// 操作数栈
	private static Stack Operands;
	// 操作符栈
	private static Stack Operators;
	// 操作符集合
	private static final Set C_OperatorSet = new HashSet() {
		/** 
		* 
		*/
		private static final long serialVersionUID = 1L;
		{
			add('+');
			add('-');
			add('*');
			add('/');
			add('(');
			add(')');
		}
	};

	// 获取优先级
	private static int getOperatorPriority(char ch) {
		if (ch == '+' || ch == '-')
			return 0;
		else if (ch == '*' || ch == '/')
			return 1;
		else
			return -1;
	}

	// 中缀转后缀
	private static String infixToSuffix(String expression) throws Exception {
		Operators = new Stack<>();
		Operators.clear();
		StringBuilder sBuilder = new StringBuilder();
		for (int i = 0; i < expression.length(); i++) {
			char ch = expression.charAt(i);
			if (ch == ' ')
				continue;
			if (C_OperatorSet.contains(ch)) {
				// 如果操作符栈为空
				if (Operators.empty()) {
					if (ch == ')') { // 只要不是 ) 就入栈
						throw new Exception("表达式错误");
						// System.out.println("括号不匹配");
						// return sBuilder.toString();
					}
					Operators.push(ch);
				} else if (ch == '(') {
					Operators.push(ch);
				} else if (ch == ')') { // 如果是 ) 括号
					char top;
					while ((top = (char) Operators.peek()) != '(') { // 如果当前操作符栈的第一个不是(

						if (Operators.empty()) {
							// System.out.println("括号不匹配");
							// return sBuilder.toString();
							throw new Exception("表达式错误");
						}
						// 把运算符加入sBuilder
						sBuilder.append(top);
						Operators.pop();
					}
					Operators.pop();
				} else { // 如果是运算符
					char top = (char) Operators.peek();
					if (getOperatorPriority(ch) <= getOperatorPriority(top)) { // 如果优先级小于操作符栈中第一个
						while (!Operators.empty()
								&& getOperatorPriority(ch) <= getOperatorPriority(top = (char) Operators.peek())) {
							// 把运算符加入sBuilder
							sBuilder.append(top);
							Operators.pop();
						}
					}
					Operators.push(ch);
				}
			} else { // 如果是数字 加入sBuilder(把[3]的括号去除)
				sBuilder.append("[" + ch);
				while (i + 1 < expression.length()
						&& (((ch = expression.charAt(i + 1)) == '.') || (ch >= '0' && ch <= '9'))) {
					sBuilder.append(ch);
					++i;
				}
				sBuilder.append(']');
			}
		}
		while (!Operators.empty()) {
			if ((char) Operators.peek() == '(') {
				throw new Exception("表达式错误");
				// System.out.println("括号不匹配");
				// return "";
			}
			sBuilder.append(Operators.peek());
			Operators.pop();
		}
		return sBuilder.toString();
	}

	// 处理负号
	public static String HandleMinus(String expression) {
		for (int i = 0; i < expression.length(); i++) {
			char ch = expression.charAt(i);
			// 存储负号个数
			int count = 0;
			// 遍历表达式,如果遍历到负号
			if (ch == '-') {
				// 存储变量加一
				count++;
				// 如果负号在第一位
				if (i == 0 && expression.charAt(i + 1) >= '0' && expression.charAt(i + 1) <= '9') {
					// 直接在前面加0
					expression = "0" + expression;
					i = 0;
					continue;
					// 如果负号前面是括号(说明负号在表达式中) 采用的办法是把0插入到负号之前 5*(-3) -> 5*(0-3)
				} else if (expression.charAt(i - 1) == '(' && expression.charAt(i + 1) >= '0'
						&& expression.charAt(i + 1) <= '9') {
					// 新建一个表达式长度加负号个数长度的数组(扫描一个负号添加一个0)
					char[] c = new char[expression.length() + count];
					// 给新的字节数组赋值
					for (int n = 0; n < expression.length(); n++) {
						c[n] = expression.charAt(n);

					}
					// 把以负号索引开始的数据右移
					for (int j = c.length - 2; j > i - 1; j--) {
						c[j + 1] = c[j];
					}
					// 赋值到i位置
					c[i] = '0';
					expression = String.valueOf(c);
					// 继续向下扫描
					i++;
					continue;
				}

			}
		}
		return expression;

	}

	public static BigDecimal evalExp(String expression) throws Exception {
		Operands = new Stack<>();
		Operands.clear();
		// double用二进制计算,因为小数点转换成二进制运算有精度损失,而且大数据运算会转换成科学计数法,所以采用decimal
		BigDecimal ret = null;
		String suffix = infixToSuffix(expression);
		System.out.println("后缀表达式为: " + suffix);
		for (int i = 0; i < suffix.length(); i++) {
			if (suffix.charAt(i) == '[') {
				i++;
				int beginIndex = i, endIndex = i;
				while (']' != suffix.charAt(i)) {
					i++;
					endIndex++;
				}
				// 取两个括号中间的数字
				Operands.push(BigDecimal.valueOf(Double.valueOf(suffix.substring(beginIndex, endIndex))));
			} else {
				BigDecimal left, right;
				BigDecimal res = null;

				right = (BigDecimal) Operands.peek();
				Operands.pop();

				left = (BigDecimal) Operands.peek();
				Operands.pop();
				switch (suffix.charAt(i)) {
				case '+':
					res = left.add(right);

					break;
				case '-':
					res = left.subtract(right);
					break;
				case '*':
					res = left.multiply(right);
					break;
				case '/':
					res = left.divide(right);
					break;
				}
				Operands.push(res);
			}
		}
		if (Operands.size() > 1) {

			throw new Exception("表达式错误");

		}

		ret = (BigDecimal) Operands.peek();

		Operands.pop();
		return ret;
	}

	// (3+4)*5 10000000*1000000 0.65656544*3
	public static void main(String[] args) throws Exception {

		Scanner input = new Scanner(System.in);
		System.out.println("请输入一个含+-*/()的表达式,请确认输入合法");
		// 5*(-3) -> 5*(0-3)
		String expression = input.nextLine();
		// 处理负号
		expression = HandleMinus(expression);
		// 去除BigDecimal后面的无用0
		System.out.println(expression + " =" + evalExp(expression).stripTrailingZeros().toPlainString());
		input.close();
	}
}

首先说一下小数运算问题:在之前的代码中,转换后缀表达式后,接下来要进行计算,但是两个操作数的数据类型为double。

但是如果小数超出double范围,就会出现精度丢失的情况

java 公式解析计算框架 java公式计算器_i++

然后是大数据计算的问题:数值较大的两个数计算时,产生的结果是用科学计数法显示的,但是日常中我们想看到的是正常的显示结果。产生这个的原因是:浮点数(float,double)整数部分达到8位及以上,会以科学计数法显示。

解决的办法有两种:

// 方法一:NumberFormat
    private static String big(double d) {
        NumberFormat nf = NumberFormat.getInstance();
        // 是否以逗号隔开, 默认true以逗号隔开,如[123,456,789.128]
        nf.setGroupingUsed(false);
        // 结果未做任何处理
        return nf.format(d);
    }

//方法二: BigDecimal
    private static String big2(double d) {
        BigDecimal d1 = new BigDecimal(Double.toString(d));
        BigDecimal d2 = new BigDecimal(Integer.toString(1));
        // 四舍五入,保留2位小数
        return d1.divide(d2,2,BigDecimal.ROUND_HALF_UP).toString();
    }

因为后面还要对结果进行处理,所以这里采用第二种。

BigDecimal的小数计算结果后面会带0,所以用以下方式再次进行处理

(BigDecimal的结果).stripTrailingZeros().toPlainString()

最后是处理负号的问题:基本步骤都写在代码注释里面了,可以作为参考

以上就是对表达式计算器的改进,如果有不对的地方,欢迎大佬前来指正。(由于某种原因并没有对大量的数据进行测试)

 

 

更新:由于老大让我优化代码,但是前面的逻辑动不了(其实是我想不出来),所以把目标放在了处理负号的函数上。

具体处理如下:

1.首先注释掉原处理函数

java 公式解析计算框架 java公式计算器_System_02

java 公式解析计算框架 java公式计算器_i++_03

2.作为代替,现采用正则表达式处理(只需要一行就可以代替上面几十行的函数)

java 公式解析计算框架 java公式计算器_操作符_04

具体解释如下:

(?=exp)

匹配exp前面的位置

(?<!exp)

匹配前面不是exp的位置

    连起来就是:匹配前面不是0-9 ) } ] 的位置   

                           匹配  - 0-9 ( { [  前面的位置

上代码:

public static void main(String[] args) throws Exception {

		Scanner input = new Scanner(System.in);
		System.out.println("请输入一个含+-*/()的表达式,请确认输入合法");
		// 5*(-3) -> 5*(0-3)
		String expression = input.nextLine();
		// 处理负号
		// expression = HandleMinus(expression);
		// 匹配前面不是0-9 ) } ] 的位置 &&&&&匹配- 0-9 ( { [ 前面的位置
		// 注意-开头的地方前面一定不能是数字或者反括号,如9-0,(3-4)-5,这里地方是不能加0的
		// 它的后面可以是数字或者正括号,如-9=>0-9, -(3*3)=>0-(3*3)
		expression = expression.replaceAll("(?<![0-9)}])(?=-[0-9({])", "0");
		// 去除BigDecimal后面的无用0
		System.out.println(expression + " =" + evalExp(expression).stripTrailingZeros().toPlainString());
		input.close();
	}

就是这样,省去了很多步骤