文章总纲
1,实现计算归纳
2,整体运算逻辑
3,括号匹配
4,初等混合运算
5,运算符的顺序
6,JS源码实现效果展示
一、实现计算归纳
在不使用 eval函数 计算结果的条件下,实现计算器逻辑实现
1,括号匹配(除了负数有关求余以外皆可以,负数求余没有时间写了)
2,求余与四则乘方混合运算(求余运算比乘方乘除要复杂一点)
3,不含括号的初等式子运算
4,带括号的长式子混合运算(括号匹配与初等运算结合)
5,暂时不能计算的负数求余,乘方负数,后面有时间再改,这里加入变号,变数逻辑就行
二、整体运算逻辑
标准化计算式子:一切处理计算之前必须进行标准化
2(2-8)===>2*(2-8)
+(1-3)*2===>0+(1-3)*2
-(1-3)*2===>0-(1-3)*2
(1-3)*2===>0+(1-3)*2
因式分割:用加减分割的式子数组
str = str.replace(/\+/g, "FF")
str = str.replace(/\-/g, "FF")
factor_arr = str.split("FF")
console.log("因式数组" + factor_arr)
优先级区分:括号里面式子最优先,从左至右,括号式子里面没有括号是括号的优先,比如式子为1*(2+3)-(4-(-5)),-5与2+3都比4-(-5)优先,而2+3最优先,虽然-5的括号深度单位是2,但是2+3在式子左边一些。
运算符优先;等级优先划分,乘方大于乘、除以、余,大于加、减,同级运算从左到右,在本次计算逻辑实现中,其实关注重点点应该是: 乘方大于余、乘、除以,大于减、加。因为乘法和除法很特别,满足集合规律,即就是,a*b/c=a*(b/c),这表示,他可以不遵守从左到右,代码实现计算起来会简单很多,而求余与减法不可以,必须同级从左到右。
总体运算逻辑:只要存在括号,就把优先的括号去掉,然后用基本计算的结果代替括号的位置上式子,这个时候需要注意变号问题,直到最后是一个基本式子,然后计算。比如;
去掉括号过程
a -2 - b * 6 / 4 ^( - 7 + ( 0 + 4 - 7 ) - ( 1 * 7 ) ) ===> a-2 -b * 6 / 4 ^( -7 -3 - ( 1 * 7) )
===> a -2 -b * 6 / 4 ^( -7 - 3 - 7 ) ===> a - 2 - b * 6 / 4 ^( -14 ) ===> a- 2 + b * 6 / 4^14
(最后一步乘方负数不对,但暂时逻辑就是这样,括号里面的结果是负数就把括号同级因式前面的+-相反)【这个例子仅仅说明,本人还没写乘方负数的变号,但也不难,方便记录下】
优先的括号是从左到右,括号里面不含子括号的括号
三、括号匹配
虽然括号有深度单位区别,但是必须优先 水平方向,从左到右,最优先的括号。需要根据深度单位排序。
/**
* 括号优先级匹配 理论上可以去掉全部括号计算,但是验证起来麻烦,计算替换后长度变化混乱,本人没解决,因此迭代每一个优先项,增加了复杂度
* @param {*} string
* @returns [res_LB_index,bool]
*/
function macth_Br(string) {
let str = string, leftB_arr_index = [], rightB_arr_index = [], str_L_R = [];
console.log(str)
let leftB_N = 0, rightB_N = 0
for (let i = 0; i < str.length; i++) {
if (str[i] == "(" || str[i] == "(") {
leftB_N = leftB_N + 1
leftB_arr_index.push(i)
str_L_R.push([i, "左"])
} else if (str[i] == ")" || str[i] == ")") {
rightB_N = rightB_N + 1
rightB_arr_index.push(i)
str_L_R.push([i, "右"])
}
}
console.log("左括号索引" + leftB_arr_index)
console.log("右括号索引" + rightB_arr_index)
console.log("水平匹配合并后的索引" + str_L_R)
if (leftB_N > 0) {
if (leftB_arr_index.length != rightB_arr_index.length || str_L_R[0][1] == "右")
bool = "输入括号有误,无法匹配括号"
}
/**
* 水平匹配法 ,通用方法【由左到右】()(())()
* @param {*} str
*/
function levelMatch(index_LB) {
let n = 0, L_R_res = []; let m = 0
function fisrtCheck() { //匹配规则 配对的左索引的必须比右括号索引小
for (let i = 0; i < rightB_arr_index.length; i++) {
if (leftB_arr_index[i] > rightB_arr_index[i]) {
bool = bool == "无法匹配"
break
}
}
}
fisrtCheck()
if (index_LB.length > 2 && bool == "可以匹配") {
for (let i = 0; i < index_LB.length; i++) {
if (index_LB[i][1] == "左") {
// console.log("左边匹配索引" + i)
//级别记录
m = 0, n = 0
for (let j = i + 1; j < index_LB.length; j++) {
if (index_LB[j][1] == "左") {
n = n + 1
// console.log("nnn是" + n)
continue
} else {
//
if (m == n) {
L_R_res.push([index_LB[i][0], index_LB[j][0], n]);
// console.log("成功匹配" +c )
break;
}
if (m != n && j == index_LB.length - 1) {
bool = "无法匹配!"
break;
}
m = m + 1
// console.log("mmm是" + m)
}
}
}
else {
continue
}
}
} else {
L_R_res.push([index_LB[0][0], index_LB[1][0], n]);
}
//分级定序匹配 排序
let res_LB_index = []
console.log("水平匹配索引" + L_R_res)
for (let i = 0; i < L_R_res.length - 1; i++) {
for (let j = i + 1; j < L_R_res.length; j++) {
let a = L_R_res[i][2], b = L_R_res[j][2]
if (a > b) {
let t = L_R_res[i]
L_R_res[i] = L_R_res[j]
L_R_res[j] = t
}
}
}
console.log("排序后匹配索引" + L_R_res)
for (let i = 0; i < L_R_res.length; i++) {
res_LB_index.push([L_R_res[i][0], L_R_res[i][1]])
}
console.log("最终优先匹配计算 索引" + res_LB_index)
//secondCheck
for (let i = 0; i < res_LB_index.length; i++) {
if (res_LB_index[i][1] - res_LB_index[i][0] == 1) {
bool = "式子存在空括号,无法计算!"
}
}
return [res_LB_index[0], bool]
}
let res_LB_index_bool = [, bool]
if (leftB_N > 0) {
res_LB_index_bool = levelMatch(str_L_R)
} else {
bool = "式子不存在括号"
res_LB_index_bool[1] = "式子不存在括号"
}
console.log(bool)
console.log(res_LB_index_bool)
return res_LB_index_bool
}
let index_bool = macth_Br(string);
bool = index_bool[1]
四、初等混合运算
不带括号,只有乘方、求余、乘、除以、加减法的混合运算。
/** 初等运算 primary operation 不含括号,只有加减乘除、乘方、开根 求余 包含小数
* @param {*} str 不含括号的式子 初等式子 0+xy或0-xy
* @returns res_N 计算结果
*/
function primaryOperation(str) {
let string0 = str
let str_0_st = str[0]
let str_arr = []
//乘方替换
str = str.replace(/\*\*/g, '^');
str = str.replace(/÷/g, '/')
console.log("初等式子计算的式子" + str)
let F_add_Sub = [], F_a_s_index = [], F_ad_Sub_str = ""
let factor_arr = []
//因式与符号与数字再次组合 顺序由谁开头决定
for (let i = 0; i < str.length; i++) {
str_arr.push(str[i])
if (str[i] == "+" || str[i] == "-") {
F_add_Sub.push(str[i])
F_a_s_index.push(i)
F_ad_Sub_str = F_ad_Sub_str + str[i]
}
}
str = str.replace(/\+/g, "FF")
str = str.replace(/\-/g, "FF")
factor_arr = str.split("FF")
let res_fac_Num = []
console.log("加减符号数组" + F_add_Sub)
console.log("因式数组" + factor_arr)
//原始字符串数组
for (let i = 0; i < factor_arr.length; i++) {
//每个因式求结果 Num_arr_fac
let str_fac = factor_arr[i]
let fac_F_str = ""
let fac_F_arr_str = [], fac_f_index_arr = [], fac_Num_arr = []
for (let j = 0; j < str_fac.length; j++) {
let item = str_fac[j]
let bool = IsOpChar(item)
if (bool == true) {
fac_F_str = fac_F_str + item
fac_f_index_arr.push(j)
}
}
fac_F_arr_str.push(fac_F_str)
str_fac = str_fac.replace(/\^/g, "FFF")
str_fac = str_fac.replace(/\*/g, "FFF")
str_fac = str_fac.replace(/\//g, "FFF")
str_fac = str_fac.replace(/\%/g, "FFF")
fac_Num_arr = str_fac.split("FFF")
console.log("第" + i + "因式数组数字" + fac_Num_arr)
console.log("第" + i + "因式数组运算符顺序" + fac_F_str)
if (fac_F_str != "") {
//乘方最高级运算
while (fac_F_str.indexOf("^") != -1) {
console.log("去除^号前" + fac_F_str)
let str = "^"
let x = fac_F_str.indexOf(str)
let y = fac_Num_arr[x] ** fac_Num_arr[x + 1]
console.log(y)
console.log(x)
//数组删除
fac_Num_arr.splice(x, 1)
fac_Num_arr[x] = y
fac_F_str = delString(fac_F_str, str)
}
console.log("去除^号以后" + fac_F_str)
for (let a = 0; a < fac_F_str.length; a++) {
let str = fac_F_str[a]
let x = fac_F_str.indexOf(str)
let y = -9999
if (str == "/") {
y = fac_Num_arr[x] / fac_Num_arr[x + 1]
}
if (str == "*")
y = fac_Num_arr[x] * fac_Num_arr[x + 1]
if (str == "%")
y = fac_Num_arr[x] % fac_Num_arr[x + 1]
console.log(y)
console.log(x)
//数组删除
fac_Num_arr.splice(x, 1)
fac_Num_arr[x] = y
fac_F_str = delString(fac_F_str, str)
console.log("去除运算符*%/以后")
console.log(fac_Num_arr)
console.log(fac_F_str)
a--
}
}
res_fac_Num.push(fac_Num_arr)
}
console.log("因式结果数组" + res_fac_Num)
console.log("原始式子加减数组" + F_ad_Sub_str)
// if (str_0_st == "+" || str_0_st == "-")
// res_fac_Num.unshift(0)
for (let nn = 0; nn < F_ad_Sub_str.length; nn++) {
let str = F_ad_Sub_str[nn]
let x = F_ad_Sub_str.indexOf(str)
let y = -9999
if (str == "+") {
y = Number(res_fac_Num[x]) + Number(res_fac_Num[x + 1])
}
if (str == "-")
y = Number(res_fac_Num[x]) - Number(res_fac_Num[x + 1])
console.log(y)
console.log(x)
//数组删除
res_fac_Num.splice(x, 1)
res_fac_Num[x] = y
F_ad_Sub_str = delString(F_ad_Sub_str, str)
console.log("去除运算符+-以后")
console.log(res_fac_Num)
console.log(F_ad_Sub_str)
nn--
}
console.log("结果数组" + res_fac_Num)
let res_Num = res_fac_Num[0]
document.getElementById("txt").value = string0 + "=" + res_Num;
return res_Num
}
五、运算符顺序
本质来说,因式计算运算符的顺序都是高阶到低阶,从左到右的顺序,但是如果没有求余,只有乘除乘方,直接等价与“^/*”,有了求余,那么每一个因式都必须从乘方阶到乘除阶 ,同阶从左到右。如果只有“^/*+”只需满足高阶到低阶顺序即可,不需要从左到右的顺序。
见初等混合运算primaryOperation()函数
六、JS源码实现效果展示
源码已经绑定文章资源,包含HTML,JS,CSS,可自行下载。
资源好像不能设置免费,网盘资源。
链接:https://pan.baidu.com/s/1PBjlEKtWZXAoNBjyUmLHPA
提取码:ygis
计算器页面展示
计算器运算展示:
七、声明
未经许可不得转载文章