This is my first question on Code Review, so please let me know if I botched anything.
After working for quite a while on my CS50 calculator, it has finally reached the point where I am completely satisfied with how it works. However, since it is hosted on Google Sites, I'm slightly worried about security issues; I controversially chose to use eval
since I'm too lazy to evaluate it myself. I'm worried this may lead to Javascript hacking.
The way my code works is it has a bunch of buttons to allow you to put in numbers, symbols, and predefined functions. Then, it adds different symbols to the actual displayed text and the evaluated text. This is so non-programmers can understand it better, e.g. **
vs ^
. I also allow you to simply type in symbols, and I restrict it to buttonable ones. Finally, I evaluate what you've typed in using eval
. You also have the ability to clear and delete.
There is a link to a working CS50 Calculator in my profile, and I'll also put a link here. You can try it out if my explanation was too vague: https://sites.google.com/view/cs50-calculator/home
And, of course, below is the full code of CS50 calculator (note that I'm not putting it in a snippet since you can access it on Google Sites anyways):
<!DOCTYPE html>
<!--Code help received from thingEvery in Stack Overflow: https://stackoverflow.com/a/63079750/13736952-->
<html lang="en">
<head>
<title>Calculator</title>
<script>
function sqrt(x)
{
return Math.sqrt(x);
}
function abs(x)
{
return Math.abs(x);
}
function sin(x)
{
return Math.sin(x);
}
function cos(x)
{
return Math.cos(x);
}
function tan(x)
{
return Math.tan(x);
}
function arcsin(x)
{
return Math.asin(x);
}
function arccos(x)
{
return Math.acos(x);
}
function arctan(x)
{
return Math.atan(x);
}
function ln(x)
{
return Math.log(x);
}
function log(x)
{
return Math.log10(x)
}
function cbrt(x)
{
return Math.cbrt(x);
}
function exp(x)
{
return Math.exp(x);
}
function root(index, radicand)
{
return radicand**(1/index);
}
function logrtm(base, argument)
{
return Math.log(argument)/Math.log(base);
}
function dec(x)
{
return parseFloat(x);
}
function int(x)
{
return Math.round(x);
}
function min(stuff)
{
let items = []
if (arguments.length > 1)
{
for (var i = 0; i < arguments.length; i++) items[i] = arguments[i];
}
else if (!isNaN(stuff))
{
items = [stuff]
}
else
{
items = stuff;
}
return Math.min(...items);
}
function max(stuff)
{
let items = []
if (arguments.length > 1)
{
for (var i = 0; i < arguments.length; i++) items[i] = arguments[i];
}
else if (!isNaN(stuff))
{
items = [stuff]
}
else
{
items = stuff;
}
return Math.max(...items);
}
function sum(stuff)
{
let items = []
if (arguments.length > 1)
{
for (var i = 0; i < arguments.length; i++) items[i] = arguments[i];
}
else if (!isNaN(stuff))
{
items = [stuff]
}
else
{
items = stuff;
}
let return_value = 0;
for (let i = 0; i < items.length; i++)
{
return_value += parseFloat(items[i]);
}
return return_value;
}
function mean(stuff)
{
let items = [];
if (arguments.length > 1)
{
for (var i = 0; i < arguments.length; i++) items[i] = arguments[i];
}
else if (!isNaN(stuff))
{
items = [stuff]
}
else
{
items = stuff;
}
return sum(items) / items.length;
}
function median(stuff)
{
let items = [];
if (arguments.length > 1)
{
for (var i = 0; i < arguments.length; i++) items[i] = arguments[i];
}
else if (!isNaN(stuff))
{
items = [stuff]
}
else
{
items = stuff;
}
items.sort(function(a, b){return Number(a) - Number(b);});
var half = Math.floor(items.length / 2);
if (half % 2 === 1)
{
return items[half];
}
else
{
return mean([items[half], items[half-1]]);
}
}
function mode(stuff)
{
let items = [];
if (arguments.length > 1)
{
for (var i = 0; i < arguments.length; i++) items[i] = arguments[i];
}
else if (!isNaN(stuff))
{
items = [stuff]
}
else
{
items = stuff;
}
var mapping = {};
for (let i = 0; i < items.length; i++)
{
if (!mapping[items[i]])
{
mapping[items[i]] = 0;
}
mapping[items[i]] += 1;
}
var max_val = max(Object.values(mapping));
var return_string = "";
for (let item in mapping)
{
if (mapping[item] === max_val)
{
if (return_string.length != 0)
{
return_string += " ";
}
return_string += item;
}
}
return return_string;
}
function range(stuff)
{
let items = [];
if (arguments.length > 1)
{
for (var i = 0; i < arguments.length; i++) items[i] = arguments[i];
}
else if (!isNaN(stuff))
{
items = [stuff]
}
else
{
items = stuff;
}
var max_val = max(items);
var min_val = min(items);
return max_val - min_val;
}
function factorial(x)
{
let return_num = 1;
for (let i = 1; i < x + 1; i++)
{
return_num *= i;
}
return return_num;
}
function combination(x, y)
{
return factorial(x) / (factorial(y) * factorial(x - y));
}
function random(low, high)
{
let return_number = Math.random() * (high - low) + low;
return return_number;
}
function quad(a, b, c)
{
if ((b**2 - 4*a*c) > 0)
{
let var1 = (-b + Math.sqrt(b**2 - 4*a*c)) / (2*a);
let var2 = (-b - Math.sqrt(b**2 - 4*a*c)) / (2*a);
let return_string = var1.toString() + " " + var2.toString();
return return_string;
}
else if ((b**2 - 4*a*c) == 0)
{
return (-b / 2*a);
}
else if ((b**2 - 4*a*c) < 0)
{
return "Undefined";
}
}
var x = "x";
function setx(stuff)
{
x = stuff;
return 0;
}
var list = ["Empty"];
function setlist()
{
list = [];
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] == "number") {
list.push(arguments[i]);
}
}
if (list.length == 0) {
list = ["Empty"];
}
return 0;
}
function addtolist()
{
if (list.length == 1 && "Empty".localeCompare(list[0]) == 0) {
list = [];
}
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] == "number") {
list.push(arguments[i]);
}
}
if (list.length == 0) {
list = ["Empty"];
}
return 0;
}
var func = null;
function makefunction(stuff)
{
func = stuff;
return 0;
}
function f(y)
{
if (func !== null)
return eval(func);
else
return "Undefined";
}
var pi = Math.PI;
var e = Math.E;
var percent = 0.01;
</script>
<style>
table
{
border: 3px solid black; border-collapse: collapse;
}
.center
{
margin: auto;
}
.num
{
color: black;
}
.oper
{
color: black;
}
.convert
{
color: black;
}
.operalt
{
color: black;
}
.numalt
{
color: black;
}
.convertalt
{
color: black;
}
.everything
{
margin: auto;
border: 5px ridge midnightblue;
background: gainsboro;
height: 360px;
width: 400px;
}
.buttonsdiv
{
text-align: center;
border-width: 3px;
border-style: ridge double;
height: 30px;
}
button
{
background: bisque;
border: 1px solid coral;
transition-duration: 0.5s;
cursor: pointer;
}
button:hover
{
background: yellow;
border: 1px solid goldenrod;
}
.buttoncenter {
margin: 4px auto;
display:block;
}
</style>
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div style="margin: auto; border: 5px ridge midnightblue; background: gainsboro; height: 400px; width: 400px;">
<div style="text-align: center; font-weight: bold; color: black;" id="expression">0</div>
<div class="buttonsdiv" style="border-color: blue; width: 300px; margin: 0 12%;">
<div class="buttoncenter">
<button class="num" id="1">1</button>
<button class="num" id="2">2</button>
<button class="num" id="3">3</button>
<button class="num" id="4">4</button>
<button class="num" id="5">5</button>
<button class="num" id="6">6</button>
<button class="num" id="7">7</button>
<button class="num" id="8">8</button>
<button class="num" id="9">9</button>
<button class="num" id="0">0</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: black; width: 180px; margin: 0% 101%; background: gainsboro;">
<div class="buttoncenter">
<button id="enter">Enter</button>
<button id="clear">Clear</button>
<button id="delete">Delete</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: green; width: 135px; margin: -9% 12%;">
<div class="buttoncenter">
<button class="oper" id="+">+</button>
<button class="oper" id="-">–</button>
<button class="operalt" id="*" name="×">×</button>
<button class="oper" id="/">÷</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: darkblue; width: 159px; margin: 0 47.3%;">
<div class="buttoncenter">
<button class="operalt" id="**2" name="²">x²</button>
<button class="numalt" id="sqrt(" name="√(">√</button>
<button class="num" id="(">(</button>
<button class="num" id=")">)</button>
<button class="num" id="[">[</button>
<button class="num" id="]">]</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: chartreuse; width: 150px; margin: 0 12%;">
<div class="buttoncenter">
<button class="num" id="sin(">sin</button>
<button class="num" id="cos(">cos</button>
<button class="num" id="tan(">tan</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: cornflowerblue; width: 144px; margin: -9% 51%;">
<div class="buttoncenter">
<button class="operalt" id="**" name="^">^</button>
<button class="numalt" id="pi" name="π">π</button>
<button class="num" id="e">e</button>
<button class="oper" id=",">,</button>
<button class="oper" id=".">.</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: darkgreen; width: 159px; margin: 9% 12%;">
<div class="buttoncenter">
<button class="numalt" id="arcsin(" name="sin⁻¹(">sin⁻¹</button>
<button class="numalt" id="arccos(" name="cos⁻¹(">cos⁻¹</button>
<button class="numalt" id="arctan(" name="tan⁻¹(">tan⁻¹</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: aqua; width: 135px; margin: -18% 53.2%;">
<div class="buttoncenter">
<button class="num" id="ln(">ln</button>
<button class="num" id="exp(">exp</button>
<button class="num" id="log(">log</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: mediumseagreen; width: 300px; margin: 18% 12%;">
<div class="buttoncenter">
<button class="num" id="root(">root(index,radicand)</button>
<button class="num" id="logrtm(">logrtm(base,argument)</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: olive; width: 151px; margin: -18% 12%;">
<div class="buttoncenter">
<button class="operalt" id="*percent" name="%">%</button>
<button class="convert" id="dec(">=>dec</button>
<button class="convert" id="int(">=>int</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: royalblue; width: 143px; margin: 9% 51.2%;">
<div class="buttoncenter">
<button class="operalt" id="%" name="mod">mod</button>
<button class="num" id="min(">min</button>
<button class="num" id="max(">max</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: teal; width: 300px; margin: -9% 12%;">
<div class="buttoncenter">
<button class="num" id="mean(">mean</button>
<button class="num" id="median(">median</button>
<button class="num" id="mode(">mode</button>
<button class="num" id="range(">range</button>
<button class="num" id="sum(">sum</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: springgreen; width: 154px; margin: 9% 12%;">
<div class="buttoncenter">
<button class="numalt" id="factorial(" name="!(">!</button>
<button class="num" id="combination(">combination(x,y)</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: mediumblue; width: 140px; margin: -18% 52.1%;">
<div class="buttoncenter">
<button class="num" id="random(">random(low,high)</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: forestgreen; width: 140px; margin: 18% 12%;">
<div class="buttoncenter">
<button class="num" id="quad(">quad(a,b,c)</button>
<button class="convert" id="setx(">setx</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: dodgerblue; width: 154px; margin: -27% 48.5%;">
<div class="buttoncenter">
<button class="num" id="setlist(">setlist</button>
<button class="convert" id="addtolist(">addtolist</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: lawngreen; width: 200px; margin: 27% 12%;">
<div class="buttoncenter">
<button class="num" id='makefunction("'>makefunction("f(y)")</button>
<button class="num" id="f(">f(y)</button>
</div>
</div>
<div class="buttonsdiv" style="border-color: darkslateblue; width: 94px; margin: -36% 63.5%;">
<div class="buttoncenter">
<button class="oper" id='"'>"</button>
<button class="num" id='func'>func</button>
</div>
</div>
</div>
<div style="visibility: hidden;"id="math">0</div>
<script>
let expression = document.querySelector("#expression");
let math = document.querySelector("#math");
let previous_exs = [];
let previous_maths = [];
function enter()
{
let result = eval(math.textContent.toLowerCase());
if (String(result).indexOf(" ") > 0)
{
expression.innerHTML = result;
}
if ((result % 1 != 0) && (!isNaN(result)))
{
expression.innerHTML = eval(result.toFixed(8));
}
else
{
expression.innerHTML = result;
}
if (Object.is(result, NaN) || result === null)
{
expression.innerHTML = "Undefined";
}
math.innerHTML = expression.textContent;
}
function clear()
{
expression.innerHTML = 0;
math.innerHTML = 0;
}
function del()
{
if ((previous_exs.length != 0) && (previous_maths.length != 0))
{
expression.innerHTML = previous_exs[previous_exs.length - 1];
math.innerHTML = previous_maths[previous_maths.length - 1];
previous_exs.pop();
previous_maths.pop();
}
else
{
expression.innerHTML = 0;
math.innerHTML = 0;
}
}
function num(item)
{
previous_exs.push(expression.textContent);
previous_maths.push(math.textContent);
if ((Number(expression.innerHTML) === 0) && (expression.textContent.length === 1)) {
expression.innerHTML = item;
math.innerHTML = item;
} else {
expression.innerHTML += item;
math.innerHTML += item;
}
}
function oper(item)
{
previous_exs.push(expression.textContent);
previous_maths.push(math.textContent);
expression.innerHTML += item;
math.innerHTML += item;
}
function convert(item)
{
previous_exs.push(expression.textContent);
previous_maths.push(math.textContent);
expression.innerHTML = item + expression.textContent + ")";
math.innerHTML = item + math.textContent + ")";
}
function operalt(item1, item2)
{
previous_exs.push(expression.textContent);
previous_maths.push(math.textContent);
expression.innerHTML += item1;
math.innerHTML += item2;
}
function numalt(item1, item2)
{
previous_exs.push(expression.textContent);
previous_maths.push(math.textContent);
if ((Number(expression.innerHTML) === 0) && (expression.textContent.length === 1)) {
expression.innerHTML = item1;
math.innerHTML = item2;
} else {
expression.innerHTML += item1;
math.innerHTML += item2;
}
}
function convertalt(item1, item2)
{
previous_exs.push(expression.textContent);
previous_maths.push(math.textContent);
expression.innerHTML = item1 + expression.textContent + ")";
math.innerHTML = item2 + math.textContent + ")";
}
const numbers = document.querySelectorAll(".num");
for (let i = 0; i < numbers.length; i++) {
numbers[i].addEventListener("click", function() {
num(numbers[i].id);
});
}
const operations = document.querySelectorAll(".oper");
for (let i = 0; i < operations.length; i++) {
operations[i].addEventListener("click", function() {
oper(operations[i].id);
});
}
const conversion = document.querySelectorAll(".convert");
for (let i = 0; i < conversion.length; i++) {
conversion[i].addEventListener("click", function () {
convert(conversion[i].id);
});
}
const operalts = document.querySelectorAll(".operalt");
for (let i = 0; i < operalts.length; i++) {
operalts[i].addEventListener("click", function () {
operalt(operalts[i].name, operalts[i].id);
});
}
const numalts = document.querySelectorAll(".numalt");
for (let i = 0; i < numalts.length; i++) {
numalts[i].addEventListener("click", function () {
numalt(numalts[i].name, numalts[i].id);
});
}
const convertalts = document.querySelectorAll(".convertalt");
for (let i = 0; i < convertalts.length; i++) {
convertalts[i].addEventListener("click", function () {
convertalt(convertalts[i].name, convertalts[i].id);
});
}
document.querySelector("#enter").onclick = enter;
document.querySelector("#clear").onclick = clear;
document.querySelector("#delete").onclick = del;
document.addEventListener('keydown', function(){
var key = event.key;
if ("0123456789[]()".includes(key) || (key.toUpperCase() != key.toLowerCase() && key.length == 1)) {
if (key != "Space")
{
num(key);
}
}
});
document.addEventListener('keydown', function(){
var key = event.key;
if ('-,.+/"'.includes(key)) {
oper(key);
}
});
document.addEventListener("keydown", function(){
var key = event.key;
var symbols = {"*":"×", "^":"**", "%":"*percent", "!":"factorial"};
if ("^%".includes(key)) {
operalt(key, symbols[key]);
}
else if ("*".includes(key)) {
operalt(symbols[key], key);
}
else if ("!".includes(key)) {
numalt("!(", "factorial(")
}
});
document.addEventListener("keydown", function() {
var key = event.key;
if (key == "Enter" || key == "=")
{
enter();
}
if (key == "Backspace")
{
del();
}
if (key == "Delete")
{
clear();
}
});
</script>
</body>
</html>
SIDENOTE: I tried asking a similar question in Stack Overflow, but it got downvoted. Hopefully it goes better here...