본문 바로가기
Project/JavaScript

[JavaScript 프로젝트 : 공학용 계산기] 공학용 계산기 upgraded

by 꾸압 2022. 1. 26.

이전 공학용 계산기의 기능이 사칙 연산 및 괄호에 따른 계산이었다.

이번 upgraded 버전은
(1) 연산자를 중복 입력하였을 시, 마지막 연산자만 기능되도록
(2) 삼각 함수 계산 추가 - 참고로 삼각함수는 sin( ) 등 반드시 괄호가 들어가도록 함
(3) 괄호가 잘못 입력시 오류 처리
(4) 소수점 기능

등을 추가 하였다.

업그레이드 과정의 70% 가량을 망할 괄호 처리에 소요하였으며, 너무 힘들었다...

왜 아무리 찾아도 괄호로 계산기 만든 사람이 없는지 이해되는...

 

<전체 코드 - HTML>

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8">
    <title>계산기</title>
    <style>
        table {
            border-collapse: collapse;
        }
        td{
            padding: 5px 10px;
            text-align: center;
        }
        input{
            border: none;
        }
    </style>
</head>
<body>
    <script type="text/javascript" src="Clear.js"></script>
    <script type="text/javascript" src="Sum.js"></script>
    
    <h1>공학용 계산기</h1>
    <form>
    <table border="1">
        <tr>
            <td colspan="5"><input type="text" id="display" size="50" value="0"></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="button" value="clear" onclick="clearDisplay()" style="height:100%; width:100%;">
            </td>
            <td colspan="1">
                <input type="button" value="(" onclick="add('(')" style="height:100%; width:100%;">
                <td><input type="button" value=")" onclick="add(')')" style="height:100%; width:100%;"></td>
                <td><input type="button" value="enter" onclick="sum()" style="height:100%; width:100%;"></td>
            </td>
        </tr>

        <tr>
            <td><input type="button" value="tan" onclick="add('t')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="1" onclick="add('1')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="2" onclick="add('2')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="3" onclick="add('3')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="+" onclick="add('+')" style="height:100%; width:100%;"></td>
        </tr>
        <tr>
            <td><input type="button" value="sin" onclick="add('s')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="4" onclick="add('4')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="5" onclick="add('5')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="6" onclick="add('6')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="-" onclick="add('-')" style="height:100%; width:100%;"></td>
        </tr>
        <tr>
            <td><input type="button" value="cos" onclick="add('c')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="7" onclick="add('7')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="8" onclick="add('8')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="9" onclick="add('9')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="*" onclick="add('*')" style="height:100%; width:100%;"></td>
        </tr>
        <tr>
            <td><input type="button" value="x^y" onclick="add('p')" style="height:100%; width:100%;"></td>
            <td colspan="2"><input type="button" value="0" onclick="add('0')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="/" onclick="add('/')" style="height:100%; width:100%;"></td>
            <td><input type="button" value="." onclick="add('.')" style="height:100%; width:100%;"></td>
        </tr>
    </table>
    </form>
</body>
</html>

 

<전체 코드 - JavaScript>

// 입력값 변수 input 선언 및 초기화
let input = "";

function add(letter){

    // 입력 문자를 더하여 
    input = input + letter;
    // display 창에 띄움
    document.getElementById('display').value = input;

}


