注: 除中缀表达式和后缀表达式算法外, 其余计算器的功能算法本人自己实现, 可能并不规范和标准, 并且可能存在一些BUG, 仅供参考.
平常我们数学中所计算的表达式 :
1.5+2*3+(5-6/2)= ?
如果在计算机中,应该如何计算该表达式?
涉及问题
- 根据运算符优先级,确定运算顺序。应先算括号里面的,后算括号外的,先算次方开方,后算乘除,最后算加减。
- 处理表达式错误问题,如出现其他字符,或者括号不匹配
- 功能完备,加入计算小数,负数,超大数的功能。
运算顺序
中缀表达式和后缀表达式
平常我们计算的表达式也称中缀表达式,20世纪50年代,波兰逻辑学家Jan也遇到了运算优先级的问题,后来他发明了后缀表达式,解决了该问题。
中缀表达式 :1.5+2*3+(5-6/2)
转为后缀表达式 : 1.5 2 3 * + 5 6 2 / - +
计算后缀表达式
计算需要用到数据结构里的栈
依次将后缀表达式的每个数字入栈
遇到运算符,从栈里出栈两个元素进行运算,随后再入栈
按照此规则,继续进行下去
最后得出答案 9.5
1.5+2*3+(5-6/2) = 9.5 可以口算一下
相比起中缀表达式, 这样一步一步对后缀表达式的字符进行处理, 很容易用计算机的编程语言来实现
如何转换后缀表达式
同样要用到栈结构.
转换运算规则如下:
- 遇到数字的话直接输出。
- 创建一个符号栈,遇到符号的话进栈.。同时需要设置符号优先级。因为先算加减,后算乘除,所以先假设 + - 优先级为 1, * / 优先级为为 2。 则在符号进栈时, 如果栈里有优先级大于或等于它( >= )的符号,则这些符号全部按顺序出栈,之后该符号再进栈。
- 遇到括号,左括号的话不做判别直接入栈,右括号不入栈,且要一直输出栈内的符号,直到遇到左括号为止。
例
转换1.5+2*3+(5-6/2)为后缀表达式
遇到数字输出, 遇到符号进栈。
1.5输出 + 进栈 2输出
之后 * 进栈, 因为+的优先级不大于也不等于( >= ) * ,所以 + 不出栈 * 直接进栈。
之后 3 输出
之后是 + 号, 因为 栈里的 * 和 + 的优先级都大于或等于它( >= ), 所以* 出栈 + 出栈, 之后该 + 号再进栈
按此规则继续下去,遇到左括号( 直接入栈。
注: 这里入栈 - 号时虽然栈里有+号,但+号在括号的后面,所以不做判断,不用出栈。
遇到右括号,则一直输出栈里内容直到左括号。
最后再输出+号 ,即可得到后缀表达式
1.5 2 3 * + 5 6 2 / - +
中缀转后缀表达式挨个检索每一个字符,之后进行处理,这种过程也适合用编程语言实现。
###中缀转后缀时处理小数,超大数和负数
处理小数 超大数
处理数字时需要循环处理
例如读入 175.2
首先创建一个空串 StringBuilder 循环判断 逐个字符读入 如果读入的是数字或小数点,则从末尾拼接到字符串里, 直到不为数字或小数点为止,最后如果字符串不为空, 则 调用 new BigDecimal(String str) 构造器把其构造成超大数.在加入栈里
String item = 175.2;
StringBuilder SB = new StringBuilder("");
while (isNumber(c) || c == '.')
{
SB.append(c);
if (++i >= item.length()) break;
c = item.charAt(i);
}
if (!SB.toString().equals(""))
{ // StringBuilder没有重写equlas 需要调用String的eqluals
stack.push(new BigDecimal(SB.toString()));
// bigDecimal 超大浮点数
}
处理负数
判断负号如果上一个字符读取的是运算符号,则给下一个读取到的数取反
例如 3*-2, 定义一个布尔类型的开关值key 每次循环默认 false, 如果取到了一个符号,则 key = true, 如果key= true 且取到了 - 号 则在处理数字的字符串 StringBuilder上先拼接一个负号,之后继续取值
StringBuilder SB = new StringBuilder("");
if (c == '-' && key)
{
++i;
if(i < item.length())
c = item.charAt(i);
SB.append('-');
}
while (isNumber(c) || c == '.')
{
......
}
表达式错误
括号不匹配
括号匹配同样需要用到栈
例如表达式 1+2*(((1+3)+2)+4)
挨个字符遍历表达式,
遇到左括号 ‘’(’’ 则入栈, 遇到右括号则从栈里出一个括号, 如果栈为空则不匹配, 一直进行下去. 到最后所有括号都遍历过且栈为空则说明括号匹配,否则括号不匹配.
出现错误字符
开始时遍历一遍字符串, 遇到不符合算数的字符删除即可
自己的实现
加入了次方运算^ 调用的是Math.pow (double, double)
超大数不能处理无线循环小数, 除法运算时需特殊处理,可以转成double类型运算.
代码
package JavaTest.caculater;
import java.math.BigDecimal;
import java.util.Scanner;
import java.util.Stack;
public class Caculater {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String item = scanner.nextLine();
Stack stack = new Stack();
Stack stack1 = new Stack();
boolean key = false;
if(!pi(item))
{
System.out.println("括号不匹配");
return;
}
for(int i = 0; i < item.length(); i++)
{
char c = item.charAt(i);
while (!isfu(c) && !isNumber(c))
{
++i;
if(i < item.length())
c = item.charAt(i);
if( i == item.length())
break;
}
if( i == item.length())
break;
StringBuilder SB = new StringBuilder("");
if (c == '-' && key)
{
++i;
if(i < item.length())
c = item.charAt(i);
SB.append('-');
}
while (isNumber(c) || c == '.')
{
key = false;
SB.append(c);
if (++i >= item.length()) break;
c = item.charAt(i);
}
if (!SB.toString().equals("")){
stack.push(new BigDecimal(SB.toString()));
}
if (c == '^')
{
if((!stack1.empty()))
if( ((char)stack1.peek() == '^'))
{
stack.push(stack1.pop());
}
}
if (c == '*' || c == '/' || c == '+' || c == '-')
{
if((!stack1.empty()))
if( ((char)stack1.peek() == '*') || ((char)stack1.peek() == '/') || ((char)stack1.peek() == '^'))
{
stack.push(stack1.pop());
}
}
if( c == '+' || c == '-')
{
if((!stack1.empty()))
if( ((char)stack1.peek() == '*') || ((char)stack1.peek() == '/') || ((char)stack1.peek() == '+') || ((char)stack1.peek() == '-') || ((char)stack1.peek() == '^'))
{
stack.push(stack1.pop());
}
}
if (isfu(c))
{
stack1.push(c);
key = true;
}
if(c == ')')
{
char cha = c;
while (cha != '(') {
cha = (char) stack1.pop();
if(cha != '(' && cha != ')')
stack.push(cha);
}
}
}
while (!stack1.empty())
{
stack.push(stack1.pop());
}
Stack stack2 = new Stack<>();
while (!stack.empty())
{
stack2.push(stack.pop());
}
while (!stack2.empty()) {
Object c = stack2.pop();
// System.out.println(c.getClass() + " " + c);
if (c.getClass() == BigDecimal.class) {
stack1.push((BigDecimal) c);
// System.out.println(1);
continue;
}
else
{
// System.out.println(2);
if ((char) c == '*' && (stack1.size() != 1))
stack1.push( ( (BigDecimal)stack1.pop() ).multiply( (BigDecimal) stack1.pop() ) );
if ((char) c == '^' && (stack1.size() != 1)) {
BigDecimal a = (BigDecimal) stack1.pop();
if(a.intValue() >= 1)
stack1.push(((BigDecimal) stack1.pop()).pow(a.intValue()));
else {
stack1.push(new BigDecimal(Math.pow(((BigDecimal) (stack1.pop())).doubleValue(), a.doubleValue())));
}
}
if ((char) c == '/' && (stack1.size() != 1)) {
Double a = ((BigDecimal) stack1.pop()).doubleValue();
stack1.push (new BigDecimal( ( (Double) ( ( (BigDecimal)stack1.pop() ).doubleValue() / a)).toString()));
}
if ((char) c == '+' && (stack1.size() != 1))
stack1.push(( (BigDecimal)stack1.pop() ).add( (BigDecimal) stack1.pop() ));
if ((char) c == '-')
{
BigDecimal a = (BigDecimal) stack1.pop();
if(!stack1.empty() && stack1.peek().getClass() != char.class )
{
stack1.push(((BigDecimal) stack1.pop()).subtract(a));
}
else
stack1.push((a).multiply(new BigDecimal(-1)));
}
}
}
System.out.printf("%.6s",stack1.pop());
}
public static boolean isNumber(char c)
{
if (c >= '0' && c <= '9')
return true;
else
return false;
}
public static boolean isfu(char c)
{
if(c == '+' || c == '-' || c == '*'|| c == '/' || c == '(' || c == '^')
return true;
else return false;
}
public static boolean pi(String string)
{
Stack<Character> stack = new Stack<Character>();
for(int i = 0; i < string.length(); i++)
{
if(string.charAt(i) == '(')
stack.push('(');
if(string.charAt(i) == ')')
if(stack.empty() || stack.pop() != '(')
return false;
}
if(!stack.empty())
return false;
return true;
}
}
测试运行
超大数
负数次方
开方
小数负数
括号匹配
错误字符删除处理
BUG肯定还存在, 后续还需继续改善。