Divider App: Improving edge cases
Tags: webdev, testing, codequality, beginners
Published: 2023-03-15 08:47:10

In the [previous article](https://how-to.dev/divider-app-how-many-edge-cases-can-have-one-operation), 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.

## Deciding on trade-offs

Often, the challenge in programming is choosing one of many imperfect solutions. The choice depends on the use cases we want to support in our application. In mathematics, dividing two numbers always gives the same results. In programming, we need to work around the limitations of computers and the data structures. Optimal rounding and the precision we choose depends on the use case. What makes the solution good enough in the context of money would introduce unnecessary errors in scientific calculations. In short: for many questions, there is no perfect solution— just different approaches that fit better or worse into a specific context.

### Rounding

In the previews article, we discussed a number representation issue that causes JS to calculate

```JS
>> 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"`, and
* `12345.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:

```JS
const resultValue = numeratorValue / denominatorValue;

result.innerHTML = parseFloat(resultValue.toPrecision(4)).toString();
```

### Input limitations

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 `9007199254740991` 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:

```HTML
<input
  type="number"
  id="denominator"
  placeholder="denominator"
  min="-1000000"
  max="1000000"
/>
```

## User experience improvements

Once the bugs are resolved, let’s improve the user experience (UX). There are many small details that impact an application’s smoothness and ease of use. Often, the issues are not visible until you have a working interface and you can see how you or the users try to use it. This was the case in our first improvement:

### Support enter

When I was testing the app, intuitively, I tried doing the calculation by pressing enter. Because the calculation was done upon a “click” event, pressing enter had no effect. To add support for the key, I had to change a few things in the application:
* I defined `divide` function, so it can be reused in multiple event callbacks,
* I added an event callback for `keydown` events, and
* in the `keydown` callback, I divide only when the key pressed is `Enter`

Here’s the relevant part of the code after those changes:

```JS
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);
```

### Show input validity

Another important part of UX is providing necessary feedback to the user as quickly as possible. 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:

```CSS
input:invalid {
  border-color: red;
}
```

The results look like:



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8fd2ndpjcukm7pvwg3k.png)

### Allow fraction inputs

Another small issue that appeared in testing is that by default, number validation expects numbers to be integers. For example:



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpqvhrfresktybztuq8l.png)

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:

```HTML
<input
  type="number"
  id="denominator"
  placeholder="denominator"
  min="-1000000"
  max="1000000"
  step="any"
/>
```

### Show error messages

As a final improvement of this iteration of the app, let’s show some error messages when inputs cannot be processed. To cover all the possible cases, which are as follows,
* nominator is valid, denominator is corrupt,
* nominator is corrupt, denominator is valid,
* both are corrupt, and
* both are valid,

I needed some slightly complicated code:

```JS
  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.`;
  }
```

## Complete code

So finally, the `index.html` looks like this:

```HTML
<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`:

```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 [here](https://github.com/how-to-js/divider-app/tree/improved-ui). 

## Want to learn more?

We’ll continue on our journey to get a simple project to a market-ready level of quality. If you are interested in getting updates on this, or other topics I cover on this blog, [sign up here](https://how-to-dev.ck.page/a5c69742db).