본문 바로가기

컴퓨터/프로젝트

계산기 웹 만들기(Calculator)

1. 소개

웹 사이트에서 간단한 계산기를 만들 것인데 후위 표기법으로 작동하는 계산기를 만들어 볼 것이다.

2. 코드 및 구현

 

(구현)

3. 구현 과정

const data = ["(", ")", "C", "%", "7", "8", "9", "÷", "4", "5", "6", "×", "1", "2", "3", "-", '.', "0", "=", "+"];

먼저 data라는 변수에 계산기 버튼의 요소들을 저장한다.

window.onload = function () {
    for (let i = 0; i < data.length; i++)
        create(i);
}

그리고 웹 페이지가 열릴 때 create라는 함수를 실행하는데 create는 버튼을 생성하는 함수이다.

/* 버튼 생성 */
function create(i) {
    let btn = document.createElement("button");
    btn.append(data[i]);

    /* 연산자라면 */
    if (isNaN(data[i]))
        btn.className = "operator";
    
    /* 피연산자 즉, 숫자라면 */
    else
        btn.className = "operand";

    document.getElementById("container").appendChild(btn);

    btn.addEventListener("click", function () {
        if (data[i] == "C")
            document.getElementById("input").value = "";
        else if (data[i] == "=") {
            let input = document.getElementById("input").value;
            if (!input)     //아무것도 입력을 하지 않을 경우
                alert("Please fill in the blank");
            else
                result();
        }
        else
            document.getElementById("input").value += data[i];
    })
}

버튼의 태그를 생성하고 그곳에 data값을 저장한 다음에 data값에 따라 연산자인지 피 연산자인지 구분해준다. 그리고 C 버튼을 눌렀을 때는 input값을 전체 삭제하고 =을 눌렀을 때는 result 함수가 실행이 되는데 이 함수는 입력된 input 값을 후위 표기법으로 변환한 뒤 계산을 진행시켜준다. 그리고 나머지 버튼들을 입력했을 때는 input값에 해당하는 버튼의 요소들을 추가시켜준다.

function result() {
    let input = document.getElementById("input").value;
    input = input.replace(/(\s*)/g, "");    //공백문자 제거
    let history = input + "=";              //기록 용도

    let stack = [];         //연산자 임시 저장 및 후위 표기법 계산 결과 저장
    let convert = [];       //후위 표기법 저장
    let temp = "";          //입력된 수 임시 저장

후위 표기법으로 변환하기 전에 필요한 변수들을 선언한다.

    for (let i = 0; i < input.length; i++) {
        let token = input[i];
        if (!isNaN(token) || token == '.') {
            temp += token;
            if ((isNaN(input[i + 1]) || (i + 1 == input.length)) && input[i + 1] != '.') { 
                convert.push(temp);
                temp = ""
            }
        }

intput의 값을 하나씩 token에 임시로 저장을 한 뒤에 token의 내용이 피연산자이거나 .이라면 temp 변수에 추가한다. 그리고 만약 input 값이 12.1+2이라고 한다면 아래 표와 같이 진행이 된다.

순번 input token temp
1 12.1+2 1 1
2 12.1+2 2 12
3 12.1+2 . 12.
4 12.1+2 1 12.1

그리고 현재 위치에서 다음 input값이 연산자이거나 다음 input값이 없고, 다음 input값에 .이 아니라면 convert에 해당하는 temp를 저장한다. 그리고 temp를 초기화 시켜준다. 아래의 코드는 input값이 연산자일 경우이다.

        else {
            switch (token) {
                case '(':
                    stack.push(token);
                    break;
                case ')':
                    while (1) {
                        let popOP = stack.pop();
                        if (popOP == '(')
                            break;
                        convert.push(popOP);
                    }
                    break;
                case '+':
                case '-':
                case '×':
                case '*':
                case '÷':
                case '/':
                case '%':
                    while (stack.length && WhoPrecOp(stack[stack.length - 1], token) >= 0) {
                        convert.push(stack.pop());
                    }
                    stack.push(token);
                    break;
            }
        }
    }

(가 입력될 경우 바로 stack에 저장을 한다. 그리고 )가 입력이 되면 (가 나올 때까지 stack을 pop한다. 그 결과를 convert에 저장을 한다.

그리고 스택이 비어있지않고, 스택에 마지막 요소와 token 중 스택의 마지막 요소가 순위가 더 높다면 스택 요소를 convert에 저장하고 token값을 stack에 저장한다.

/* 연산자 순위 */
function GetOpPrec(op) {
    switch (op) {
        case '×':
        case '*':
        case '÷':
        case '/':
        case '%':
            return 5;
        case '+':
        case '-':
            return 3;
        case '(':
            return 1;
    }
    return -1;
}

/* 연산자 순위 비교 */
function WhoPrecOp(op1, op2) {
    op1Prec = GetOpPrec(op1);
    op2Prec = GetOpPrec(op2);

    if (op1Prec > op2Prec)
        return 1;
    else if (op1Prec < op2Prec)
        return -1;
    else
        return 0;
}

GetOpPrec은 연산자들 연산 순위를 매긴 것이고 WhoPrecOP는 연산 순위를 기반으로 연산 순위를 비교한 것이다.

while (stack.length != 0) {
    convert.push(stack.pop());
}

그리고 마지막에 stack에 남은 요소들을 전부 convert에 저장한다.

 for (let i = 0; i < convert.length; i++) {
        let token = convert[i];

        if (!isNaN(token)) {
            stack.push(token);
        }

그리고 convert의 요소에서 피연산자는 stack에 저장을 한다.

        else {
            let op2 = Number(stack.pop());
            let op1 = Number(stack.pop());

            if (!op1) op1 = 0;
            
            switch (token) {
                case '+':
                    stack.push(op1 + op2);
                    break;
                case '-':
                    stack.push(op1 - op2);
                    break;
                case '×':
                case '*':
                    stack.push(op1 * op2);
                    break;
                case '÷':
                case '/':
                    stack.push(op1 / op2);
                    break;
                case '%':
                    stack.push(op1 * op2 / 100);
                    break;
            }
        }
    }

만약 연산자가 나오면 stack에 저장된 피연산자를 빼내서 해당 연산자에 맞게 계산을 진행한다. 그리고 연산 결과는 stack에 저장한다. 이것을 convert길이만큼 반복한다.

    document.getElementById("input").value = stack;

    history += stack;
    record(history)
}

그리고 연산 결과가 저장된 stack값을 input 값으로 표기한다. 그리고 history에도 stack의 값을 추가하고 record 함수를 실행하는데 이 함수는 계산 결과 및 과정을 기록하는 함수이다.

/* 계산 과정 및 결과 기록 */
function record(result) {
    let p = document.createElement("p");
    p.append(result);
    document.getElementById("record").prepend(p);
}

history값을 result로 가져와서 result에 p태그를 씌우고 기록이되어야하는 위치에 추가한다.

3. 느낀 점

윤성우의 열혈 자료구조 책을 보며 공부하는 도중에 스택을 이용한 후위 표기법에 대해 설명이 있는데 이것을 이용해서 비슷하게라도 웹으로 구현해보면 재미있을 것 같아서 구현해보았고, 추가적으로 소수점 계산을 할 수 있게 하였다. 다만 다중 괄호가 있는 연산을 불가능하고 간단한 연산들만 가능하다.

'컴퓨터 > 프로젝트' 카테고리의 다른 글