Math.round() behaves the same as C’s familiar
round with one key difference: it rounds halfways (“is biased”) towards positive infinity. Here is its spec in ES 5.1. It suggests an implementation too:
The value of Math.round(x) is the same as the value of Math.floor(x+0.5)...
Unfortunately, the implementation in the spec does not correctly implement the spec.
One bug is that adding 0.5 can result in precision loss, so that a value less than .5 may round up to 1. Mac user? Try it yourself (Safari 11.0.3): One engine attempted to patch it by just checking for .5: However this fails on the other end: when x is large enough that fractional values can no longer be represented, x + 0.5 rounds up to x + 1, so JSRounding a large integer like Math.pow(2, 52) would actually increment it.
What's a correct implementation? SpiderMonkey checks on the high end, and exploits the loss of precision on the low end: fish's attempt just checks high and low: which produces surprisingly pleasant assembly, due to the compiler's fabs() and copysign() intrinsics.
Update: Steve Canon finds a floor-based approach, which surely must be optimal or close to it:
The ES6 spec sheepishly no longer suggests an implementation, it just disavows one:
Math.round(x) may also differ from the value of Math.floor(x+0.5) because of internal rounding when computing x+0.5...