运算式错误判别
接下来要做的就是判断运算式是否正确,如果存在错误最好能够给出大致的错误类型。
基本思路
错误类型整理
首先判断可能出现错误的地方(即大致有哪几种错误类型),比较容易想到的大概就这些:
- 除零:最容易想到的,除法运算中除数为0
- 左右括号不匹配
- 定义域错误:暂时没有这个问题,因为之后可能要加入次方运算,就会对定义域有一定要求,比如不能对负数开方等
- 运算符左右两边只有一边有数字,或多个运算符连续
- 一个数字中存在多个小数点
- 不能识别的符号
- 其他可能没有想到的错误。
大致就是这些问题,最后两个其实可以归于一条,即数字字符串内的内容无法被转换为数字。
运算符的问题要考虑到之后可能加入的次方运算的特殊性(次方一般是一边是数字,另一边是其他运算符)。乘除运算不能放在运算式开头,一个运算符之后不能紧跟乘除运算(某些加减号在上一篇中已经被作为正负号处理了)。
进行简单的整理后大致有:
异常名称 | 简单解释 | 引发原因(程序中的辨别方法) |
---|---|---|
除零异常 | 除法运算中除数为0 | 在进行除法运算前检查除数 |
定义域异常 | 次方运算、幂运算中运算数超出定义域 | 在进行次方运算、幂运算前检查操作数(暂时用不到) |
运算符异常 | 算式第一个乘除前没有数字或运算符后紧跟乘除号 | 设置一个标记,初始为true,读取到数字时改为false,读取到运算符后变为true,当读取到乘除号时以此进行判断 |
数字异常 | 算式中存在无法识别的符号,或数字中有多个小数点 | 这个问题最终会引发数字字符串转化数字失败,因此只需要判断转化是否成功 |
左右括号不匹配 | 算式中左括号数量和右括号数量不一致 | 这个问题会导致有括号被当做运算符运算,所以可以在运算前判断运算符 |
不明原因异常 | 最后结束时符号栈栈顶应当是开始符号,结果发现不是 | 结束时判断符号栈栈顶元素,这个问题最可能是因为括号不匹配 |
准备工作(自定义异常)
先编写一个自定义异常类,供后续抛出异常使用,将异常的提示信息写死。
package exception;
public class FormulaException extends Exception {
//除零异常
private static FormulaException divideByZeroException = new FormulaException("除零异常:算式中某个除法运算的除数为0,请检查");
//定义域异常
private static FormulaException definitionDomainException = new FormulaException("定义域越界:请检查根号或次方运算等是否正确");
//运算符异常,某个运算符后紧接着乘除号或运算式开头为乘除号
private static FormulaException characterException = new FormulaException("运算符使用错误,请检查!");
//运算式中存在无法识别的符号,或数字中存在多个小数点,最终导致数字字符串无法转化为数字
private static FormulaException figureException = new FormulaException("运算式错误:存在无法识别的符号");
//左右括号不匹配
private static FormulaException bracketMismatchException = new FormulaException("左右括号不匹配");
//不明原因的异常,最可能是因为左右括号不匹配
private static FormulaException unKnowException = new FormulaException("运算式不明错误:可能是左右括号不匹配");
/*无参构造函数*/
private FormulaException(){
super();
}
//用详细信息指定一个异常
private FormulaException(String message){
super(message);
}
//用指定的详细信息和原因构造一个新的异常
private FormulaException(String message, Throwable cause){
super(message,cause);
}
//用指定原因构造一个新的异常
private FormulaException(Throwable cause) {
super(cause);
}
/**
* 除零异常
* @return 带有除零异常提示信息的对象
*/
public static FormulaException getDivideByZeroException()
{
return divideByZeroException;
}
/**
* 定义域异常
* @return 带有定义域异常提示信息的对象
*/
public static FormulaException getDefinitionDomainException()
{
return definitionDomainException;
}
/**
* 运算符异常
* @return 带有运算符使用错误信息的对象
*/
public static FormulaException getCharacterException()
{
return characterException;
}
/**
* 数字异常
* @return 带有引发该异常原因的提示信息的对象
*/
public static FormulaException getFigureException()
{
return figureException;
}
/**
* 不明异常
* @return 带有异常提示信息的对象
*/
public static FormulaException getUnKnowException()
{
return unKnowException;
}
/**
* 括号匹配异常
* @return 带有括号匹配错误信息的对象
*/
public static FormulaException getBracketMismatchException()
{
return bracketMismatchException;
}
}
其他要求
我们希望在运算式发现错误时,计算过程能够立刻停下来,不再继续向下读取,然后直接抛出异常。
解决方法为,每个操作方法的返回值都改为boolean,如果发现异常则返回false,操作正常返回true,每次读取字符后都进行一次判断,如果发现返回了false,则停止读取。
由于同一个异常被反复抛出有可能会导致抛出失败(系统会自动忽视它),所以只在主方法中抛出异常。
编码
准备工作做完了,异常的判别方法在上面也已经列出,接下来只要根据判别方法进行编码即可。首先先在Calculator类的成员变量中添加一个异常:
private FormulaException exception; //异常
当运算发现异常时,将异常在主方法中抛出。
运算符异常
先在Calculator类的成员变量中定义一个标记
private boolean flag = true; //标记,前一个符号是否为运算符,是则为true
然后当读取到数字时,将它改为false,读取到运算符时,将它改为true,在原代码中修改:
读取到运算符时,将它改为true:
/**
* @description 当前读取到的字符是运算符时进行的操作
* <br>
*
* @param currentCharacter 当前读取到的字符
*/
private void doWhenCharIsOperator(char currentCharacter,char proCharacter)
{
//非(当前符号是加减号且正负号标记激活状态),即当前符号不是正负号
if (!(getPriority(currentCharacter) == 1 && NPCanUse == true))
{
pushFigure(currentCharacter,proCharacter); //尝试将数字字符串进栈数字栈(有些特殊情况不能进栈)
operate(currentCharacter); //尝试运算(有些情况当前字符不会参与运算)
if (currentCharacter != ')')
flag = true;
}
//当前符号是正负号,并且为符号时,正负号标记*-1
else
NP *= currentCharacter == '-' ? -1: 1;
//当读取到加减号或左括号,下一个加减号视为正负号,NP激活
if (getPriority(currentCharacter) == 1 || getPriority(currentCharacter) == 3)
NPCanUse = true;
}
读取到数字时,将它改为false:
/**
* @description 当前读取到的字符不是运算符时进行的操作
* <br>
* 说明读取到的字符是数字,将它接到数字字符串之后
* <br>
* 由于下一个字符一定不会是正负号,所以激活标记睡眠
*
* @param currentCharacter 当前读取到的字符
*/
private void doWhenCharIsNotOperator(char currentCharacter)
{
figureString.append(currentCharacter);
flag = false;
NPCanUse = false; //NP标记休眠
}
接下来就是异常判断了,在读取到运算符时,如果flag标记为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(currentCharacter,proCharacter); //尝试将数字字符串进栈数字栈(有些特殊情况不能进栈)
operate(currentCharacter); //尝试运算(有些情况当前字符不会参与运算)
if (currentCharacter != ')') //除右括号外其他运算符右边均不能接乘除号
flag = true;
}
//当前符号是正负号,并且为符号时,正负号标记*-1
else
NP *= currentCharacter == '-' ? -1: 1;
//当读取到加减号或左括号,下一个加减号视为正负号,NP激活
if (getPriority(currentCharacter) == 1 || getPriority(currentCharacter) == 3)
NPCanUse = true;
return true;
}
然后在主方法中调用这个方法的地方检测这个方法是否成功,如果失败则抛出异常,在原来的代码中修改:
/**
* @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();
}
到这里,运算符使用异常的辨识代码就写完了,然后进行一下简单的测试:
当然,真正测试不可能只测这么三个,多测些,经过各种测试 以后。。。没有发现bug,那应该,可能,大概率是没有问题了,吧。那这个运算符异常就算过掉了。
上一页
下一页