Building a calculator is one of the first challenges to take when learning JavaScript. There are different methods that we can follow different types of calculators. We'll be creating a simple calculator with the four primary operators +, -, * and / along with parenthesis and can handle decimal numbers. This tutorial covers three different methods of creating a simple calculator:
- Using the eval method
- Using for loops
- Using the shunting yard algorithm
Simple Introduction to HTML, CSS and JS:
- HTML is the markup language used to structure a webpage.
- CSS is the styling language used to design the layout and look of the elements of the webpage.
- JS is the scripting language used to define the behavior and working of the elements in the webpage.
The website contains separate tutorials for each of the three methods, and this compilation focuses on these interconnected methods. When implementing these three methods, the JavaScript code undergoes modifications. The HTML and CSS code remains consistent across all three methods:
HTML:
The idea is to create a table. The first row of the table should be the display. The next rows must contain buttons of numbers, clear, Back space, equals to and operators.
- On clicking numbers or operators, the value of the button must be displayed on the display.
- On clicking on C, the display has to become empty.
- On clicking B, the last element on the display must be deleted.
- On clicking =, everything on the display must be solved using eval and now the display has to show the result of the expression entered.
Hence, we need to write 4 functions in JavaScript for the functionalities of four types of buttons. You can style the calculator anyway you want using CSS. For simplicity, we aren't using many styling features.
- Create a new file with .html extension: calci.html
- In the head, we've given the title as calci. The first tag in body is center, to place the heading and the calculator in the middle of the web page.
- Then, we gave the main heading of the web page as "Simple Calculator".
- We constructed a table, such that: Every row consists of buttons to be pressed and on every button click, a function from js is called for the calculation. Buttons in the first row: 0, (, ), empty column, C, B. C is for clearing the display and B is for giving a back space. Buttons in the second row: 7, 8, 9, empty column, +, and -. Buttons in the third row: 6, 5, 4, empty column, *, and /. Buttons in the fourth row: 1, 2, 3, empty column, decimal point and =.
- We'll be using inline CSS and js in the head section with <style></style> and <script></script> tags.
- Every row consists of buttons to be pressed and on every button click, a function from js is called for the calculation.
- Buttons in the first row: 0, (, ), empty column, C, B. C is for clearing the display and B is for giving a back space.
- Buttons in the second row: 7, 8, 9, empty column, +, and -.
- Buttons in the third row: 6, 5, 4, empty column, *, and /.
- Buttons in the fourth row: 1, 2, 3, empty column, decimal point and =.
Here is the HTML code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calci</title>
<script type = text/javascript></script>
<style></style>
</head>
<body>
<center>
<h1>Simple Calculator</h1>
<table>
<tr><input id = "display"></tr>
<tr><td> <button onclick = fun(0)>0</button></td>
<td> <button onclick = fun("(")>(</button></td>
<td> <button onclick = fun(")")>)</button></td>
<td></td>
<td> <button onclick = C()>C</button></td>
<td> <button onclick = B()>B</button></td>
</tr>
<tr><td> <button onclick = fun(7)>7</button></td>
<td> <button onclick = fun(8)>8</button></td>
<td> <button onclick = fun(9)>9</button></td>
<td></td>
<td> <button onclick = fun("+")>+</button></td>
<td> <button onclick = fun("-")>-</button></td>
</tr>
<tr><td> <button onclick = fun(6)>6</button></td>
<td> <button onclick = fun(5)>5</button></td>
<td> <button onclick = fun(4)>4</button></td>
<td></td>
<td><button onclick = fun("*")>*</button></td>
<td><button onclick = fun("/")>/</button></td>
</tr>
<tr><td> <button onclick = fun(1)>1</button></td>
<td> <button onclick = fun(2)>2</button></td>
<td> <button onclick = fun(3)>3</button></td>
<td></td>
<td> <button onclick = fun(".")>.</button></td>
<td> <button onclick = res()>=</button></td>
</tr>
</table>
<div id = "footer">By: Jeevani Anumandla, Hyderabad</div></center>
</body>
</html>
Output:
Upon clicking these buttons, no action occurs since we have not provided any JavaScript code yet.
Now, it is time to design the web page using CSS:
<style>
table {
background-color: brown;
border: 1px solid black;
table-layout: fixed;
border: 1px solid black;
border-spacing: 10px;
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
border-radius: 10px;
}
button{
padding: 16px;
border-radius: 8px;
}
button:hover{
background-color: black;
color: white;
}
button:active{
transform: scale(0.98);
box-shadow:3px 2px 22px 1px rgba(0, 0, 0, 0.24);
}
#display{
width:290px;
height:40px;
margin: 10px 0;
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
border-radius: 10px;
}
#display:hover{
background-color: black;
color: white;
}
body{
background-color: black;
}
h1{
color: white;
}
body:hover{
background-color: floralwhite;
}
body:hover h1{
color: black;
}
#footer{
position: fixed;
padding: 7px 7px 7px 7px;
bottom: 0;
width: 100%;
height: 20px;
background: grey;
}
</style>
Output:
Below is a comprehensive list of CSS properties along with their corresponding explanations:
Table styling:
| Property | Explanation |
|---|---|
| table_layout: fixed | The layout of all the cells in the table remains fixed (Width of the columns) irrespective of the length of content inside. |
| border-spacing: 10px | Distance or space between the borders of cells. It applies only when border-collapse is set to separate. We can set both vertical and horizontal distances. |
| box-shadow | To attach a shadow to an element. We can add multiple shadows to a single element by separating them by a comma. |
| border-radius | To curve the edge of the element's corners. (To add rounded corners to an element). The greater the value, rounder the corner will be. We can give four values to four corners. |
| background-color | The color to the background of an element. We can give the name of the color or use hexadecimal codes. |
| border | One property for adding the width, style and color to the borders of an element. |
Button:
| Property | Explanation |
|---|---|
| padding | To create space between the element and the border around it. Using padding-top, padding-right, padding-bottom and padding-left, we can give spaces on each side of the element. |
| border-radius | To curve the edge of the element's corners. (To add rounded corners to an element). The greater the value, rounder the corner will be. We can give four values to four corners. |
| hover: background-color | Using element:hover, we can add a property to the element when user hovers (moves) the cursor on the element. Here, we are changing the background color. |
| color | Font color. |
| active: tranform | Using element:active, we can add a property to the element when user clicks on the element. Transform is used to add 2D effects to the elements. Here, we are using scale(). This function can resize the element and then back to the original size. We used this to get a button effect when pressed. |
| body: hover h1{} | When user hovers on the body, we can change the property of h1 using this syntax. |
Now, the main functionality lies in JavaScript which we will develop using the three methods mentioned above:
1. eval method:
The eval function takes a string expression as input, processes it to evaluate the result, and then outputs the result.
For example:
eval("4*3+2") returns 14
However, using eval is not recommended as it can lead to malicious attacks. As discussed above eval can accept an expression in any form even large strings. If someone-a hacker or some mischievous user gives it an infinite value, it keeps executing slowing down the whole server trying to evaluate the expression rather than identifying the attack.
- Using eval can be a security hazard . If the program accepts a user input and passes it to eval, a hacker can inject a malicious code as input that runs arbitrarily in the scope of the function leaking confidential credentials like login details, etc.
- We need to sanitize the input of the eval function or it is better to use some other function in the place of eval . After taking the input from the user, we'll give the expression as input to the function. Observe the HTML code, we've called four functions fun, C, B and res. fun(a): On clicking all the buttons of numbers and operators, we call fun with the value of the number or operator as argument. In the function, we need to print that value on the display input box, hence, we concatenate the argument to the value already in the input box. C: We'll simply reassign the value of the display input box to an empty string to clear the display. B: We'll get the whole string on the display and slice it deleting the last element and re-assign it to the display again. res: This function is activated on clicking =. We'll just get the whole expression on the display and pass it as an argument to eval and reassign the result to the display.
- After taking the input from the user, we'll give the expression as input to the function.
- Observe the HTML code, we've called four functions fun, C, B and res.
- fun(a): On clicking all the buttons of numbers and operators, we call fun with the value of the number or operator as argument. In the function, we need to print that value on the display input box, hence, we concatenate the argument to the value already in the input box.
- C: We'll simply reassign the value of the display input box to an empty string to clear the display.
- B: We'll get the whole string on the display and slice it deleting the last element and re-assign it to the display again.
- res: This function is activated on clicking =. We'll just get the whole expression on the display and pass it as an argument to eval and reassign the result to the display.
- On clicking all the buttons of numbers and operators, we call fun with the value of the number or operator as argument.
- In the function, we need to print that value on the display input box, hence, we concatenate the argument to the value already in the input box.
Inline JS Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calci</title>
<script type = text/javascript>
function fun(a)
{
document.getElementById("display").value+=a;
}
function C()
{
document.getElementById("display").value = "";
}
function B()
{
var y = document.getElementById("display").value;
document.getElementById("display").value = y.slice(0, y.length-1);
}
function res()
{
var a = document.getElementById("display").value;
document.getElementById("display").value=a+"="+eval(a);
}
</script>
2. Using for loops:
Within this approach, four loops are employed to manage the four fundamental operators. These loops are structured in accordance with the BODMAS rule to adhere to the sequence of assessment - division, multiplication, addition, and subtraction. A pair of arrays will be utilized - one containing the indices of operators and the other containing the operands within the given expression.
The primary functionality is contained within the res function. Initially, take a look at the following code snippet:
<script type = text/javascript>
function fun(a)
{
document.getElementById("display").value+=a;
}
function C()
{
document.getElementById("display").value = " ";
}
function B()
{
var y = document.getElementById("display").value;
document.getElementById("display").value = y.slice(0, y.length-1);
}
function res()
{
var a = document.getElementById("display").value;
var i, count=0;
const index = [];
const numbers = [];
for(i=0;i<a.length;i++)
{
if(a[i]=="+" || a[i]=="-" || a[i]=="*" || a[i]=="/")
{
count+=1;
index.push(i);
}
}
document.getElementById("p").innerHTML="Indices of Operators: "+index;
var j = 0;
var num="";
const res = [];
var k = 0;
for(i=0;i<a.length;i++)
{
if(i==0 && (a[0]=="-" || a[0]=="+"))
{
res[0]=0;
j+=1;
k+=1;
continue;
}
if(j==index.length)
{
num+=a[i];
if(i==a.length-1)
{
res[k]=parseFloat(num);
break;
}
}
else if(i==index[j])
{
j++;
res[k]=parseFloat(num);
num="";
k++;
}
else{
num+=a[i];
}
}
document.getElementById("p1").innerHTML="Operands:"+res;
for(i=0;i<index.length;i++)
{
var j = 0;
if(a[index[i]]=="/")
{
res[j] = res[j]/res[j+1];
res.splice(j+1, 1);
}
}
for(i=0;i<index.length;i++)
{
var j = 0;
if(a[index[i]]=="*")
{
res[j] = res[j]*res[j+1];
res.splice(j+1, 1);
}
}
for(i=0;i<index.length;i++)
{
var j = 0;
if(a[index[i]]=="+")
{
res[j] = res[j]+res[j+1];
res.splice(j+1, 1);
}
}
for(i=0;i<index.length;i++)
{
var j = 0;
if(a[index[i]]=="-")
{
res[j] = res[j]-res[j+1];
res.splice(j+1, 1);
}
}
document.getElementById("display").value=a+"="+res;
}
</script>
Output:
Mechanism:
- The expression typed on the display is stored in variable a .
- Now, we created two arrays index and numbers .
- Using a for loop, we traverse the expression to store the indices of operators into the array index and printed them in the paragraph defined in HTML.
- So, the array index consists of the indices of the operators in a.
- Now, we need to separate the expression which is in the form of a string into numbers using operators.
- We create another array res.
- Let us take an example to get it better: a = 8 + 9 - 8 * 23
- Now, the array index = [1, 3, 5]
- Suppose, the expression starts with a -, to handle that, we check if a[0] is - and if it is minus, we add a 0 to the expression at the start For example: -8-9 -> 0-8-9
- In the for loop, we traverse the expression to fill res with values or numbers in a using index.
- After getting the strings, we convert the values into floating numbers using parseFloat. We print that in the next paragraph.
- Now, according to the above example: a = 8 + 9 - 8 * 23 index = [1, 3, 5] res = [8, 9, 8, 23]
- See that the size of res = size of index - 1
- We need to follow the mathematical rule BODMAS to solve any expression
- If there is an expression like 3-25 3-10 = -7 should be the answer and not 15 = 5. Multiplication dominates subtraction
- BODMAS: Brackets, Of, Division, Multiplication, Addition and Subtraction
- There are no brackets in this calculator, so we just have to follow the four operators (Arithmetic - +, -, * and /)
- Here, we used four for loops.
- The idea is to traverse index and do the operations of operator at index[i] on res[j] and res[j+1]. To traverse index, we use i and j for traversing res.
- splice is used to manipulate res after solving the operations. It pulls all the values to the empty indices with every empty index.
- The first for loop does all the divisions, the second does the multiplications, third-> addition and the last one does the subtractions.
- In the above example:
a = 8 + 9 - 8 * 23
In the second loop:
index = [1, 3, 5]
res = [8, 9, 8, 23]
a[index[2]] = *
res[1] = res[2]res[3] = 823 = 184
res = [8, 9, 184]
In the third loop:
index = [1, 3, 5]
res = [8, 9, 184]
a[index[0]] = +
res[0] = res[0] + res[1] = 8 + 9 = 17
res = [17, 184]
In the fourth loop :
index = [1, 3, 5]
res = [17, 184]
a[res[1]] = 17 - 184 = -167
The variable "res" holds the value of -167.
Once the calculation is complete, we update the value displayed with the computed result.
Limitations:
- This code can't handle , consecutive operators and isn't very efficient in terms of using more number of loops. This is just one of the thoughts we get to make a calculator.
- We can extend the code into handling these. Observe that in the example we took, there is no / in the expression, but the first loop is just traversed without any use. Hence, this isn't a very efficient approach.
- We'll see the best approach in the next tutorial called the " Shunting yard algorithm" by Dijkstra.
3. Using Shunting Yard algorithm:
Typically, this algorithm is commonly employed for transforming an infix expression into postfix, which is also referred to as Reverse Polish Notation (RPN). In infix notation, an expression appears as a + b, whereas in postfix notation, it is represented as ab+. The process of evaluating a postfix expression is straightforward, and it is common practice for a computer to convert an infix expression into postfix using stacks to facilitate evaluating the result.
Instead of presenting the entire code simultaneously, let's divide it into parts. We'll begin with the section that handles displaying.
<script>
function fun(b)
{
document.getElementById("display").value+=b;
}
function C()
{
document.getElementById("display").value="";
}
function B()
{
var a = document.getElementById("display").value;
document.getElementById("display").value=a.slice(0, a.length-1);
}
function res()
{
var a = document.getElementById("display").value;
a = a.trim();
document.getElementById("display").value=fun1(a);
}
- fun(a): On clicking all the buttons of numbers and operators, we call fun with the value of the number or operator as argument. In the function, we need to print that value on the display input box, hence, we concatenate the argument to the value already in the input box.
- C: We'll simply reassign the value of the display input box to an empty string to clear the display.
- B: We'll get the whole string on the display and slice it deleting the last element and re-assign it to the display again.
- res: This function is activated on clicking =.
- On clicking all the buttons of numbers and operators, we call fun with the value of the number or operator as argument.
- In the function, we need to print that value on the display input box, hence, we concatenate the argument to the value already in the input box.
By utilizing the trim function, superfluous spaces are eliminated from the expression before passing it as an argument to another function called fun1.
fun1:
The function holds the operational logic or the backend of the calculator, processing the input expression.
Code:
function fun1(a)
{
var i=0;
const values = [];
const operators = [];
var opc=0, clc=0;
for(i=0;i<a.length;i++)
{
if(a[i]=="(")
{
opc+=1;
}
if(a[i]==")")
{
clc+=1;
}
}
if(opc!=clc)
{
return "Invalid paranthesis";
}
if(opc>0)
{
var op = a.indexOf("(");
var cl = a.lastIndexOf(")");
var x = a.slice(0, op);
var y = fun1(a.slice(op+1, cl));
var z = a.slice(cl+1, a.length);
var b = x + y + z;
return fun1(b);
}
if(opc==0)
{
for(var i = 0;i<a.length;i++)
{
if(i==0 && (a[0]=="+" || a[0]=="-"))
{
values.push(0);
}
if((a[i]=="+" || a[i]=="-"|| a[i]=="*" || a[i]=="/") && (a[i+1]=="*" || a[i+1]=="/" || a[i+1]=="+"))
{
return "Invalid statement";
}
if(a[i]=="+" || a[i]=="-" || a[i]=="*" || a[i]=="/")
{
if(operators.length==0 || (precedence(operators[operators.length-1])<precedence(a[i])))
{
operators.push(a[i]);
}
else if(precedence(operators[operators.length-1])>=precedence(a[i]))
{
var f, s;
f = parseFloat(values.pop());
s = parseFloat(values.pop());
if(operators[operators.length-1]=="+")
{
values.push(s+f);
}
if(operators[operators.length-1]=="-")
{
values.push(s-f);
}
if(operators[operators.length-1]=="*")
{
values.push(s*f);
}
if(operators[operators.length-1]=="/")
{
values.push(s/f);
}
operators.pop();
operators.push(a[i]);
}
}
if((a[i]=="+" || a[i]=="-"|| a[i]=="*" || a[i]=="/") && (a[i+1]=="-"))
{
var ch="";
var j;
for(j=i+1;j<a.length;j++)
{
if(a[j+1]=="+" || a[j+1]=="-" || a[j+1]=="*" || a[j+1]=="/")
{
ch+=a[j];
break;
}
ch+=a[j];
}
values.push(parseFloat(ch));
i+=ch.length;
}
else if(a[i]!="+" && a[i]!="-"&& a[i]!="*" && a[i]!="/")
{
var ch="";
var j;
for(j=i;j<a.length;j++)
{
if(a[j+1]=="+" || a[j+1]=="-" || a[j+1]=="*" || a[j+1]=="/")
{
ch+=a[j];
break;
}
ch+=a[j];
}
values.push(parseFloat(ch));
i+=ch.length-1;
}
}
}
while(operators.length!=0)
{
var f, s;
f = parseFloat(values.pop());
s = parseFloat(values.pop());
if(operators[operators.length-1]=="+")
{
values.push(s+f);
}
if(operators[operators.length-1]=="-")
{
values.push(s-f);
}
if(operators[operators.length-1]=="*")
{
values.push(s*f);
}
if(operators[operators.length-1]=="/")
{
values.push(s/f);
}
operators.pop();
}
return values;
}
function precedence(a)
{
if(a=="+")
{
return 1;
}
if(a=="-")
{
return 2;
}
if(a=="*")
{
return 3;
}
if(a=="/")
{
return 4;
}
}
</script>
Explanation:
- We declared two arrays (stacks), values and operators.
- opc and clc stand for opening braces and closing braces.
- In the next for loop, we've checked if the number of opening braces is equal to the number of closing braces. Else, it returns that the expression provided is invalid.
- According to BODMAS, we need to first solve the sub-expressions inside brackets, hence, we check if there are any brackets and if there are, we extract the sub-expression between the brackets and call fun1 again with the sub-expression as input.
- This recursion continues if there are any brackets in the expression.
- Now, coming to the actual solving part: We check if there is a preceding sign to the expression, if there is any, we push a 0 into values. Suppose, if expression is -8: 0-8 is still -8. Then, we checked if there are consecutive operators which return invalid expression again. If there is an operand, push it into the values stack Now, if there is an operator: If the operators stack is empty, push the operator into the stack If the precedence of the operator found is greater than the precedence of the operator on the top of the operators stack, push the operator into the stack If the precedence of the operator found is less than or equal to the operator on the top of the operators stack, pop the last operator from the operators stack and two operands from the values stack and perform the operation on the two operands and push the result into the values stack. Continue the process until the precedence of the operator found is greater than the precedence of the operator on the top of the stack.
- Now, we handled a situation where + and - can be consecutive. This usually occurs where there are brackets: 3 + (2 - 3) -> 3 + - 1 -> 2
- In that scenario, we get the operand along with the - sign, convert it into float and push it into values stack like a regular operand.
- Finally, if the operators stack isn't empty which means there are still a few operators and values left, pop out the operands from the values stack and apply the operator on the top of the operators stack and pop the operator from the operators stack.
- Continue the process until there are no operators and operands left in the operators and values stack.
- The function precedence is to manage the precedence rules as in BODMAS/ PEDMAS
- We check if there is a preceding sign to the expression, if there is any, we push a 0 into values. Suppose, if expression is -8: 0-8 is still -8.
- Then, we checked if there are consecutive operators which return invalid expression again.
- If there is an operand, push it into the values stack
- Now, if there is an operator: If the operators stack is empty, push the operator into the stack If the precedence of the operator found is greater than the precedence of the operator on the top of the operators stack, push the operator into the stack If the precedence of the operator found is less than or equal to the operator on the top of the operators stack, pop the last operator from the operators stack and two operands from the values stack and perform the operation on the two operands and push the result into the values stack. Continue the process until the precedence of the operator found is greater than the precedence of the operator on the top of the stack.
- If the operators stack is empty, push the operator into the stack
- If the precedence of the operator found is greater than the precedence of the operator on the top of the operators stack, push the operator into the stack
- If the precedence of the operator found is less than or equal to the operator on the top of the operators stack, pop the last operator from the operators stack and two operands from the values stack and perform the operation on the two operands and push the result into the values stack.
- Continue the process until the precedence of the operator found is greater than the precedence of the operator on the top of the stack.
Example:
Expression: 3 + (5 + 9 * 9)
- The expression between the parenthesis is passed again to the function: 5 is pushed into the values stack + is pushed into the operators stack 9 is pushed into the values stack The precedence of is greater than +, so, it is pushed into the values stack 9 is pushed into the values stack. Values: Operators: Pop two operands from values stack: 9 9 and apply the operator at the top of the operators stack: 9 9 = 81. Push 81 into the values stack: 81 + 5 = 86.
- Now, 86 is concatenated to the original string in the place of the expression within parenthesis
- Expression: 3 + 86
- 3 is pushed into the values stack.
- + is pushed into the operators stack
- 86 is pushed into the values stack
- 86 + 3 = 89 is the final result .
- 5 is pushed into the values stack
- + is pushed into the operators stack
- 9 is pushed into the values stack
- The precedence of * is greater than +, so, it is pushed into the values stack
- 9 is pushed into the values stack. Values: Operators:
- Pop two operands from values stack: 9 9 and apply the operator at the top of the operators stack: 9 * 9 = 81. Push 81 into the values stack:
- 81 + 5 = 86.
Output Screens: