Building a simple calculator

Who wants to laugh at my unfinished calculator that doesn’t work at all, only displays number inputs? :joy: :sweat_smile:

I don’t have any specific questions–I’m working on this for class, and it’s important that I struggle through the assignment in order to learn. This week is my first exposure to JavaScript, though, so any thoughts from experienced developers are welcome!

You’ll noticed I’ve commented out some sections containing code that I’m setting aside for the moment but don’t want to completely discard yet.

// variables to track the current operator and stored value
let selectedOperator = '';
let storedValue = '';
let number = '';
let leftOperand = '';
let rightOperand = '';

// get references to the display and buttons

const display = document.getElementById('display');
const buttonOne = document.getElementById('one');
const buttonTwo = document.getElementById('two');
const buttonThree = document.getElementById('three');
const buttonFour = document.getElementById('four');
const buttonFive = document.getElementById('five');
const buttonSix = document.getElementById('six');
const buttonSeven = document.getElementById('seven');
const buttonEight = document.getElementById('eight');
const buttonNine = document.getElementById('nine');
const buttonZero = document.getElementById('zero');
const buttonPlus = document.getElementById('plus');
const buttonMinus = document.getElementById('minus');
const buttonMultiply = document.getElementById('multiply');
const buttonDivide = document.getElementById('divide');
const buttonEquals = document.getElementById('equals');
const buttonReset = document.getElementById('reset');
const buttonClear = document.getElementById('clear');

// number button event listeners

buttonOne.addEventListener('click', function () {
    handleNumberClick('1');
})
buttonTwo.addEventListener('click', function () {
    handleNumberClick('2');
})
buttonThree.addEventListener('click', function () {
    handleNumberClick('3');
})
buttonFour.addEventListener('click', function () {
    handleNumberClick('4');
})
buttonFive.addEventListener('click', function () {
    handleNumberClick('5');
})
buttonSix.addEventListener('click', function () {
    handleNumberClick('6');
})
buttonSeven.addEventListener('click', function () {
    handleNumberClick('7');
})
buttonEight.addEventListener('click', function () {
    handleNumberClick('8');
})
buttonNine.addEventListener('click', function () {
    handleNumberClick('9');
})
buttonZero.addEventListener('click', function () {
    handleNumberClick('0');
})

// operator button event listeners

buttonPlus.addEventListener('click', function () {
    selectedOperator = '+';
    handleOperator('+');
    return selectedOperator;
})
buttonMinus.addEventListener('click', function () {
    selectedOperator = '-';
    handleOperator('-');
    return selectedOperator;
})
buttonMultiply.addEventListener('click', function () {
    selectedOperator = '*';
    handleOperator('*');
    return selectedOperator;
})
buttonDivide.addEventListener('click', function () {
    selectedOperator = '/';
    handleOperator('/');
    return selectedOperator;
})

// handle number inputs

function handleNumberClick(number) {
    if (selectedOperator === '') {
        // if no operator is selected, append the number to the end of the stored number sequence
        leftOperand += number;
        display.value = leftOperand;
        storedValue = leftOperand;
    } else {
        // if an operator is selected, start a new number sequence
        rightOperand += number;
        display.value = rightOperand;
        storedValue = leftOperand + rightOperand;
    }
    return storedValue;
}

// handleOperator function

function handleOperator(operator) {
    // if no operator was already selected, append the operator to the end of the stored string
    if (selectedOperator === '') {
        display.value = leftOperand;
        leftOperand += operator;
        storedValue = leftOperand
    } else {
        // if an operator was already selected, perform the prior operation and display the result
        display.value = parseFloat(leftOperand + rightOperand);
        storedValue = display.value;
    }
}

/* function handleOperator(operator) {
    // get the current display and parse it as a number
    const currentValue = display.value;
    
    // check for '=' operator
    if (operator === '=') {
        selectedOperator = ''; // clear the selected operator
        return; // return without performing any additional calculation
    }
    
    // when a value is stored and an operator is selected, perform the corresponding calculation
    if (selectedOperator && storedValue !== '') {
        // perform calculation based on previous operator
        switch (selectedOperator) {
            case '+':
                storedValue += currentValue;
                break;
            case '-':
                storedValue -= currentValue;
                break;
            case '*':
                storedValue *= currentValue;
                break;
            case '/':
                if (currentValue !== 0) {
                    storedValue /= currentValue;
                } else {
                    // handle division by zero
                    clearCalculator();
                    display.value = 'Error';
                    storedValue = ''; // reset storedValue
                    selectedOperator = ''; // reset selectedOperator
                    return
                }
                break;
        }
        // display result
        display.value = storedValue;
    } else {
        // set current value as stored value if there is no selected operator or stored value
        storedValue = currentValue;
    }
    
    // update selected operator
    selectedOperator = operator;
} */