function sum() {

    let stack = [];
    let arr = [];

    let tmp = "";
    let del = 0;

    // 연산자 우선 순위
    function priority(operator) {
        switch(operator) {
            case '(': case ')':
                return 0;
            case '+': case '-':
                return 1;
            case '*': case '/':
                return 2;
        }
        return 99;
    }

    // display 에 띄운 값을 받아 계산
    const formOrigin = document.getElementById('display').value;


    // 원본 문자열 변경 방지를 위한 새로운 문자열
    let formCpy = formOrigin;

    // 문자열 공백 제거
    let formTrim = formCpy.replace(/(\s*)/g,"");

    // 문자열 내 중복 연산자 제거
    let formNoDup = removeDup(formTrim);
    
    // 괄호에 대한 연산 오류 처리
    let form = exception(formNoDup);


    // pre-calculating of 삼각 함수 괄호 내부


    for(let i = 0; i < form.length; i++) {

        const char = form.charAt(i);

        switch(char) {
            case '(':
                stack.push(char);
                break;

            case '+': case '-': case '*': case '/':
                while(stack[stack.length - 1] != null &&
                    (priority(char) <= priority(stack[stack.length - 1]))) {

                        tmp += stack.pop();

                        if(isNaN(stack[stack.length - 1])) {
                            arr.push(tmp);
                            tmp = "";
                        }
                    }
                stack.push(char);
                break;

            case ')':
                if(del === 0) { }
                else {
                    let returnOp = stack.pop();
                    while(returnOp != '(') {
                        tmp += returnOp;
                        returnOp = stack.pop();

                        if(isNaN(stack[stack.length - 1])) {
                            arr.push(tmp);
                            tmp = "";
                        }
                    }
                    break;
                }

            default:
                tmp += char;

                function checkTriNum(letters){
                    if(letters.includes('s')) return true;
                    else if(letters.includes('c')) return true;
                    else if(letters.includes('t')) return true;
                    else if(letters.includes('p')) return true;
                }

                // 삼각함수 안에 괄호가 있는건 여기서 고려하지 않고, 
                // 삼각함수 연산자 다음에 숫자가 나온 뒤 다른 연산자 혹은 문자열의 끝을 만나면
                // 바로 삼각함수가 계산되도록 함.
                if(isNaN(form.charAt(i+1)) || (i+1 === form.length)) {

                    // 삼각함수 검사
                    if(checkTriNum(tmp) && form.charAt(i+1) !== '(') {
                        
                        console.log('tmp 진입 확인 : ' + tmp);

                        if(tmp.includes('s')) { tmp = triCal(tmp, 's'); }
                        else if(tmp.includes('c')) { tmp = triCal(tmp, 'c'); }
                        else if(tmp.includes('t')) { tmp = triCal(tmp, 't'); }
                        else if(tmp.includes('p')) { tmp = triCal(tmp, 'p'); }
                    }

                    console.log('triCal 계산값 : ' + tmp);

                    // 다음 연산자가 삼각함수, 소수점, '(' 면 tmp에 저장하고 skip
                    if(checkTriNum(form.charAt(i+1))) { }
                    else if(form.charAt(i+1) === '.') { }
                    else if(form.charAt(i+1) === '(') { }
                    else {
                        // del : 삼각함수 문자열에 든 ')' 가 다음 문자열에 포함되지 않게 함
                        if(form.charAt(i+1) === ')') { del++ }
                        console.log('tmp 전달 최종 : ' + tmp);
                        arr.push(tmp);
                        tmp = "";
                    }
                    
                }
                break;
        }
    }
        
    console.log('후위 연산식으로 들어갈 arr : ' + arr);

    // stack 에 남은 연산자 to arr 이동
    while(stack.length != 0) {
        arr.push(stack.pop());
    }

    // 후위계산식
    function solution(postFixArr) {
        let stack = [];

        for (let post of postFixArr) {

            if(!isNaN(post)) stack.push(+post);
            else {
                let rightNum = stack.pop();
                let leftNum = stack.pop();

                if (post === '+') stack.push(leftNum + rightNum);
                else if (post === '-') stack.push(leftNum - rightNum);
                else if (post === '*') stack.push(leftNum * rightNum);
                else if (post === '/') stack.push(leftNum / rightNum);
            }
        }
        return +stack;
    }


    let result = "";
    for(let element of arr ){
        result += element;
        result += " ";
    }

    console.log(form);
    console.log(result);

    // final result number
    console.log("Result : " + solution(arr));

    // html에 결과값 전달
    document.getElementById('display').value = solution(arr);


    // 괄호에 대한 오류 처리 code
    function exception(errChk) {

        const phrase = errChk;

        // const alarm = '괄호 잘못 입력'

        let i = 0;
        let leftCnt = 0;
        let rightCnt = 0;

        let phraseArr = phrase.split("");

        while(i !== phrase.length) {

            // if 조건문이 각 2개이며, 가독성을 위해 각 부분에 중괄호 추가
            {
            if(phraseArr[i] === ('(')) { leftCnt++; }
            else if(phraseArr[i] === (')')) { rightCnt++; }
            i++; 
            }

            {
            // error : ')' 에 연속하여 숫자
            if(phraseArr[i-1] === ')' && !isNaN(phraseArr[i])) {
                return 0;
            }
            // error : 숫자에 연속하여 '('
            else if(phraseArr[i] === '(' && !isNaN(phraseArr[i-1])) {
                return 0;
            }
            }
        }

        // error : 괄호 갯수가 홀수
        if(leftCnt !== rightCnt) { 
            return 0;
        }
        else {
            return errChk;
        }
    }

    // 연산자 중복 제거
    function removeDup(phrase) {

        let formPh = phrase;

        let tmpArr = [];

        for(let i=0; i < formPh.length; i++) {

            let classifi = formPh.charAt(i);

            // i-1 index 를 쓰기 위해 index[0] 은 항상 push
            if(i === 0 ) {
                tmpArr.push(classifi);
            }
            else {
                // 숫자면
                if(!isNaN(classifi)) {
                    tmpArr.push(classifi);
                }
                // 이전 및 현재 index 모두 연산자면
                else if(isNaN(classifi) && isNaN(formPh.charAt(i-1))) {

                    // 이전 index ['(']
                    if(formPh.charAt(i-1) === '(') {
                        
                        // 현재 index : ['('] >>> 이전 괄호 pop
                        if(classifi === '(') {
                            tmpArr.pop();
                            tmpArr.push(classifi);
                        }
                        // 현재 index : [')'] >>> 괄호 둘 다 제거
                        else if(classifi === ')') {
                            tmpArr.pop();
                        }
                        // 현재 index : ['연산자'] >>> 이전 중복 연산자 모두 제거. 현재 index 생존
                        else {
                            while(isNaN(tmpArr[tmpArr.length-1])) {
                                tmpArr.pop();
                            }
                            tmpArr.push(classifi);
                        }
                    }
                    // 이전 index [')']
                    else if(formPh.charAt(i-1) === ')') {

                        // 현재 index : ['('] || index : [')]
                        if(classifi === '(' || classifi === ')') { }
                        // 현재 index : ['나머지 연산자']
                        else {
                            tmpArr.push(classifi);
                        }
                    }
                    // 이전 index ['나머지 연산자']
                    else {
                        // 현재 index '('
                        if(classifi === '(') {
                            tmpArr.push(classifi);
                        }
                        // ')'
                        else if(classifi === ')') {
                            tmpArr.pop();
                            tmpArr.push(classifi);
                        }
                        // 'rest operator'
                        else {
                            tmpArr.pop();
                            tmpArr.push(classifi);
                        }
                    }
                }
                // 현재 index 는 연산자, 이전 index는 숫자
                else if(isNaN(classifi) && !isNaN(formPh.charAt(i-1))) {
                    tmpArr.push(classifi);
                }
            }
        }

        let reformed = tmpArr.join("");

        console.log(reformed);

        return reformed;
    }

    
    // 삼각함수 변환 로직
    function triCal(letters, triNum) {

        console.log('afterTmp 전환 전 tmp 확인 : ' + letters);

        // 삼각함수에 따라 split
        let afterTmp = letters.split(/s|c|t|p/);

        let num = '0';
    
        console.log('afterTmp 변환 확인 : ' + afterTmp + '\n');

        switch (triNum) {
            case 's':
                num = Math.sin(afterTmp[1]);

                console.log(afterTmp[1]);
                console.log('sin 계산 : ' + Math.sin(afterTmp[1]));
                console.log('num 전환값 : ' + num);

                if(!isNaN(parseFloat(afterTmp[0]))) { 
                    return parseFloat(afterTmp[0]) * parseFloat(num); 
                }
                else { 
                    return parseFloat(num); 
                }
            case 'c':
                num = Math.cos(afterTmp[1]);
                if(!isNaN(parseFloat(afterTmp[0]))) { return parseFloat(afterTmp[0]) * parseFloat(num); }
                else { return parseFloat(num); }
            case 't':
                num = Math.tan(afterTmp[1]);
                console.log('arr[1] : ' + afterTmp[1]);
                console.log('num : ' + num + '\n');

                console.log(afterTmp[1]);
                console.log('tan 계산 : ' + Math.tan(afterTmp[1]));
                console.log('num 전환값 : ' + num);

                if(!isNaN(parseFloat(afterTmp[0]))) { 
                    return parseFloat(afterTmp[0]) * parseFloat(num); 
                } else {
                    return parseFloat(num);
                }
                break;
            case 'p':
                if(!isNaN(parseFloat(afterTmp[0]))) {
                    num = Math.pow(parseFloat(afterTmp[0]), parseFloat(afterTmp[1]));
                    console.log('pow 계산값 : ' + num);
                    return parseFloat(num);
                }
                else return(console.log('입력 error'));
        }
    }
}

 

>>> FeedBack

사수 분 피셜 : hmtl 에서 onclick으로 문자열 add 하지 말고, html의 class 로 js에 직접 문자를 전달하면 좋다고 하셨다.

시간이 되면 추가 예정

 

 

 

 

 

 

 

 

 

댓글