0

I'm learning JavaScript and I have a page that displays the result of dividing two user-inputted numbers.

I didn't like that (1 / 3) would fill the whole result box with 3.333333333333..., so I set the result with .toFixed(4). But this has the result of making (4 / 2) return 2.0000 instead of just 2.

So, in order to fix that I check if (answer % 1 == 0), in which case it displays the answer normally, then checks (answer % 0.1 == 0), which displays answer.toFixed(1), and so on, with the final else being .toFixed(4).

This works as intended for some numbers but not for others, and I don't see why or what's going wrong in the cases it doesn't work.

Examples:

  • (2 / 5) returns 0.4. This satisfies the second condition, (answer % 0.1 == 0), and returns answer.toFixed(1), displaying 0.4. Great.

  • (2 / 10) also works and displays 0.2, and something like (2 / 7) correctly gets pushed to the else and displays 0.2857.

  • (2 / 8) returns 0.25, then incorrectly displays 0.2500

  • (2 / 20) correctly returns and displays 0.1

  • (9 / 6) returns 1.5, then incorrectly displays 1.5000

  • (2 / 4) returns 0.5, but displays 0.5000. It failed all the conditions and jumps to the else sentence, so answer.toFixed(4) is used.

Why does JavaScript consider (0.4 % 0.1 == 0) to be true, but (0.5 % 0.1 == 0) to be false?

This is my current code:

let num1 = parseFloat($("calcInput1").value);
let num2 = parseFloat($("calcInput2").value);
answer = num1 / num2;

if (answer % 1 == 0) {
    document.getElementById("calcResult").value = answer;
    alert("answer % 1 == 0");
} else if (answer % 0.1 == 0) {
    document.getElementById("calcResult").value = answer.toFixed(1);
    alert("answer % 0.1 == 0");
} else if (answer % 0.01 == 0) {
    document.getElementById("calcResult").value = answer.toFixed(2);
    alert("answer % 0.01 == 0");
} else if (answer % 0.001 == 0) {
    document.getElementById("calcResult").value = answer.toFixed(3);
    alert("else");
} else {
    document.getElementById("calcResult").value = answer.toFixed(4);
}  
Shidersz
  • 16,846
  • 2
  • 23
  • 48
Zachary
  • 11
  • 1
  • Because it's Javascript? Or maybe because Javascript uses double-precision floating point? This question is very difficult to read; do you think maybe you could clean it up a bit? – Robert Harvey Jun 01 '19 at 16:06
  • It isn't because it is JavaScript @Robert, that is a bit naive. This Java code will produce the same result: `System.out.println((0.25 % 0.01));` It is simply a well understood floating point mathematics dilemma. ` – Randy Casburn Jun 01 '19 at 16:14
  • @barmar - It's not clear to me that that's what the OP's talking about. In particular, the question *""* isn't significantly impacted by floating point imprecision. – T.J. Crowder Jun 01 '19 at 16:14
  • `0.4 % 0.1 == 0` is true because `0.4 % 0.1` is `0`. `0.5 % 0.1 == 0` is false because `0.5 % 0.1` is `0.09999999999999998` (very near `0.1`), which isn't `== 0`, so...false. Why do you find these `== 0` results surprising? It's also probably worth noting that `%` isn't modulus, it's [remainder](https://tc39.github.io/ecma262/#sec-applying-the-mod-operator), though I *think* that's a distinction that doesn't come into the positive values above... – T.J. Crowder Jun 01 '19 at 16:15
  • @RandyCasburn: I was not entirely serious. – Robert Harvey Jun 01 '19 at 16:15
  • @Zachary221334 Because `0.2` and `0.1` can't be represented exactly in floating point, so roundoff errors are introduced when you calculate the modulus. – Barmar Jun 01 '19 at 16:17
  • By analogy, `0.666666 % 0.333333 == 0`, but `1.000000 % 0.333333 == 0.000001`. – Barmar Jun 01 '19 at 16:19
  • How about `answer.toFixed(4).replace(/0+$/, '')`? Or even `String(Math.round(1e4 * answer) / 1e4)` – that’s weirdly reliable. – Ry- Jun 01 '19 at 16:24
  • So when doing math 0.5 is treated as 0.49999999, even though when I populate a textbox with that variable it has no problem displaying it as 0.5... – Zachary Jun 01 '19 at 16:45

1 Answers1

1

I believe, that what you need to output can be achieved using:

var answer = +(num1 / num2).toFixed(4);

Casting the string generated with toFixed() back to a number can help to avoid the redundant zeros at the end. Also, you don't need to use the modulus operator at all and do not need to face the problems related to arithmetics precision with floating point.

Note I have use the unary plus to cast the string back to number, as on the MDN says:

Unary plus is the fastest and preferred way of converting something into a number.

Example:

var button = document.getElementById("calc");
var in1 = document.getElementById("calcInput1");
var in2 = document.getElementById("calcInput2");
var out = document.getElementById("answer");

button.addEventListener("click", function()
{
    var num1 = in1.value, num2 = in2.value;
    var answer = +(num1 / num2).toFixed(4);
    out.value = answer;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<input type="text" id="calcInput1">
<input type="text" id="calcInput2">
<button type="button" id="calc">Calc</button>
<input type="text" id="answer">
Community
  • 1
  • 1
Shidersz
  • 16,846
  • 2
  • 23
  • 48