Approximate round-to-even rounding #336
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Right now, NPF uses "round to nearest, ties to infinity".
printf should actually use the current rouding mode configured in the system.
See https://en.cppreference.com/w/c/numeric/fenv/FE_round
See also the standard, about printf:
Using the system rounding mode might be overkill for a "nano" library.
However, the most common rounding is "round to nearest, ties to even", slightly different from what NPF uses.
This PR changes NPF to use round-to-even.
This is the
FE_TONEARESTof the standard (androundTiesToEvenof IEEE 754), whereas NPF's current policy corresponds toFE_TONEARESTFROMZERO(androundTiesToAwayof IEEE 754).I'll let you decide whether the increase in code size is worth it.
Note that "even" refers to the least significant decimal digit that survives the rounding, ie 2.345000 rounded to 2 fractional digits is 2.34 because "4" is even, whereas 2.315000 is 2.32 because "1" is odd.
Also note that these examples are correct only with infinite precision. With float64, the numbers are actually:
2.345000 -> 2.345000000000000195399252334027551114559173583984375000000000
2.315000 -> 2.314999999999999946709294817992486059665679931640625000000000
So, the first one would round up because it is not actually a tie.
And the second one would round down, for the same reason.
That is, they would both round in the opposite direction as the one that would be used with infinite precision.
However, some numbers do indeed round in the infinite-precision way (those numbers whose full mantissa fits in a
double).For instance (rounding to the last-but-one digit):
0.5 -> rounds to 0
0.25 -> rounds to 0.2
0.75 -> rounds to 0.8
1.5 -> rounds to 2
1.25 -> rounds to 1.2
1.75 -> rounds to 1.8
Also note that NPF has inaccurate rounding. This is true especially if the integer used for the calculations has less bits than
double, but it also happens in some other cases, if we don't have enough additional bits (see the discussion in #325).As a result, perfect ties might be overestimated or understimated by NPF, resulting in a possibly wrong rounding. Also, non-ties might appear as perfect ties, again with possibly wrong results.
Anyway, this best effort does improve things, on average.
With 64-bit
double, anduint64_tasNANOPRINTF_CONVERSION_FLOAT_TYPE, all the following cases do round appropriately.The long expansions are not from NPF, they are just to see which ones are actually ties.
The part after "vs" is the current NPF rounding (only for the wrong ones)
This is just for %f.
If you find it worthwhile, something similar will be needed for %e and %g, working from #325, especially considering that the rounding, in those cases, can also happen at an arbitrary integral digit, not just a fractional one. I'll have to think about that, it seems we might need something quite convoluted.