Say I am rouding the number 1.20515
to 4 decimal places in IEEE-compliant languages (C, Java, etc.) using the default round-to-half-even rule, the result will be "1.2051" which is not even.
I think this is due to the fact that 1.20515
is slightly biased towards 1.2051
when stored in binary, so there isn't even a tie in binary space.
However, if the input 1.20515
is exact in decimals, isn't this kind of rounding actually wrong?
Edit:
What I really want to know is if I do not want to use exact decimal arithmetic (e.g. Java's BigDecimal
), would these binary rounding rules introduce bias in the work flow: exact decimal in string (6 d.p. max) -> parse to IEEE double -> round using IEEE rules to 4 d.p.
Edit 2:
The "exact decimal" input is generated by Java using BigDecimal
or String
that comes directly from a database. The formatting, unfortunately, has to be done in JavaScript, which lacks a lot of support for proper rounding (and I am looking into implementing some).
You're correct: 1.20515 isn't representable by IEEE754 binary64, so the decimal -> binary conversion will round to the nearest value which is 1.2051499999999999435118525070720352232456207275390625.
The IEEE754 standard doesn't actually have anything to say about rounding binary values to non-integer decimals (rounding to the nearest integer doesn't suffer from this problem), and so any such functionality is up to the language standard (if it chooses to define it). JavaScript
toFixed
clearly defines it as the exact mathematical value (i.e. 1.2051).(UPDATE: actually, the IEEE754 standard does specify how FP -> string conversions should be performed, see Stephen Canon's comment below).
However if you want correct rounding over the whole pipeline, you can instead do
which will work as long as
s
has fewer than 16 digits (i.e. the absolute value is less than 109).Why is this the case?
Math.round(parseFloat(s)*1e6)
is exact: this is because binary64 can correctly round-trip up to 15 decimal digits, and this is doing essentially the same thing by scaling to an integer value.1e2
will involve some rounding (since not all values are exactly representable), but importantly, it can (i) represent values with a fractional half exactly, and (ii) won't round any other values to a fractional half (since we still have fewer than 16 decimal digits).roundeven
implements ties-to-even rounding to the nearest integer. This implementation is valid for any value in the above range._.toFixed(2)
) will give the correct result.(thanks to bill.cn and Mark Dickinson for the corrections)