It is not uncommon that we have to deal with decimal numbers in programming - and we often have to round them off for display or other purposes in many circumstances.
In my current work situation, I have encountered one of such problems myself, and here is how I deal with this. Hope this helps people who are faced with similar problems.
I have to round off some decimal numbers to a designated decimal points.
For example, if I have to round off to
3 decimal points, that means:
0.1234 --> 0.123 1.2345 --> 1.235 (note the 5 in the end)
At a first glance, .toPrecision() seems to be the solution, but actually isn't:
Number(0.1234).toPrecision(3) --> 0.123 Number(1.2345).toPrecision(4) --> 1.234
You can see two problems here:
 it doesn't round
off the answer like I needed, but rather rounding it
down by simply removing the extra digits at the end.
 I need to know how many significant digits are there in the integer part in order to determine the precision to use.
So I continue the search. Then, I found this post.
The solution is elegant - multiply the number by a certain power of 10 (depending on the decimal points you want), then use Math.round() to round to the nearest integer. In the end, simply divide the number by the same power of 10 to get the correct answer.
If you look into its best answer, you might notice something interesting - there is a Number.EPSILON.
This is what I wanted to explain in this post.
First of all, I am not going into full detail into floating point arithmetic. If you really do want to go all-in, here is a post for your reference.
The simplest of them all is an integer. When doing calculations, it is in its binary format, for example:
13 can be represented as 1101 in binary because 1101 (in binary) = 1 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0 = 8 + 4 + 1 = 13
How about decimal number? They are stored similarly, but the powers of 2 used are negative powers.
0.875 can be represented as 0.111 in binary because 0.111 (in binary) = 1 * 2^-1 + 1 * 2^-2 + 1 * 2^-3 = 0.5 + 0.25 + 0.125 = 0.875
Now, you may see a problem with this system. Not all decimal numbers can be precisely represented in the binary format. And this is the reason why we have this weird result:
Coming back to the previous observation, why do we need the
Number.EPSILON ? This is because of the existence of some edge cases in our previous algorithm. They suggested the problem with the number
1.005 - it being rounded to
1 instead of
1.01 when rounding to 2 decimal points.
Here, we investigate an even more interesting number to understand why: assume we have to round this number
4.975 to 2 decimal points. Here's the algorithm:
1. First, we have 4.975. 2. We multiply it by 100 to get 497.5 3. We perform Math.round(497.5) to get 498 4. We divide it by 100 to get the answer 4.98
Why so? If you test step-by-step, you will see that:
You can see that
This is why the original post added
I can now safely say the the stackoverflow answer is WRONG! Haha! Right in your face!
Ok, joking aside, how do we deal with this problem now?
1. Starting with 4.975 again. 2. We multiply 1000 to 4.975 to get 4975, an integer. 3. We now divide it by 10 to get 497.5 for rounding. 4. We perform Math.round(497.5) to get 498. 5. We now divide it by 100 to get 4.98, our final answer.
Does this work? Yes.
Why? This is because in step 2, we convert
4.975 into a precisely represented integer
4975. When it was divided by
497.5 is now precisely represented because its decimal part
0.5 can be precisely represented in binary.
That's all for now! Hope you enjoy this post!
Convert the number into integer first before doing any operation or Math.round(). Refer to this post for the algorithm.