// equals button event listener

buttonEquals.addEventListener('click', function () {
    selectedOperator = '=';
    handleOperator('=');
    return selectedOperator;
})

// reset (C) event listener

buttonReset.addEventListener('click', function () {
    clearCalculator();
})

// clear entry (CE) event listener

buttonClear.addEventListener('click', function () {
    storedValue = ''; // clear storedValue
    display.value = ''; // clear display
})

// function to clear (reset) the calculator (C)

function clearCalculator() {
    currentInput = '';
    storedValue = '';
    selectedOperator = null;
    updateDisplay();
}

// function to clear entry (CE)

function clearEntry() {
    display.value = '';
}

// add function

/* function add() {
    // get current display and parse it as a number
    const currentValue = parseFloat(display.value);
    // perform addition, update display
    display.value = currentValue + parseFloat(display.value);
}

// subtract function

function subtract() {
    // get current display and parse it as a number
    const currentValue = parseFloat(display.value);
    // perform subtraction, update display
    display.value = currentValue - parseFloat(display.value);
}

// multiply function

function multiply() {
    // get current display and parse it as a number
    const currentValue = parseFloat(display.value);
    // perform subtraction, update display
    display.value = currentValue * parseFloat(display.value);
}

// divide function

function divide() {
    // get current display and parse it as a number
    const currentValue = parseFloat(display.value);
    // perform subtraction, update display
    display.value = currentValue / parseFloat(display.value);
} */

First thing I’d do is start looking at all the values you have stored, all the time.

let selectedOperator = '';
let storedValue = '';
let number = '';
let leftOperand = '';
let rightOperand = '';

const display = { value: '' }; // mostly because I didn't want to touch the code

const dumpState = () => console.log(JSON.stringify({ selectedOperator, storedValue, number, leftOperand, rightOperand, displayValue: display.value }));

function handleNumberClick(number) {
    console.log(`handleNumberClick(${number})`);
    if (selectedOperator === '') {
        // if no operator is selected, append the number to the end of the stored number sequence
        leftOperand += number;
        display.value = leftOperand;
        storedValue = leftOperand;
    } else {
        // if an operator is selected, start a new number sequence
        rightOperand += number;
        display.value = rightOperand;
        storedValue = leftOperand + rightOperand;
    }
    dumpState();
    // return storedValue; return where and for what reason?
}

function handleOperator(operator) {
    console.log(`handleOperator(${operator})`);
    // if no operator was already selected, append the operator to the end of the stored string
    if (selectedOperator === '') {
        display.value = leftOperand;
        leftOperand += operator;
        storedValue = leftOperand
    } else {
        // if an operator was already selected, perform the prior operation and display the result
        display.value = parseFloat(leftOperand + rightOperand);
        storedValue = display.value;
    }
    dumpState();
}

handleNumberClick('1');
handleNumberClick('2');
handleOperator('+');
handleNumberClick('3');
handleOperator('=');

Result:

handleNumberClick(1)
{"selectedOperator":"","storedValue":"1","number":"","leftOperand":"1","rightOperand":"","displayValue":"1"}
handleNumberClick(2)
{"selectedOperator":"","storedValue":"12","number":"","leftOperand":"12","rightOperand":"","displayValue":"12"}
handleOperator(+)
{"selectedOperator":"","storedValue":"12+","number":"","leftOperand":"12+","rightOperand":"","displayValue":"12"}
handleNumberClick(3)
{"selectedOperator":"","storedValue":"12+3","number":"","leftOperand":"12+3","rightOperand":"","displayValue":"12+3"}
handleOperator(=)
{"selectedOperator":"","storedValue":"12+3=","number":"","leftOperand":"12+3=","rightOperand":"","displayValue":"12+3"}

At a glance, selectedOperator, rightOperand and number never get touched. So, um, that parseFloat thingy isn’t going to do what’s it’s supposed to be doing. Also, your clearCalculator sets selectedOperator = null, which is simply a bug, as you default it to '' elsewhere.

I profoundly disagree with “if no operator was already selected, append the operator to the end of the stored string”. Feels awkward and confusing. The stored string up to the point is only digits. Don’t pollute it, convert it. If you want to show the operator at the end of the display, that’s a view problem.

I believe, though I’m not 100% certain, that you only need three moving parts:

