除零异常、左右括号不匹配和定义域异常
基本思路
接下来就判断除零异常。除零异常非常简单,只要在运算前判断运算符是不是除号,如果是除号,除数是不是0就行了。
左右括号不匹配如果要严谨一些可能会比较麻烦,比较简单的方法是,如果左右括号不匹配,那么极有可能某个括号进栈后会被当做运算符出栈并参与运算。那么只要在运算前判断一下运算符是不是括号就行了,如果运算符是括号,说明一定出现了括号匹配问题。
定义域异常判断也很简单,在进行运算前,判断一下运算符,如果是次方或者幂运算,在根据情况判断一下参与运算的数字是不是在合适的范围内,如果不是则抛出异常。
这三个异常判断的位置是一样的,因此可以写在同一个方法里。定义域异常暂时不做处理。
编码
在原来的代码中添加一个方法:
/**
* @description 判断即将要进行的运算是否正确
* <br>
* 检查是否是除数为0的除法运算或定义域不正确的次方运算
* @param c 运算符
* @param u 运算符左侧的数字(若运算符是次方或根号,则是进行次方或开方运算的数字)
* @param d 运算符右侧的数字
* @return
*/
public boolean operateIsRight(char c,float u,float d)
{
if (c == '/' && d == 0)
{
exception = FormulaException.getDivideByZeroException(); //除零异常
return false;
}
else if (c == '(' || c == ')') //括号不匹配异常
{
exception = FormulaException.getBracketMismatchException();
return false;
}
return true;
}
然后将这个方法写入原来的代码中进行判断,在原来的代码中进行修改:
/**
* @description 运算操作
* <br>
* 根据当前符号的意义判断是否进行运算,如果是则进行运算
* <br>
* 如果当前符号暂时不能参与运算,根据当前符号做相应的处理
* <br>
* @param currentCharacter 当前读取的符号
*/
private boolean operate(char currentCharacter)
{
//尝试将当前符号拿来参与运算
while((getPriority(currentCharacter) <= getPriority(operator.peek())) && operator.peek() != '(' && operator.peek() != '#')
{
float d = figure.pop();
float u = figure.pop();
if (!operateIsRight(operator.peek(),u,d)) //判断将要进行的运算是否会出现异常
return false; //出现异常
figure.push(operate(operator.pop(),u,d));
}
if (getPriority(currentCharacter) < getPriority(operator.peek()) && getPriority(currentCharacter) + getPriority(operator.peek()) == 0)
{
operator.pop(); //去括号
}
else
operator.push(currentCharacter); //当前符号优先级太低没参与运算,且不是右括号,进栈符号栈
return true;
}
然后再将判断写入上一级的方法中(调用operate的方法)
/**
* @description 当前读取到的字符是运算符时进行的操作
* <br>
*
* @param currentCharacter 当前读取到的字符
*/
private boolean doWhenCharIsOperator(char currentCharacter,char proCharacter)
{
//非(当前符号是加减号且正负号标记激活状态),即当前符号不是正负号
if (!(getPriority(currentCharacter) == 1 && NPCanUse == true))
{
if (flag == true && currentCharacter != '(') //运算符后不能紧跟除左括号外的运算符
{
exception = FormulaException.getCharacterException();
return false;
}
pushFigure(currentCharacter,proCharacter); //尝试将数字字符串进栈数字栈(有些特殊情况不能进栈)
if (!operate(currentCharacter)) //尝试运算(有些情况当前字符不会参与运算)
return false;
if (currentCharacter != ')') //除右括号外其他运算符右边均不能接乘除号
flag = true;
}
//当前符号是正负号,并且为符号时,正负号标记*-1
else
NP *= currentCharacter == '-' ? -1: 1;
//当读取到加减号或左括号,下一个加减号视为正负号,NP激活
if (getPriority(currentCharacter) == 1 || getPriority(currentCharacter) == 3)
NPCanUse = true;
return true;
}
这样就写好啦,然后测试一下
测试
除零异常正常,但是左右括号匹配逻辑存在问题,暂时保留问题,留到下篇解决。这很正常,毕竟程序员的程序哪有一帆风顺的(手动滑稽)
数字异常
我们在程序逻辑中,将所有非运算符(运算符包括了左右括号)的符号视为数字,暂时放在数字字符串中,当需要将数字进栈时,先尝试将数字字符串转化为数字然后再进栈。如果算式中存在无法识别的符号或者是数字的格式有问题,比如说有多个小数点等,最终会导致数字字符串转化为数字失败。
数字异常就是值这一类异常。
基本思路
由于这类问题最终会导致数字字符串转化数字失败,所以只要在数字字符串转化数字的那一步中加一个异常捕获就可以了,如果捕获到异常说明转化失败。
编码
在原来的代码中修改:
/**
* @description 数字栈进栈
* <br>
* 根据当前符号判断是否将数字符号串内的内容转为数字并进栈数字栈
* <br>
* 并重置NP标记,并将其改为休眠状态
* <br>
* @param currentCharacter 当前读取的符号
* @param proCharacter 当前读取的符号的上一个符号
*/
private boolean pushFigure(char currentCharacter,char proCharacter)
{
//若当前符号不是左括号且前一个符号不是右括号,数字栈进栈并将数字字符串清空。否则当前数字字符串内应该是没有内容的
//若当前符号是左括号,说明之前的不是数字,那在之前数字字符串就被清空过;前一个符号是右括号时同理
if (currentCharacter != '(' && proCharacter != ')')
{
try {
figure.push(Float.valueOf(figureString.toString()) * NP); //将数字字符串的内容转为数字并进栈数字栈
}catch (Exception e){
exception = FormulaException.getFigureException(); //数字异常
return false;
}
figureString.setLength(0); //清空数字字符串
NP = 1; //重置NP及其激活状态
NPCanUse = false; //NP休眠
}
return true;
}
/**
* @description 当前读取到的字符是运算符时进行的操作
* <br>
*
* @param currentCharacter 当前读取到的字符
*/
private boolean doWhenCharIsOperator(char currentCharacter,char proCharacter)
{
//非(当前符号是加减号且正负号标记激活状态),即当前符号不是正负号
if (!(getPriority(currentCharacter) == 1 && NPCanUse == true))
{
if (flag == true && currentCharacter != '(') //运算符后不能紧跟除左括号外的运算符
{
exception = FormulaException.getCharacterException();
return false;
}
//pushFigure尝试将数字字符串进栈数字栈(有些特殊情况不能进栈)
//operate尝试运算(有些情况当前字符不会参与运算)
if (!pushFigure(currentCharacter,proCharacter) || !operate(currentCharacter))
return false;
if (currentCharacter != ')') //除右括号外其他运算符右边均不能接乘除号
flag = true;
}
//当前符号是正负号,并且为符号时,正负号标记*-1
else
NP *= currentCharacter == '-' ? -1: 1;
//当读取到加减号或左括号,下一个加减号视为正负号,NP激活
if (getPriority(currentCharacter) == 1 || getPriority(currentCharacter) == 3)
NPCanUse = true;
return true;
}
代码就修改完成了,非常轻松。随便写几个奇怪的符号进去测试一下即可,没有发现bug。
不明原因的异常
所有上述方法没有捕获到异常,却最终出现异常的异常都算是不明原因的异常。比如主方法中,倒数第五行,字符串读完了,我们期望符号栈栈顶此时是结束符号,但是并不是。这个问题就算是不明原因的:
/**
* @description 计算运算式(主程序)
* <br>
* 根据传入的代表算式的字符串计算结果并返回
* @param formula 代表算式的字符串
* @return 计算结果
*/
public float calculate(String formula) throws FormulaException
{
formula += "#";
operator.push('#');
for (int i = 0;i < formula.length();i ++)
{
//读取到非运算符(非数字符号)时进行的操作
if (!isOperator(formula.charAt(i)))
doWhenCharIsNotOperator(formula.charAt(i));
//读取到非运算符(数字符号)时进行的操作
else
{
if (!doWhenCharIsOperator(formula.charAt(i),formula.charAt(i - 1 > -1 ? i -1 : 0)))
{
throw exception;
}
}
}
if (getPriority(operator.pop()) != -4)
//字符串读取完成,但是符号栈内仍然有运算符
return 0;
else
return figure.pop();
}
编码
将上面贴出来的主方法改掉:
/**
* @description 计算运算式(实现类)
* <br>
* 根据传入的代表算式的字符串计算结果并返回
* @param formula 代表算式的字符串
* @return 计算结果
*/
private float calculateImpl(String formula) throws FormulaException
{
formula += "#";
operator.push('#');
for (int i = 0;i < formula.length();i ++)
{
//读取到非运算符(非数字符号)时进行的操作
if (!isOperator(formula.charAt(i)))
doWhenCharIsNotOperator(formula.charAt(i));
//读取到非运算符(数字符号)时进行的操作
else
{
if (!doWhenCharIsOperator(formula.charAt(i),formula.charAt(i - 1 > -1 ? i -1 : 0)))
throw exception;
}
}
if (getPriority(operator.pop()) != -4)
//字符串读取完成,但是符号栈内仍然有运算符
{
exception = FormulaException.getUnKnowException();
throw exception;
}
else
return figure.pop();
}
然后再写一个方法作为主方法,去调用上面的这个方法calculateImpl,然后捕获它抛出的异常,如果没有捕获到自己定义的FormulaException,但是捕获到了Exception,说明运算式有问题,但是这个问题暂时不能够被我们现在写的逻辑捕获到,那就抛出一个unknownException:
/**
* @description 计算运算式(主程序)
* <br>
* 根据传入的代表算式的字符串计算结果并返回
* <br>
* 如果运算式存在问题,捕获异常并抛出
* @param formula
* @return
* @throws FormulaException
*/
public float calculate(String formula) throws FormulaException
{
try {
return calculateImpl(formula);
}
catch (FormulaException e)
{
throw e;
}
catch (Exception e)
{
exception = FormulaException.getUnKnowException();
throw exception;
}
}