40

I'm having a big problem with number comparison in javascript.

The script accuses that the comparison "7 < 10" is false.

console.clear();

var min = parseFloat("5").toFixed(2);
var max = parseFloat("10").toFixed(2);
var value = parseFloat("7").toFixed(2);

console.log(min, max, value);

console.log(value > min); // OK.
console.log(value < max); // ---- false ??????

Anyone knows what is happing?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Alexandre Perez
  • 3,355
  • 3
  • 21
  • 21
  • 2
    When all else fails…[ECMA-262 §15.7.4.5](http://ecma-international.org/ecma-262/5.1/#sec-15.7.4.5) – RobG Feb 10 '14 at 23:51
  • 2
    Possible duplicate of [Javascript: Comparing two float values](http://stackoverflow.com/questions/3343623/javascript-comparing-two-float-values) – Sean the Bean Nov 13 '15 at 15:51

3 Answers3

65

As it turns out .toFixed() returns strings - Try adding parseFloat before comparing the values to see the result:

console.log(parseFloat(value) < parseFloat(max)); // ---- now true
prototype
  • 3,303
  • 2
  • 27
  • 42
  • I wouldn't do it if I were you. Try this to find out why ;) `console.log(parseFloat(6760/100*100) < parseFloat(6760));` Check my full answer below https://stackoverflow.com/a/55164784/1737158 – Lukas Liesis Mar 14 '19 at 14:15
  • 1
    is not working! my example: ```javascript 3 === 2.999999999999999999 => true parseFloat(3) === parseFloat(2.999999999999999999) => true ``` – Crisan Lucian Oct 28 '19 at 08:42
  • 1
    JavaScript is weird in comparing floating point numbers - i'd recommend either avoiding them completely (when possible) or using third party solution. Here is a good read - https://dev.to/alldanielscott/why-floating-point-numbers-are-so-weird-e03 – prototype Oct 28 '19 at 12:12
19

You should always round float numbers or you will get weird results on some cases.

Try this to see the issue:

console.log(parseFloat(6760 / 100 * 100));

With rounding, you will get correct results:

console.log(Math.round(parseFloat(value)*100000) < Math.round(parseFloat(max)*100000));

Or you can try using something like: http://mikemcl.github.io/decimal.js/

Floating-point numbers are not accurate numbers they are a close representation of the numbers.

It's actually pretty simple. When you have a base 10 system (like ours), it can only express fractions that use a prime factor of the base. The prime factors of 10 are 2 and 5. So 1/2, 1/4, 1/5, 1/8, and 1/10 can all be expressed cleanly because the denominators all use prime factors of 10. In contrast, 1/3, 1/6, and 1/7 are all repeating decimals because their denominators use a prime factor of 3 or 7. In binary (or base 2), the only prime factor is 2. So you can only express fractions cleanly which only contain 2 as a prime factor. In binary, 1/2, 1/4, 1/8 would all be expressed cleanly as decimals. While 1/5 or 1/10 would be repeating decimals. So 0.1 and 0.2 (1/10 and 1/5) while clean decimals in a base 10 system, are repeating decimals in the base 2 system the computer is operating in. When you do the math on these repeating decimals, you end up with leftovers which carry over when you convert the computer's base 2 (binary) number into a more human-readable base 10 number. Source


This is not JavaScript's issue. It happens because the computer really understands just 1 and 0.

If you want to dive deep into the topic, I suggest this as a starting point: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
Lukas Liesis
  • 24,652
  • 10
  • 111
  • 109
1

Here is an example of what Lukas Liesis suggested, note that this does work without the decimal spaces being used.

console.clear();

var min = parseFloat("5").toFixed(2);
var max = parseFloat("10").toFixed(2);
var value = parseFloat("7").toFixed(2);

console.log(min, max, value);
console.log(Math.round(min), Math.round(max), Math.round(value));

console.log(Math.round(value) > Math.round(min));
console.log(Math.round(value) < Math.round(max));

Now let's try to see how well it works with floating point precision.

console.clear();

var min = parseFloat("5.04").toFixed(2);
var max = parseFloat("5.06").toFixed(2);
var value = parseFloat("5.05").toFixed(2);

console.log(min, max, value);
console.log(Math.round(min), Math.round(max), Math.round(value));

console.log(Math.round(value) > Math.round(min));
console.log(Math.round(value) < Math.round(max));

As you can tell, it doesn't handle decimals very well.

Now finally, using the parseFloat method.

console.clear();

var min = parseFloat("5.04").toFixed(2);
var max = parseFloat("5.06").toFixed(2);
var value = parseFloat("5.05").toFixed(2);

console.log(min, max, value);
console.log(parseFloat(min), parseFloat(max), parseFloat(value));

console.log(parseFloat(value) > parseFloat(min));
console.log(parseFloat(value) < parseFloat(max));

This works with floating point precision as well, and if you werent going to use that precision, well, then you might as well just use integers instead, no reason to turn them into floats.

The reason why this works is that toFixed(2), makes JavaScript round the number to the second decimal place. Thus removing the noise.

This would also work for Lukas' example, but would still suggest reading Lukas' post as well since it does add context to why you should be a bit careful about using floats, since computers generally aren't the best at parsing them, as after the 15th significant digit it adds noise. (i.e. 1.99999999999999 instead of 2)

TL;DR: If you don't care about the decimal precision, replace the parseFloat(5).toFixed(2) with 5, since after using Math.round that's what it's going to be anyways.

If you do care about decimal precision, use parseFloat(), since toFixed() turns it into a string, thus removing the ability to compare properly.

By the way, I know this is a late reply, but I wanted to add this context for people that also have a similar issue.

I also want to point out that you don't even need to use parseFloat() when initializing the variables.

console.clear();

var min = (5.04).toFixed(2);
var max = (5.06).toFixed(2);
var value = (5.05).toFixed(2);

console.log(min, max, value);
console.log(parseFloat(min), parseFloat(max), parseFloat(value));

console.log(parseFloat(value) > parseFloat(min));
console.log(parseFloat(value) < parseFloat(max));

You might still want to surround the entire thing with parseFloat() to avoid having to keep writing it though.

console.clear();

var min = parseFloat((5.04).toFixed(2));
var max = parseFloat((5.06).toFixed(2));
var value = parseFloat((5.05).toFixed(2));

console.log(min, max, value);

console.log(value > min);
console.log(value < max);

And yes, you do need the parentheses around the number to use toFixed().

var value = 1.toFixed(2);
console.log(value);
LW001
  • 2,452
  • 6
  • 27
  • 36
Sir. ZyPA
  • 11
  • 4