四则运算器
-
产品说明
-
使用说明
你可以在calculate.html
网页中的输入框内写入或使用数字键盘任意仅包含四则运算的的式子
(可带有括号),随后可以使用回车或点击 = 键来得到计算结果。
如输入1+1
将得到计算结果2
。
同时你可以通过滑动小数点精度设置滑动条,以改变含有除法的计算结果中小数点位数。
此外,你还可以点击随机生成表达式按键来随机生成一段表达式并知悉其计算结果。
-
考虑到的情况
- 用户输入了非法字符。
- 解决:通过一定的条件判断,程序将告知用户的输入含有不可用的字符。
- 用户输入了不正确的表达式。
- 解决:通过一定的条件判断,程序将告知用户输入的表达式不正确,无法完成计算请求。
- 用户输入的表达式中没有匹对的括号。
- 解决:通过一定的条件判断,程序将告知用户输入的括号无法形成配对,无法完成计算请求。
- 用户没有任何输入,或者只输入了空格。
- 解决:通过一定的条件判断,程序将请求用户输入表达式。
- 用户输入的表达式中含有空格。
- 解决:通过一定的语句,程序将自动删除这些空格。
- 计算中负号和减号的问题。
- 解决:通过一定的条件判断,程序将自动识别负号与减号。当然,程序不允许出现多个负号叠加。
- 除以零问题。
- 解决:通过一定的条件判断,程序将告知用户发生了除以零事件。
- 用户使用时没有设置精度的问题。
- 解决:通过给get方法设置初始值,避免了无数据传回导致的错误。
-
学习历程
-
@RequestParam标签设置默认值
准备用get方法来上传数据时,发现若忘记在url里传递数据,容易引起程序的错误。
我搜索“get方法默认值设定”,结果遇见了熟悉的面孔:Python中通过get方法来给字典中新建的键的值的默认值(
显然,这不是我想要的。于是我换了个搜索关键词,最后找到了一篇有关的文章。
我们通过@RequestParam(value = "name", defaultValue = "koish")String name
来给name设置默认值。它的意思是:
如果没有名为name
的参数没有收到值,就设其值为"koish"
。
或者,我们通过@RequestParam(value = "name", required = false)String name
来表明参数name
非必须。
- BigDecimal的应用
刚开始解决数据溢出,我最先想到利用数组来储存多位的数据。
随后我从Q频了解了BigDecimal类,脱离了原来的肝脏地狱(笑)
BigDecimal可以传入String来构建:
BigDecimal a = new BigDecimal("2.5");
BigDecimal b = new BigDecimal("5");
随后,我们可以对传入的数据进行计算:
System.out.println(a.add(b));
System.out.println(a.subtract(b));
System.out.println(a.multiply(b));
System.out.println(a.divide(b,5,4));
以上的输出结果是:
7.5
-2.5
12.5
0.50000
其中唯独divide多传入了两个参数。
第二个参数是为了防止无限循环小数产生无限位数而不得不加上的小数位数限定。
第三个参数规定了 如何舍去小数 的模式。其中4模式代表四舍五入模式。
此外,BigDecimal还可以用setScale
方法进行截断和四舍五入。例如:
BigDecimal c = new BigDecimal("1.23456789");
System.out.println(c.setScale(3,4))
输出结果:
1.235
最后我们想取出BigDecimal中的数据时,我们可以用toString
方法来将BigDecimal对象转化为String对象。
- 过滤空格的问题
在解决空格问题时,我遇到了很多问题,
一开始设置若发现传回数据的第一个字符为空,即判断该数据为空,
然而事实上第一个字符误输空格的问题很多,这会导致被误判。
于是决定在数据解析的过程中判断传回数据为空,很快就造成了很多bug。
最后选择上网找答案,发现只需要使用代码equation = equation.replaceAll(" ","")
将所有空格替换为空字符串,就可以实现对空格的过滤。
如此,就能将只有空格和无数据传回统一起来,还同时解决了传回表达式中含有空格的问题。
- 递归算法应用
在自动生成表达式的方法中我使用了递归算法。
它的优点是易写,易读。
实现递归算法,需要一个方法中包含对自身的调用。
例如:
int f(int x) {
if (x > 1) {
return f(x-1) + x;
} else {
return 1;
}
}
显然,当调用该方法f(5)
时,我们实现了1+2+3+4+5
的计算。
在自动生成表达式的方法中,我利用递归算算法的层层相套的特性,实现了对代码的简化。
当然,这可能导致太多层次的嵌套导致内存溢出。不过,这已经不在我的考虑过程中了。
-
代码:
-
Controller
package com.example.calculate.controller;
import com.example.calculate.service.Mainservise;
import org.springframework.web.bind.annotation.*;
@RestController
public class Main {
Mainservise Ms = new Mainservise();
@RequestMapping(value = "/calculate", method = RequestMethod.GET)
public String calculate(@RequestParam(value = "accuracy", defaultValue = "20") int accuracy, @RequestParam String equation) {
equation = equation.replaceAll(" ","");
equation = equation.replaceAll("(","(");
equation = equation.replaceAll(")",")");
String result;
if (equation.equals("Random")) {//获取随机表达式
equation = Ms.Randomequation(100);
}
String[] splitation = Ms.split("(" + equation + ")");//分段
if (splitation[0].equals("(")) {//计算
result = Ms.getResult(splitation, accuracy);
} else {//报错
result = splitation[0];
}
return equation + "<br>" + result;
}
}
-
Service
package com.example.calculate.service;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Random;
@Service
public class Mainservise {
public final String[] error = {
"这是一个非法的表达式!",//0
"这个表达式存在非法的字符!",//1
"这个表达式的括号无法配对!",//2
"请输入要计算的表达式!",//3
"除数不能为0!"//4
};
/*split方法相关解释:
split方法用于将整行的算式切割成分开的元素,存储于一个数组中,并检查算式表达是否有误。
例如:
输入String类型数据:"21+(3.1-1)"
该方法会返回String类型数组:{"21","+","(","3.1","-","1",")"}
通过该方法能使得算式更易于计算机读取。
*/
public String[] split(String equation) {
if (equation.length() == 2) {//没有输入、只输入空格
return new String[]{error[3]};
}
String[] split = new String[500];//存储表达式
int splitcur = 0;
char readcur;//存储读取到的字符串中的字符
boolean check = false,//用于判断前一个是否为数字或 )
minus = true;//用于控制负号的个数不超过1
int len = equation.length(),//记录算式长度
brackets = 0,//判断括号是否匹配
j = 0;//确定算式中某个数字的头位置
for (int i = 0;i < len;i++) {
readcur = equation.charAt(i);
switch (readcur) {
case '+':
case '*':
case '/':// +,/,* 前面必须是数字或 )
if (check) {
split[splitcur++] = equation.substring(j,i);
split[splitcur++] = readcur + "";
j = i+1;
check = false;
minus = true;
}else{//前面不是数字或 ) ,返回错误类型0
return new String[]{error[0]};
}
break;
case '-':// - 可以跟在数字前面作为负号,也可以作为运算符。
if (check) {//作为运算符
split[splitcur++] = equation.substring(j,i);
split[splitcur++] = "-";
check = false;
minus = true;
j = i+1;
}else if(minus){//作为负号
minus = false;
j = i;
}else{
return new String[]{error[0]};
}
break;
case '(':// ( 前面必须是运算符或 (
if (check) {//前面是数字或 ) ,返回错误类型0
return new String[]{error[0]};
}else{
split[splitcur++] = readcur + "";
check = false;
j = i+1;
if (i != 0) {
brackets++;
}
}
break;
case ')':// ) 前面必须是数字或 )
if (check) {
if (! split[splitcur-1].equals(")")) {//特殊讨论,前面不为数字时将不会导致空串存入数组
split[splitcur++] = equation.substring(j,i);
}
split[splitcur++] = readcur + "";
check = true;
j = i+1;
if (i != len-1) {
brackets--;
}
}else{//前面不是数字或 ) ,返回错误类型0
return new String[]{error[0]};
}
break;
default://表达式不许有特殊字符,数字前面不许有)
if ((48 <= readcur && readcur <= 57) || readcur == '.') {
if (split[splitcur-1].equals(")")){//特殊讨论,前面有 ) 时返回错误类型0
return new String[]{error[0]};
}else{
check = true;
}
}else{//存在不合法字符,返回错误类型1
return new String[]{error[1]};
}
}
if (brackets < 0) {//括号不匹配,返回错误2
return new String[]{error[2]};
}
}
if (brackets > 0) {//括号不匹配,返回错误2
return new String[]{error[2]};
}
String[] splitation = new String[splitcur];
for (j = 0;j < splitcur;j++) {//切割split的多余空间
splitation[j] = split[j];
}
return splitation;
}
/*getResult计算流程说明:
将数字和运算符与括号分别入栈
若遇见 +,- 运算符,若上一位为 +,-,/,* 则先弹出上一位,再入栈,否则直接入栈
若遇见 *,/ 运算符,直接入栈
若遇见 ( ,则入栈后继续往下
若遇见 ) ,则一直弹出栈内运算符直到在栈内遇见 ( ,随后将 ( 弹出
若遇见数字,若上一位运算符为 *,/ 则弹出
*/
public String getResult(String[] equation,int accuracy) {
String[] numlist = new String[300];//存储数字
String[] symbollist = new String[300];//存储符号
int numcur = -1 , symbolcur = -1;
int len = equation.length;//存储表达式长度
String symbol,element;//存储用于计算的符号、当前读取的元素
BigDecimal num1,num2;//存储要计算的数字
for (int j = 0;j < len;j++) {
element = equation[j];
symbol = "#";
if ("()+-*/".contains(element)) {
switch (element) {
case "(":
symbollist[++symbolcur] = "(";
break;
case ")":
if (symbollist[symbolcur].equals("(")) {
symbolcur--;
if (symbolcur > -1 && "*/".contains(symbollist[symbolcur])) {
symbol = symbollist[symbolcur--];
}
} else {
symbol = symbollist[symbolcur--];
j -= 1;
}
break;
case "-":
case "+":
if (symbollist[symbolcur].equals("(")) {
symbollist[++symbolcur] = element;
} else {
symbol = symbollist[symbolcur];
symbollist[symbolcur] = element;
}
break;
case "*":
case "/":
symbollist[++symbolcur] = element;
}
} else {
numlist[++numcur] = element;
if ("*/".contains(symbollist[symbolcur])) {
symbol = symbollist[symbolcur];
symbolcur--;
}
}
if (!symbol.equals("#")) {
num1 = new BigDecimal(numlist[numcur--]);
num2 = new BigDecimal(numlist[numcur]);
numlist[numcur] = operations(num1,symbol,num2,accuracy);
if (numlist[numcur].equals("#")) {
return error[4];
}
}
}
return "=" + numlist[0];
}
//利用BigDecimal完成二元计算
public String operations(BigDecimal num1,String symbol,BigDecimal num2,int accuracy) {
String num = null;
if (symbol.equals("+")) {
num = num2.add(num1).toString();
}
if (symbol.equals("-")) {
num = num2.subtract(num1).toString();
}
if (symbol.equals("*")) {
num = num2.multiply(num1).toString();
}
if (symbol.equals("/")) {
if (num1.toString().charAt(0) == '0') {//除数不为零
return "#";
}
num = num2.divide(num1,accuracy,4).toString();
}
return num;
}
//随机生成一段式子
public String Randomequation(int upperlimit) {//upperlimit限制表达式长度
Mainservise Ms = new Mainservise();
String Requation = "";
Random R = new Random();
char[] symbol = {'+','-','*','/'};
BigDecimal Rnum;
int Rchoose;
while (Requation.length() < upperlimit) {
Rchoose = R.nextInt(3);//2:结束生成表达式,1:生成含有随机表达式的(),0:生成随机数
if (Rchoose == 2 && !Requation.isEmpty()){//2
break;
}else if (Rchoose == 1){//1
Requation += "(" + Ms.Randomequation(upperlimit - Requation.length()) + ")";
}else{//0
Rnum = new BigDecimal((R.nextDouble() - 0.5) * 5000);//生成-2500~+2500之间的浮点数
Requation += Rnum.setScale(2,4);
}
Requation += symbol[R.nextInt(4)];//生成随机符号
}
Requation = Requation.substring(0,Requation.length()-1);//掐掉最后多余的符号
return Requation;
}
}
-
接口文档
URL:
HTTP请求方式:
GET
请求参数:
参数 | 必须 | 类型 | 说明 |
---|---|---|---|
accuracy | false | int | 小数点精度 |
equation | true | String | 表达式 |
接口示例:
-
HTML源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="koish" content="width=device-width, initial-scale=1.0">
<title>四则运算器</title>
<script>
function fun(){
let equation = document.getElementById("equationbox").value;
document.getElementById("getequationbox").value = equation;
document.getElementById("form").submit();
}
function fun2(){
let accuracy = document.getElementById("accuracybox").value;
document.getElementsByClassName("p")[0].innerHTML = accuracy;
document.getElementById("getaccuracybox").value = accuracy;
}
function fun3(){
document.getElementById("getequationbox").value = "Random";
document.getElementById("form").submit();
}
function fun4(symbol){
before = document.getElementById("equationbox").value;
document.getElementById("equationbox").value = before + symbol;
}
function fun5(){
before = document.getElementById("equationbox").value;
before = before.slice(0,-1);
document.getElementById("equationbox").value = before;
}
</script>
</head>
<body>
<h3>四则运算器</h3>
<p>输入表达式:<input id="equationbox" type="text" maxlength="300"></p>
<p>
<button onclick="fun4('+')">+</button> <button onclick="fun4('-')">-</button> <button onclick="fun4('*')">*</button> <button onclick="fun4('/')">/</button>
<button onclick="fun4('.')">.</button> <button onclick="fun5()">退格</button>
<br/>
<button onclick="fun4('1')">1</button> <button onclick="fun4('2')">2</button> <button onclick="fun4('3')">3</button> <button onclick="fun4('(')">(</button>
<br/>
<button onclick="fun4('4')">4</button> <button onclick="fun4('5')">5</button> <button onclick="fun4('6')">6</button> <button onclick="fun4(')')">)</button>
<br/>
<button onclick="fun4('7')">7</button> <button onclick="fun4('8')">8</button> <button onclick="fun4('9')">9</button> <button onclick="fun4('0')">0</button>
<button onclick="fun()">=</button><br/>
</p>
<p>小数点精度选择:<input id="accuracybox" type="range" min="0" max="100" step="1" value="20" onchange="fun2()" name="accuracy"><b class="p">20</b></p>
<button onclick="fun3()">随机生成表达式</button>
<form id="form" action="http://localhost:8080/calculate" method="get">
<input id="getaccuracybox" type="hidden" name="accuracy">
<input id="getequationbox" type="hidden" name="equation">
</form>
</body>
</html>