3

Number 6.35 can't be accurately represented:

alert( 6.35.toFixed(20) ); // 6.34999999999999964473

But why 6.35 * 10 == 63.5 is true?

6.35 is not accurate, 10 is accurate, and 63.5 is accurate. I can't understand how (inaccurate * accurate) equal accurate.

Bicheng
  • 687
  • 8
  • 20
  • javascript Numbers have a precision of 16 or 17 digits - you've asked for 21 (including the `6` ... perhaps [this question](https://stackoverflow.com/questions/588004/is-floating-point-math-broken/588014#588014) will help – Bravo Nov 18 '19 at 04:49
  • 2
    The rounding system is designed to minimize rounding error, not to guarantee it will always affect comparisons. Your result just means that 10 times the closest number to 6.35, rounded to the nearest Number, is 63.5. – Patricia Shanahan Nov 18 '19 at 04:56

2 Answers2

3

This is an error of logic: the last operation 6.35 * 10.0 is innacurate rather than accurate.
It's just that it might happen that several consecutive "rounding error" annihilate, as it might also happen that they cumulate.

The nearest double to 635/100 is 635/100 - 1/2,814,749,767,106,560
Or if you prefer: 635/100 - 1/(10 * 2^48)
So an accurate *10 operation should answer 635/10 - 1/(2^48).
But this quantity is not representable as double precision (see below)...
So the last operation is innacurate.

The two neighbours are 63.5 (which is exactly 635/10) and its predecessor 635/10 - 1/(2^47).
An interesting case of exact tie: the exact quantity is at same distance of the two representable double neighbours, The default rounding mode is round to nearest, tie to even, so the FPU will choose the double with even significand, that is 635/10.

Is this luck or is it a nice property of IEEE 754 arithmetic?
If I evaluate this snippet in Squeak/Pharo Smalltalk (which have exact fraction and comparison of exact arithmetic values):

(1 to: 10000) count: [:x | (x/10.0) = (x/10) and: [(x/100.0) ~= (x/100)]].

I get 1600 cases where the x/10 is exactly representable as double, while x/100 is not.

If I select those 1600 cases, and verify if rounding error annihilates:

((1 to: 10000) select: [:x | (x/10.0) = (x/10) and: [(x/100.0) ~= (x/100)]])
    count: [:x | (x/100.0*10) = (x/10)]

I count 1600 cases out of the 1600 for which the error annihilate, so it is a nice property of IEEE754 arithmetic. But this is still luck.

If I retry by dividing by 1000.0 then multiply by 100, I get a false answer to this question:

((1 to: 10000) select: [:x | (x/10.0) = (x/10) and: [(x/1000.0) ~= (x/1000)]])
    allSatisfy: [:x | (x/1000.0*100) = (x/10)]

The answer is true for 1649 cases out of the 1920, which is already a good score.

aka.nice
  • 9,100
  • 1
  • 28
  • 40
0

Javascript uses IEEE 754 floating-point standard in which all the numbers can't be represented accurately.

Numbers are represented as multiples of powers of 2, including negative powers of two, the numbers whose denominator is not the power of 2 cannot be represented accurately.

For the same reason, we get 0.1 + 0.2 == 0.3 //false.

This is the most famous side effect of binary floating-point numbers which remains true for all the languages which use IEEE 754 format to represent numbers (not just Javascript).

Hence there are some cases in which you need to be more careful, especially when dealing with fractional decimal values.

The most commonly accepted practice is to use a tiny rounding error while making comparisons. This value is known as machine epsilon which is 2 ^ 52.

As of ES6, Number.EPSILON is predefined with this tolerance value.

function numbersCloseEnoughToEqual(n1,n2) {
  return Math.abs( n1 - n2 ) < Number.EPSILON;
}

var a = 6.35.toFixed(20);
var b = 6.35;

console.log(numbersCloseEnoughToEqual( a, b ));// true
Akshay Bande
  • 2,491
  • 2
  • 12
  • 29
  • This does not answer the question that was asked. – Eric Postpischil Nov 18 '19 at 10:51
  • I tried to explain how floating-point numbers work and what javascript has to minimize the side effects because of the IEEE 754 standard`(Number. EPSILON)`. I tried to mention what should the user do to avoid side effects while dealing with **fractional decimal values**. This is what the user's intention were behind asking **I can't understand how (inaccurate * accurate) equal accurate.** – Akshay Bande Nov 18 '19 at 11:41
  • Sure, you tried to explain how floating-point numbers work and what JavaScript has to minimize the undesired effects of floating-point arithmetic. However, that does not answer the question of how multiplying `6.35` and `10` yields exactly 63.5 even though `6.35` is not exactly 6.35. Regardless of what you tried to do in this answer, this answer does not answer the question that was asked. – Eric Postpischil Nov 18 '19 at 11:43
  • Additionally: (a) “Numbers are represented as whole number's power of 2” is incorrect. That may be largely a language issue; “Numbers are represented as multiples of powers of two, including negative powers of two” would be correct. (b) Recommending comparing with a tolerance is bad advice in general. (c) Recommending the tolerance be the absolute value `Number.EPSILON` is wrong. The errors that occur in results computed with floating-point results vary from 0 to infinity and can also be NaN, so no single tolerance can serve. – Eric Postpischil Nov 18 '19 at 11:47
  • For point `(a)` I have updated my answer, for point `(b)` yes you are definitely correct on that **no single tolerance can serve**. But here I tried to compare two floating values one for which can be the result of the operation and other can be expected for example `0.1 + 0.3` will turn out to be the different result but expected is 0.3` so that you can get it correct using `numbersCloseEnoughToEqual`. – Akshay Bande Nov 18 '19 at 12:01