1

Here is a situation where a round_to_2_digits() function is rounding down when we expected it to round up. This turned out to be the case where a number cannot be represented exactly in a double. I don't remember the exact value, but say this:

double value = 1.155;
double value_rounded = round_to_2_digits( value );

The value was the output of a function, and instead of being exactly 1.155 like the code above, it actually was returning something like 1.15499999999999999. So calling std::round() on it would result in 1.15 instead of 1.16 like we thought.

Questions:

I'm thinking it may be wise to add a tiny value in round_to_2_digits() prior to calling std::round().

  • Is this standard practice, or acceptable? For example, adding the 0.0005 to the value being rounded.
  • Is there a mathematical term for this kind of "fudge factor"?
    • EDIT: "epsilon" was the term I was looking for.
  • And since the function rounds to only 2 digits after the decimal point, should I be adding 0.001? 0.005? 0.0005?

The rounding function is quite simple:

double round_to_2_decimals( double value )
{
    value *= 100.0;
    value = std::round(value);
    value /= 100.0;
    return value;
}
Stéphane
  • 19,459
  • 24
  • 95
  • 136
  • 4
    1.154999 should round to 1.15. If you don't want the correctly rounded result, what do you want? Rounding can't compensate for accumulated errors in your calculation. – Alan Stokes Nov 06 '15 at 22:36
  • 1
    What if the actual number is 0.0004 away from being rounded up and you add 0.0005? Then the result is wrong. There's no good way to do this – Kevin Nov 06 '15 at 22:37
  • 1
    There will always be some error when rounding. Whatever you do, there will be a *threshold*, so that anything that is too close to the "threshold" will be rounded either way, somehow randomly. I suggest that you "accept" that fact and re-think the problem. – A.S.H Nov 06 '15 at 22:41
  • I thought perhaps when dealing with values of 1/100 granularity, it could be shown mathematically that the result of adding 1/10000 or some other small value is insignificant to the result, yet can help compensate for limitations of representing non-integer values. – Stéphane Nov 06 '15 at 22:48
  • 1.154999 should actually round to 1.16. It gives me 1.16, live example [here](http://ideone.com/LhcHOC) which compiles using GCC 5.1 – ccoder83 Nov 06 '15 at 23:26
  • @ccoder83 how does that make sense? Unless the 1.15499999999999999 value is stored as 1.155 due to the imperfect nature of double? – Stéphane Nov 06 '15 at 23:30
  • Ah, my bad, I see what you mean. Hmm, I'm not sure how the C++ standard library algorithm works, however, when you actually use `std::setprecision(20)` in `std::cout`, the result is `1.15999999999999992006`, so it seems the `round` function is adding `0.5` at some point, presumably to deal with [tie-breaks](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero), and then a further rounding takes place when using `std::cout`. Also, look at the end of this [article](http://www.cplusplus.com/articles/1UCRko23/) on rounding algorithms. – ccoder83 Nov 07 '15 at 00:56
  • Also, if you want actual algorithms to see what's going on under the hood, check the NewLib sources by following [this](http://stackoverflow.com/questions/4572556/concise-way-to-implement-round-in-c/4572877#4572877) – ccoder83 Nov 07 '15 at 00:58
  • 1
    If you care about decimal rounding specificially, you should use a decimal format (either fixed point or floating point), and not a binary format. There are libraries available in various places, or you cal roll your own -- fixed point is pretty trivial. – Chris Dodd Nov 07 '15 at 01:25

2 Answers2

2

Step one is admitting that double may not be the right data type for your application. :-) Consider using a fixed-point type, or multiplying all of your values by 100 (or 1000, or whatever), and working with integer values.

You can't actually guarantee that adding any particular small epsilon won't give you the wrong results in some other situation. What if your value actually was 1.54999999..., and you rounded it up? Then you'd have the wrong value the other way.

The problem with your suggested solution is that at the end of it, you're still going to have a binary fraction that's not necessarily equal to the decimal value you're trying to represent. The only way to fix this is to use a representation that can exactly represent the values you want to use.

Mark Bessey
  • 19,598
  • 4
  • 47
  • 69
  • Yep, screwing things up even more won't magically patch everything. – Deduplicator Nov 07 '15 at 01:19
  • I don't get to pick the data type that 3rd party software and libraries use. Double used by the database, and the library, and it makes sense. In our case, we take these doubles and we only care to 2 decimal places. I was just looking for a valid way to round these doubles. Thank you for using the term "epsilon", that was one of the words I was looking for. – Stéphane Nov 07 '15 at 20:26
  • The thing I was trying to get across, but possibly failing, is that you can't actually round to 2 places with floating point. Some (most) two-digit decimals aren't exactly-representable in floating-point format. You'll need to do rounding on output/display. – Mark Bessey Nov 07 '15 at 22:45
0

This question doesn't make a lot of sense. POSIX mandates std::round rounds half away from zero. So the result should in fact be 116 not 115. In order to actually replicate your behavior, I had to use a function that pays attention to rounding mode:

std::fesetround(FE_DOWNWARD);
std::cout << std::setprecision(20) << std::rint(1.155 * 100.0);

This was tested on GCC 5.2.0 and Clang 3.7.0.

  • 1
    The problem is some numbers cannot be represented perfectly as doubles. I didn't recall the exact example I ran into this afternoon, but instead of 1.55, try using 1.15499999999999999 as described in the question above. – Stéphane Nov 06 '15 at 22:57
  • The C++ Standard is not determined by POSIX. – user515430 Nov 06 '15 at 22:58
  • @user515430 C++ does in fact incorporate POSIX. Either way, the C++ standard says th esame thing. – user5535385 Nov 06 '15 at 23:17
  • *POSIX mandates std::round rounds half away from zero.* First, please define *half away from zero* **precisely**. – Andrew Henle Nov 06 '15 at 23:22
  • Since when does C++ incorporate POSIX? They reserve a namespace for it and try not to break it, but aside from that... – Deduplicator Nov 07 '15 at 01:17