visit
In the previous article, we took a look at all the edge cases one can find in a simple operation: dividing two numbers. Now, let’s improve all those aspects of the application.
>> 0.3 / 0.1
2.9999999999999996
This could be solved by rounding to 0.01—which would make perfect sense for currency calculations. In the case of this application, I wanted to maintain support for other use cases as well. For example, consider the division of the numbers orders of magnitudes away from each other, such as 1/1000000
. Overly aggressive rounding would turn those results into 0.
An interesting solution provided by JavaScript is Number.prototype.toPrecision()
. It returns the numbers as a string, limiting the values to provided precision. So
0.00012345.toPrecision(4)
returns "0.0001234"
, and12345.toPrecision(4)
returns "1.235e+4"
The first result is perfect; the second is in scientific notation. To turn it back into a standard number, we can use parseFloat
again—the same function that we use for parsing user input. The final code to run the calculation is thus:
const resultValue = numeratorValue / denominatorValue;
result.innerHTML = parseFloat(resultValue.toPrecision(4)).toString();
Allowing an overly wide range of values is a sure way of generating many edge cases. For example, we have seen in the previous article that numbers above 90071
start to behave weirdly.
Luckily, most of the meaningful uses don’t require such a high number. In our app, we can limit the input values to ± 1 million. This input validation can be implemented with min
& max
arguments on the inputs:
<input
type="number"
id="denominator"
placeholder="denominator"
min="-1000000"
max="1000000"
/>
divide
function, so it can be reused in multiple event callbacks,keydown
events, andkeydown
callback, I divide only when the key pressed is Enter
function divide() {
const numeratorValue = parseFloat(numerator.value),
denominatorValue = parseFloat(denominator.value);
const resultValue = numeratorValue / denominatorValue;
result.innerHTML = resultValue;
}
body.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
divide();
}
});
equals.addEventListener("click", divide);
We use <input type=”number” />
, which comes with native input validation: when a user provides a non-number in the input, the field will look empty for us on the JS side. The validation state of the field is available to us as an :invalid
pseudo-class in CSS. We can use this to style the application:
input:invalid {
border-color: red;
}
To address this hitch, we need to set the step
attribute on inputs—either to a number to indicate a precision of the input, or to any
to allow all numbers. Updated code:
<input
type="number"
id="denominator"
placeholder="denominator"
min="-1000000"
max="1000000"
step="any"
/>
const numeratorValue = parseFloat(numerator.value),
denominatorValue = parseFloat(denominator.value);
let errors = [];
if (isNaN(numeratorValue)) {
errors.push("numerator");
}
if (isNaN(denominatorValue)) {
errors.push("denominator");
}
if (errors.length === 0) {
// … calculations
} else {
result.innerHTML = `Cannot parse ${errors.join(" and ")} as number.`;
}
So finally, the index.html
looks like this:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Divider App</title>
<link rel="shortcut icon" href="#" />
<style>
#equals {
margin: 8px 0;
}
input:invalid {
border-color: red;
}
</style>
</head>
<body>
<input
type="number"
id="numerator"
placeholder="numerator"
min="-1000000"
max="1000000"
step="any"
/>
<hr />
<input
type="number"
id="denominator"
placeholder="denominator"
min="-1000000"
max="1000000"
step="any"
/>
<br />
<button id="equals">=</button>
<div id="result"></div>
<script src="./main.js"></script>
</body>
</html>
And main.js
:
const body = document.querySelector("body"),
numerator = document.querySelector("#numerator"),
denominator = document.querySelector("#denominator"),
equals = document.querySelector("#equals"),
result = document.querySelector("#result");
function divide() {
const numeratorValue = parseFloat(numerator.value),
denominatorValue = parseFloat(denominator.value);
let errors = [];
if (isNaN(numeratorValue)) {
errors.push("numerator");
}
if (isNaN(denominatorValue)) {
errors.push("denominator");
}
if (errors.length === 0) {
const resultValue = numeratorValue / denominatorValue;
result.innerHTML = parseFloat(resultValue.toPrecision(4)).toString();
} else {
result.innerHTML = `Cannot parse ${errors.join(" and ")} as number.`;
}
}
body.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
divide();
}
});
equals.addEventListener("click", divide);
As you can see, there’s much more logic than in our first implementation. You can find the code .
Also published .