0

I've noticed weird behavior when using .toFixed(i). I was trying to round (38.174999 * 100) and got weird results

console.log(`i: ${i} = `, (38.174999 * 10 ** 2).toFixed(i))
i: 1 =  3817.5
i: 2 =  3817.50
i: 3 =  3817.500
i: 4 =  3817.4999
i: 5 =  3817.49990
i: 6 =  3817.499900
i: 7 =  3817.4999000
i: 8 =  3817.49990000
i: 9 =  3817.499900000
i: 10 =  3817.4999000000
i: 11 =  3817.49990000000
i: 12 =  3817.499900000000
i: 13 =  3817.4998999999998

from 13 onward it's similar. Does anyone know reason and already tried to check what is going one according to JS spec http://www.ecma-international.org/ecma-262/#sec-number.prototype.tofixed

For all those that are marking this question as 0.1 + 0.2 problem it's not the same. Question is why .toFixed(13) changes value in comparison to .toFixed(12)

piro
  • 176
  • 1
  • 11
  • 3
    This has nothing to do with `toFixed()`, this is inherent floating point precision. – connexo Jan 15 '19 at 12:15
  • 1
    @connexo This isn’t quite the classic `0.1 + 0.2` problem, though. `(38.174999 * 10 ** 2)` is indeed `3817.4999`, not `3817.4998999999998`. – Sebastian Simon Jan 15 '19 at 12:17
  • @Xufox, yes, it is indeed weird behaviour of `.toFixed`, because if you use primitive fixed positions rounding (`multiply n**10 -> round -> divide n**10`) you will get just `3817.4999`. – user28434'mstep Jan 15 '19 at 12:29
  • Even `(38.174999 * 10 ** 2).toString().padEnd(5 + i, "0")` would give you better result. – user28434'mstep Jan 15 '19 at 12:33
  • @Xufox it's not, 3817.4999.foFixed(13) shouldn't be `3817.4998999999998`. Main problem is why it's broken when i is more than 12? – piro Jan 15 '19 at 12:38
  • @piro I didn’t say anything about `toFixed`. – Sebastian Simon Jan 15 '19 at 12:40
  • 1
    @Xufox you've said that it's `3817.4999` and that correct but why `toFixed(13)` rounds it to `3817.4998999999998`. That's a problem – piro Jan 15 '19 at 12:42
  • 1
    I think, I can see why… `3817.4998999999998 === 3817.4999`. So, when _this_ number gets displayed, it is indeed displayed as `3817.4999`, even though its actual value is `3817.4998999999998`. So `toFixed` does work correctly. `3817.4999` is a lie, as it’s not representable as a IEEE-754 double-precision number. – Sebastian Simon Jan 15 '19 at 12:47
  • @Xufox i think you might be right, because if you increase size of initial number from `38.174999` to `389.174999`, `toFixed` starts to return different result from `12` not `13`. – piro Jan 15 '19 at 13:02
  • @Xufox, why `(38.174999 * 10 ** 2).toString().padEnd(5 + 13, "0")` gives you `"3817.4999000000000"` then? – user28434'mstep Jan 15 '19 at 13:03
  • 1
    I've found sth really interesting in V8 implementation https://github.com/v8/v8/blob/master/src/dtoa.h#L40-L47 – piro Jan 15 '19 at 13:08
  • 2
    The linked dupe gives all the information on why this happens,.. Basically `3817.4999` cannot be stored accurately using IEEE-754. If storage of decimal numbers is important, a 3rd party lib like `decimal.js` https://mikemcl.github.io/decimal.js/#sd maybe something you want to look into,. Another advantage is it works with numbers much bigger than 64bit can handle. – Keith Jan 15 '19 at 13:08
  • @user28434 Because `(38.174999 * 10 ** 2).toString()` yields exactly this shortened representation `"3817.4999"`. – Sebastian Simon Jan 15 '19 at 13:13

0 Answers0