What tricks does JS employ to show/caclulate floating point numbers

Viewed 135

Yes, I know all about IEEE floating point numbers.

For instance, there is no precise representation of the decimal value 0.1 in binary floating point maths.

This being the case, what tricks are being used to "round" the result of the expression:

console.log(0.1 * 10)

to be exactly 1 (i.e. (0.1 * 10) === 1 is true) when, if I make my own calculations using addition:

console.log([...Array(10)].reduce((a) => a + 0.1, 0))

the result is (on my machine) 0.9999999999999999.

How does Javascript (and, most likely other runtimes) fudge the multiplication to give the (intuitively) correct answer without falling into the same problem I did when doing this manually?

1 Answers
  1 23456789012345678901234              bit #
  1.10011001100110011001101 * 0.0001     decimal 0.1
x                      1010              decimal 10
---------------------------
         
   1111 1111111111111111111              carries                      
   0011.0011001100110011001101 * 0.0001  1.10011001100110011001101 * binary 10
+  1100.11001100110011001101   * 0.0001  1.10011001100110011001101 * binary 1000
------------------------------
  10000.0000000000000000000001 * 0.0001  = 1 *trailing 1 dropped per LGRS
  12345 6789012345678901234
                          LGRS           LSb Guard Round Sticky
                          0001           ==>round down

BTW, 0.999999... == 1 ("proof of this is in Knuth's "Art of Computer Programming" book). BUT, you are correct, adding the 10 numbers gives a different result, and it is not 1.000..., 0.999..., nor 0.9999999999999999 (all non-whole number floats have 5 as the last non-zero digit to the right of the decimal point), it is:

>>> print("%100.100f\n") % (0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1)
0.9999999999999998889776975374843459576368331909179687500000000000000000000000000000000000000000000000

It was suggested I explain the above sum, which is a good suggestion. I suspect the "proof" is buried in "What Every Computer Scientist Should Know About Floating-Point Arithmetic"(https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#932)--a "must read". So, I will leave the laborious manual verification as an exercise to the asker. To help, here is the truth table for LGRS:

"first" bit
v
v
v     "last" bit (once set to 1, it NEVER shifts out to 0)
v     v
v     v
L G R S  Round (G, R, and S bits are not in final result)
0 0 0 0  down (means leave L bit as is)
0 0 0 1  down
0 0 1 0  down
0 0 1 1  down
0 1 0 0  down
0 1 0 1  up (means add 1 to L bit)
0 1 1 0  up
0 1 1 1  up
1 0 0 0  down
1 0 0 1  down
1 0 1 0  down
1 0 1 1  down
1 1 0 0  up
1 1 0 1  up
1 1 1 0  up
1 1 1 1  up

(not a "proof") Since while adding S==0, the above shows that there are 5 downs vs 3 ups. Also, since the 1s density is about 50%, the expected results will likely traverse the whole of this 1/2 of the truth table. So, a loss of magnitude makes sense. When there is a sticky bit involved the odds even out, or may be stacked the other way.

Related