Building a JavaScript Calculator
Posted on: July 15, 2023
For the past week, I have been learning and completing tasks on FreeCodeCamp. The projects were built using a mix of HTML, JavaScript, CSS, Bootstrap, SASS, React, Redux, and jQuery. In this blog I am going to talk about one of the projects JavaScript Calculator, and the three difficulties in this project that cost me some time to solve.
Difficulty 1: Removing Leading Zeros
As you can see in Fig.1, there are two rows in the display area above the buttons, the upper row is showing the whole input expression, and the lower one is displaying the current user input. The expression always starts with an empty value, whereas the current input starts with 0.
The expectation of the input row is: when inputting numbers, it should not show leading zeros; however if a decimal point is the first input, a zero should be prepended.
I have tried using if-else statement to perform different actions depending on whether the input is a number or a decimal point. Next I checked if the input state length is bigger than 1 and perform parseFloat()
to remove leading zeros. But this method is too complicated and thus would be hard to maintain.
Therefore after a few trials, my final solution is:
- First append the user input to the current value
- Then use regular expression to remove all leading 0s if the following characters are numbers instead of decimal point
Difficulty 2: Dealing with Decimal Points
Each number can only contain one decimal point. It is very easy to deal with this requirement in input state, as the calculator just does nothing if the input state already contains a decimal point. However it is difficult to prevent a second decimal point in the same number in expression state.
This is because if we use the same method as we used in input state state.includes(".")
to check if a decimal point already exists in expression state, it would also stop a decimal point to be added to another number.
I have also thought about updating expression state using input state as such: setExpressionState("1 + " + inputState)
, but this requires striping and appending inputState to expressionState everytime inputState updates. And since setState
is async, we would need to wait for inputState to update before updating expressionState, which is not ideal. It is better to keep them separated, but still update them like they sync.
The final solution is:
- First split the expression state by operators: + - x / to get an array of numbers
- Get the last value of the array, this would be the current input number
- Check if the current input number already has a decimal point, and does nothing if it has
Difficulty 3: Preventing 2 Consecutive Operators
User Story 13 states the newly input operator should replace the previous operator if two consecutive operators are entered.
The initial method is if these two requirements fulfilled: the previous input is an operator and the new input is not "-", then the previous operator would be removed and append the new operator. But this method allows appending multiple "-", and could only erase one previous character from the expression state, not all "-" are replaced by the new input operator.
I then tried using different regular expressions to remove all operators behind the latest number in expression state before adding the new input operator, but it did not give the best result.
Eventually I worked out it is easier to deal with array then regex in this case:
- Does nothing if the previous input and the new input are both "-", this prevents multiple "-".
- Split the expression state into an array of characters
- While the last character of the array is an operator, we pop it out of the array
- Stop popping until the last character is a number or a decimal point
- Then expression state is replaced by the joined array, and append the new operator