When you do multiplication, errors add up. Therefore the error will be a magnitude larger than the error of the fraction. But it is still usually a lot smaller than 0.1, and therefore it will usually not cause Math.round
to behave in an unexpected way. This piece of code is totally fine (as long as you don't use it with very large / very small numbers).
proof:
The maximum rounding error between a number x
and it's (imprecise) representation rd(x)
fullfills the formula | (x - rd(x)) / x | < 2 * E
, or in other words: The error grows linear to our number x
, and will always be smaller than |x| * 2E
(E
being the Machine Epsilon - the largest possible relative error).
For floating-point multiplication of a*b
, the result is a*b = a*b*(1 + k)
, whereas k < E
. Therefore if we combine the error of representation and multiplication we end up at:
rd(rd(a) * rd(b))
= (a + e1)(b + e2)(1 + e3)
Now assuming the worst case (that e1
is |x| * 2E
), it results in:
rd((a + |a| * 2E)(b + |b| * 2E))
= rd(a * b + a * |b| * 2E + b * |a| * 2E + |ab| * 2E)
= a * b + a * b * E + a * |b| * 2E + a * |b| * 3E + b * |a| * 2E + b * |a| * 3E + |ab| * 2E + |ab| * 3E
= a * b + |a * b| * (E + 2E + 3E + 2E + 3E + 2E + 3E)
= a * b + |a * b| * (15E)
That is, our relativ rounding error is 15E
at max. Given that our rounding error has to be smaller 0.1
for the rounding to work correctly, this results in:
0.1 > |a * b| * (15E)
0.1 / 15E > |a * b|
30023997515803 > |a * b|
Given that a = 100
, we get b < 300239975158
.
Note that this is a worst case estimation. I checked a few numbers above that border, and couldn't find anything off. The first calculation going wrong was above the maximum safe integer range, and then it's obvious that the result can't be right:
console.log(
Math.round(90071992547409.93 * 100) === 9007199254740993,
);
For a proof of the formulas used, see What Every Computer Scientist Should Know About Floating-Point Arithmetic.