/**标签:ch,Java,String,tokens,token,str,字符串,return,表达式 From: https://www.cnblogs.com/XiangHuai0v0/p/18408557
* 计算字符串表达式的值, 不支持小数
* <ul>
* <li>加法('+')</li>
* <li>减法('-')</li>
* <li>乘法('*')</li>
* <li>除法, 保留两位小数('/')</li>
* <li>取余, 获取商('//')</li>
* <li>取余, 获取余数('%')</li>
* </ul>
*/
public class Calculator {
record Token(String str, int begin) {
}
public String eval(String expr) {
List<Token> tokens = getTokens(expr);
var value = expr(tokens);
if (!"eof".equals(tokens.getFirst().str)) {
tokens.removeLast();
String msg = "token没有被全部消耗: " + tokens.stream().map(Token::str).collect(Collectors.joining());
throw new IllegalArgumentException(msg);
}
return value.toString();
}
private List<Token> getTokens(String expr) {
List<Token> tokens = new LinkedList<>();
char[] chars = expr.toCharArray();
int i = 0;
while (i < chars.length) {
char ch = chars[i];
if (ch >= '0' && ch <= '9') {
int begin = i;
do {
i++;
} while (i < chars.length && chars[i] >= '0' && chars[i] <= '9');
String str = new String(chars, begin, i - begin);
tokens.add(new Token(str, begin));
} else if (ch == '+' || ch == '-' || ch == '*' || ch == '%') {
String str = String.valueOf(ch);
tokens.add(new Token(str, i));
i++;
} else if (ch == '/') {
if (i + 1 < chars.length && chars[i + 1] == '/') {
tokens.add(new Token("//", i));
i += 2;
} else {
tokens.add(new Token("/", i));
i++;
}
} else if (ch == '(' || ch == ')') {
String str = String.valueOf(ch);
tokens.add(new Token(str, i));
i++;
} else if (ch == ' ') {
i++;
} else {
String msg = String.format("非法字符'%c'(%d)", ch, i);
throw new IllegalArgumentException(msg);
}
}
tokens.add(new Token("eof", chars.length));
return tokens;
}
private BigDecimal expr(List<Token> tokens) {
// expr -> term | term '+' expr | term '-' expr
var value = term(tokens);
Token token = tokens.getFirst();
if ("+".equals(token.str)) {
tokens.removeFirst();
return value.add(expr(tokens));
}
if ("-".equals(token.str)) {
tokens.removeFirst();
return value.subtract(expr(tokens));
}
return value;
}
private BigDecimal term(List<Token> tokens) {
// term -> factor | factor '*' term | factor '/' term | factor '//' term | factor '%' term
var value = factor(tokens);
Token token = tokens.getFirst();
if ("*".equals(token.str)) {
tokens.removeFirst();
return value.multiply(term(tokens));
}
if ("/".equals(token.str)) {
tokens.removeFirst();
return value.divide(term(tokens), 2, RoundingMode.HALF_UP);
}
if ("//".equals(token.str)) {
tokens.removeFirst();
return value.divideAndRemainder(term(tokens))[0];
}
if ("%".equals(token.str)) {
tokens.removeFirst();
return value.divideAndRemainder(term(tokens))[1];
}
return value;
}
private BigDecimal factor(List<Token> tokens) {
// factor -> num | '-' num | '(' expr ')'
Token token = tokens.getFirst();
if ("-".equals(token.str)) {
tokens.removeFirst();
return num(tokens).negate();
}
if ("(".equals(token.str)) {
tokens.removeFirst();
var value = expr(tokens);
Token next = tokens.removeFirst();
if (!")".equals(next.str)) {
String msg = String.format("括号没有关闭 %s", token);
throw new IllegalArgumentException(msg);
}
return value;
}
return num(tokens);
}
private BigDecimal num(List<Token> tokens) {
Token token = tokens.removeFirst();
for (char ch : token.str.toCharArray()) {
if (ch < '0' || ch > '9') {
throw new IllegalArgumentException("非法数值: " + token);
}
}
return new BigDecimal(token.str);
}
}