let currentOperator = undefined;
let currentValue = 0;
let digits = '';

Running our little tester again:

handleNumberClick('1');
handleNumberClick('2');
handleOperator('+');
handleNumberClick('3');
handleOperator('=');

Should get something like:

handleNumberClick(1)
{"currentValue":0,"digits":"1"}
handleNumberClick(2)
{"currentValue":0,"digits":"12"}
handleOperator(+)
{"currentOperator":"+","currentValue":12,"digits":""}
handleNumberClick(3)
{"currentOperator":"+","currentValue":12,"digits":"3"}
handleOperator(=)
{"currentOperator":"=","currentValue":15,"digits":""}

Notice that currentValue is an actual number. When enter handleOperator, do a const nextValue = digits === '' ? 0 : parseInt(digits); and go from there.

Hope this helps. Good luck.

2 Likes

Thank you so much for this, I have been looking for a code like this, My client Quality Preferred Painting Servicesemphasized text wants to add a calculator on his page. Can this work?

My calculator isn’t finished yet–the assignment for which I am writing it is due this week–but @baavgai’s code might work.

This was more an answer to a particular person’s code.

There are innumerable ways to attack this problem. Indeed, it’s a pretty standard student problem.

If you’re not in the process of trying to learn to code, you’d be better off just grabbing some complete, polished, example. A quick google of js calculator yields all kinds of solutions.

Thank you, that was really helpful! The dumpState() function you included really helped me start to think deliberately through what my program is actually doing.

At this stage my code looks like this:

// variables to track the current operator, current value, and operating value

let currentOperator = undefined;
let currentValue = 0;
let operatingValue = '';

// get references to the display and buttons

const display = document.getElementById('display');
const buttonOne = document.getElementById('one');
const buttonTwo = document.getElementById('two');
const buttonThree = document.getElementById('three');
const buttonFour = document.getElementById('four');
const buttonFive = document.getElementById('five');
const buttonSix = document.getElementById('six');
const buttonSeven = document.getElementById('seven');
const buttonEight = document.getElementById('eight');
const buttonNine = document.getElementById('nine');
const buttonZero = document.getElementById('zero');
const buttonPlus = document.getElementById('plus');
const buttonMinus = document.getElementById('minus');
const buttonMultiply = document.getElementById('multiply');
const buttonDivide = document.getElementById('divide');
const buttonEquals = document.getElementById('equals');
const buttonReset = document.getElementById('reset');
const buttonClear = document.getElementById('clear');
display.value = 0;

// number button event listeners

buttonOne.addEventListener('click', function () {
    handleNumberClick('1');
})
buttonTwo.addEventListener('click', function () {
    handleNumberClick('2');
})
buttonThree.addEventListener('click', function () {
    handleNumberClick('3');
})
buttonFour.addEventListener('click', function () {
    handleNumberClick('4');
})
buttonFive.addEventListener('click', function () {
    handleNumberClick('5');
})
buttonSix.addEventListener('click', function () {
    handleNumberClick('6');
})
buttonSeven.addEventListener('click', function () {
    handleNumberClick('7');
})
buttonEight.addEventListener('click', function () {
    handleNumberClick('8');
})
buttonNine.addEventListener('click', function () {
    handleNumberClick('9');
})
buttonZero.addEventListener('click', function () {
    handleNumberClick('0');
})

// operator button event listeners

buttonPlus.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '+';
    return currentOperator;
    dumpState();
})
buttonMinus.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '-';
    return currentOperator;
    dumpState();
})
buttonMultiply.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '*';
    return currentOperator;
    dumpState();
})
buttonDivide.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '/';
    return currentOperator;
    dumpState();
})
buttonEquals.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '=';
    return currentOperator;
    dumpState();
})

const dumpState = () => console.log(JSON.stringify({ currentOperator, currentValue, operatingValue, displayValue: display.value }));

// handle number inputs

function handleNumberClick(number) {
    console.log(`handleNumberClick(${number})`);
    if (currentOperator === undefined) {
        // if no operator is selected, append the number to the end of currentValue
        operatingValue += number;
        display.value = operatingValue;
    }
    // if an operator (excluding '=') was selected, record the operating value
    else if (currentOperator !== '=' && currentOperator !== undefined) {
        if (operatingValue === '' && currentValue !== 0) {
            // append the number to the end of the operating value
            operatingValue += number;
            display.value = operatingValue;
        } else {
        }
    } else {
        // no operator selected or '=' was clicked, start a new operation
        operatingValue += number;
        display.value = operatingValue;
        currentOperator = undefined;
    }
    dumpState();
    // return storedValue; return where and for what reason?
}

