计算器运算逻辑实现(带括号,求余,乘方,加减乘除),不使用eval函数-前端JavaScript 源码实现——括号匹配与初等运算。

文章总纲

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)),-52+3都比4-(-5)优先,而2+3最优先,虽然-5的括号深度单位是2,但是2+3在式子左边一些。

运算符优先;等级优先划分,乘方大于乘、除以、余,大于加、减同级运算从左到右,在本次计算逻辑实现中,其实关注重点点应该是: 乘方大于余、乘、除以,大于减、加。因为乘法和除法很特别,满足集合规律,即就是,a*b/c=a*(b/c),这表示,他可以不遵守从左到右,代码实现计算起来会简单很多,而求余与减法不可以,必须同级从左到右。

总体运算逻辑:只要存在括号,就把优先括号去掉,然后用基本计算的结果代替括号的位置上式子,这个时候需要注意变号问题,直到最后是一个基本式子,然后计算。比如;

去掉括号过程

-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 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

计算器页面展示

计算器运算展示:

七、声明

未经许可不得转载文章

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

先生余枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值