function handleOperator(operator) {
    console.log(`handleOperator(${operator})`);
    // if no operator was already selected
    if (currentOperator === undefined) {
        currentValue = parseFloat(operatingValue);
        operatingValue = '';
        display.value = currentValue;
    } else if (currentOperator === '+') {
        currentValue = currentValue + parseFloat(operatingValue);
        operatingValue = '';
        display.value = currentValue;
    } else if (currentOperator === '-') {
        currentValue = currentValue / parseFloat(operatingValue);
        operatingValue = '';
        display.value = currentValue;
    } else if (currentOperator === '*' && operatingValue !== '') {
        currentValue = currentValue * parseFloat(operatingValue);
        operatingValue = '';
        display.value = currentValue;
    } else if (currentOperator === '/') {
        // check for division by zero
        if (operatingValue !== 0) {
            currentValue = currentValue / parseFloat(operatingValue);
            operatingValue = '';
            display.value = currentValue;
        } else {
            display.value = 'ERROR';
        }
    } else if (currentOperator !=='' && operatingValue === '') {
    return;
    }
    else {
        display.value = "INVALID OPERATION";
    }
    return currentValue;
    dumpState();
}

// reset (C) event listener

buttonReset.addEventListener('click', function () {
    clearCalculator();
})

// clear entry (CE) event listener

buttonClear.addEventListener('click', function () {
    storedValue = ''; // clear storedValue
    display.value = ''; // clear display
})

// function to clear (reset) the calculator (C)

function clearCalculator() {
    currentOperator = undefined;
    currentValue = 0;
    operatingValue = '';
    display.value = 0;
}

// function to clear entry (CE)

function clearEntry() {
    
    display.value = '';
}

I’m trying to deal with one problem at a time. The current problem I’m working through is that, when a user clicks, for example, ‘2’, then ‘+’, then ‘+’, then ‘2’, I want the program to understand this simply as ‘2 + 2’. I’m struggling to capture that situation logically in such a way as to distinguish and deal with it.

Thanks for your help so far. I’ll keep working.

One question I had about what you wrote: you said that you thought the program probably could be written with only three moving parts, but later in the same answer you referred to what would be a fourth moving part (‘nextValue’). I think I must be misunderstanding what you wrote. Could you clarify?

Update: No urgency–got my calculator to do everything it needed to do to pass the assignment. Thanks again!

The code I ended up with:

// variables to track the current operator, current value, and operating value

let currentOperator = undefined;
let currentValue = 0;
let operatingValue = '';

// get references to the display and buttons

const display = document.getElementById('display');
const buttonOne = document.getElementById('one');
const buttonTwo = document.getElementById('two');
const buttonThree = document.getElementById('three');
const buttonFour = document.getElementById('four');
const buttonFive = document.getElementById('five');
const buttonSix = document.getElementById('six');
const buttonSeven = document.getElementById('seven');
const buttonEight = document.getElementById('eight');
const buttonNine = document.getElementById('nine');
const buttonZero = document.getElementById('zero');
const buttonPlus = document.getElementById('plus');
const buttonMinus = document.getElementById('minus');
const buttonMultiply = document.getElementById('multiply');
const buttonDivide = document.getElementById('divide');
const buttonEquals = document.getElementById('equals');
const buttonReset = document.getElementById('reset');
const buttonClear = document.getElementById('clear');
display.value = 0;

// number button event listeners

buttonOne.addEventListener('click', function () {
    handleNumberClick('1');
})
buttonTwo.addEventListener('click', function () {
    handleNumberClick('2');
})
buttonThree.addEventListener('click', function () {
    handleNumberClick('3');
})
buttonFour.addEventListener('click', function () {
    handleNumberClick('4');
})
buttonFive.addEventListener('click', function () {
    handleNumberClick('5');
})
buttonSix.addEventListener('click', function () {
    handleNumberClick('6');
})
buttonSeven.addEventListener('click', function () {
    handleNumberClick('7');
})
buttonEight.addEventListener('click', function () {
    handleNumberClick('8');
})
buttonNine.addEventListener('click', function () {
    handleNumberClick('9');
})
buttonZero.addEventListener('click', function () {
    handleNumberClick('0');
})

// operator button event listeners

buttonPlus.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '+';
    dumpState();
})
buttonMinus.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '-';
    dumpState();
})
buttonMultiply.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '*';
    dumpState();
})
buttonDivide.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '/';
    dumpState();
})
buttonEquals.addEventListener('click', function () {
    handleOperator(currentOperator);
    currentOperator = '=';
    dumpState();
})

const dumpState = () => console.log(JSON.stringify({ currentOperator, currentValue, operatingValue, displayValue: display.value }));

// handle number inputs

function handleNumberClick(number) {
    console.log(`handleNumberClick(${number})`);
    if (currentOperator === undefined) {
        // if no operator is selected, append the digit to the end of operatingValue
        operatingValue += number;
        display.value = operatingValue;
    } else if (currentOperator === '=') {
        currentValue = 0;
        operatingValue = '';
        operatingValue += number;
    } else {
        operatingValue += number;
    }
    dumpState();
}

function handleOperator(operator) {
    console.log(`handleOperator(${operator})`);
    if (currentOperator === undefined) {
        currentValue = parseFloat(operatingValue);
        operatingValue = '';
        currentOperator = operator;
        display.value = currentValue;
    } else {
        if (operatingValue !== '') {
            if (currentOperator === '+') {
                currentValue = currentValue + parseFloat(operatingValue);
                operatingValue = '';
                display.value = currentValue;
            } else if (currentOperator === '-') {
                currentValue = currentValue - parseFloat(operatingValue);
                operatingValue = '';
                display.value = currentValue;
            } else if (currentOperator === '*' && operatingValue !== '') {
                currentValue = currentValue * parseFloat(operatingValue);
                operatingValue = '';
                display.value = currentValue;
            } else if (currentOperator === '/') {
                // check for division by zero
                if (operatingValue !== 0) {
                    currentValue = currentValue / parseFloat(operatingValue);
                    operatingValue = '';
                    display.value = currentValue;
                } else {
                    display.value = 'ERROR';
                }
            }
        }
    }
    dumpState();
}

// reset (C) event listener

buttonReset.addEventListener('click', function () {
    clearCalculator();
})

// clear entry (CE) event listener

buttonClear.addEventListener('click', function () {
    clearEntry();
})

// function to clear (reset) the calculator (C)

function clearCalculator() {
    currentOperator = undefined;
    currentValue = 0;
    operatingValue = '';
    display.value = 0;
}

// function to clear entry (CE)

function clearEntry() {
    operatingValue = '';
    display.value = '';
}
1 Like

what would be a fourth moving part (‘nextValue’)

The moving parts are the current state. The nextValue is a hint for determining that next state.

Perhaps something like:

function handleOperator(operator) {
    // store the number value, we're gonna use it
    const nextValue = operatingValue === '' ? 0 : parseInt(operatingValue);
    // we can zero it out now
    operatingValue = '';
    // now the choice
    if (currentOperator === undefined) {
        currentValue = nextValue;
        currentOperator = operator;
    } else if (currentOperator === '+') {
        currentValue = currentValue + nextValue;
        display.value = currentValue;

I’ll admit I played about with this a little.

Not sure if this is a great learning example, but this was my approach:

const initState = () => ({
    operator: undefined,
    value: 0,
    digits: "",
    errorMessage: undefined,
    stack: ""
});

const operations = {
    "+": (a, b) => a + b,
    "-": (a, b) => a - b,
    "/": (a, b) => a / b,
    "*": (a, b) => a * b,
};

const applyOperator = state => {
    const nextValue = state.digits === '' ? 0 : parseInt(state.digits);
    if (state.operator === undefined) {
        return { ...initState(), stack: state.stack, value: nextValue };
    } else {
        const value = operations[state.operator](state.value, nextValue);
        if (isFinite(value)) { // check for div by zero, whatever else
            return { ...initState(), stack: state.stack, value };
        } else {
            // chuck state, just offer error
            return { ...initState(), errorMessage: `invalid operation: ${state.value} ${state.operator} ${nextValue}` };
        }
    }
};

const nextState = (state, x) => {
    const stack = state.stack + x;
    const result = changes => ({ ...state, ...changes });
    if (typeof (x) !== "string" || x.length !== 1) {
        return result({ errorMessage: `${x} is invalid input` });
    } else if ("0123456789".includes(x)) {
        return result({ digits: state.digits + x, stack });
    } else if (x === "=") {
        return { ...applyOperator(state), stack };
    } else if (x in operations) {
        return { ...applyOperator(state), operator: x, stack };
    } else {
        return result({ errorMessage: `'${x}' unknown operation` });
    }
};

const test = expr => {
    expr.split('').reduce((state, x) => {
        const ns = nextState(state, x);
        console.log(x, ns);
        return ns;
    }, initState());
};
test("12 +3-4*6=");
2 Likes