6

I known the common way is to multiply 10^n and then divide 10^n. round a float with one digit number But due to the double precision accuracy problem, I found the solution above doesn't fully work for my case:

0.965 * 0.9 = 0.8685

std::round(0.8685 * 1000) / 1000 = 0.869
// expects it to be 0.869
std::round(0.965 * 0.9 * 1000) / 1000 = 0.868

If I want to get 0.869 from std::round(0.965 * 0.9 * 1000) / 1000 directly, I have to change the statement to

std::round((0.965 * 0.9 + std::numeric_limits<double>::epsilon()) * 1000) / 1000 = 0.869

Is there a simpler way to do the rounding without add an epsilon for every calculation?

Edit: The problem is that, intuitively the value of std::round(0.965 * 0.9 * 1000) / 1000 shall be 0.869 (because 0.965 * 0.9 = 0.8685), but it actually give 0.868. I want to find common way get the accurate math value with 3 decimal precision.

qduyang
  • 353
  • 1
  • 8
  • It depends on the purpose of the rounding. If the purpose is printing the value out (say, to n significant figures or to n decimal places) then simply use appropriate format specifiers (or stream manipulators). If the purpose is something else, you'll need to explain it to get a useful answer - after all, simple values like `0.1` cannot be represented exactly in floating point, so rounding like you describe inherently introduces error which cannot be eliminated - techniques to deal with that depend on why you're worrying about rounding in the first place. – Peter Nov 16 '17 at 08:07
  • @Peter, The purpose is to round it to 3 decimals with the expected value and then convert it to a string (which is similar to printing). In my case, I expect the string (or printed) value to `0.869` instead of `0.868`. – qduyang Nov 16 '17 at 08:33
  • 1
    In that case, worry about formatting the printed or converted output, not about changing the value. – Peter Nov 16 '17 at 08:44
  • @Peter, formatting cannot do rounding.. – qduyang Nov 16 '17 at 09:02
  • 1
    The logic by which you would convert a string like "0.9649999" (readily obtained with simple format specifiers or I/O manipulators) to "0.965" is not exactly difficult. And doesn't require worrying about fiddling the floating point value from which the string is obtained. – Peter Nov 16 '17 at 09:28
  • @Peter is saying **don't round**. Keep the `double` value as-is, and *in the conversion to string* supply appropriate formatting instruction. – Caleth Nov 16 '17 at 09:52
  • @Peter, @Caleth, thanks for the explanation. My case is slightly different. The purpose shall be converting `0.868 499 999 999 999 938 72` to `0.869` with only 3 decimals. The calculation error occurs during `0.965 * 0.9`. – qduyang Nov 16 '17 at 10:17
  • What is the "expected value", why do you expect it, and what happens if you get a slightly different value? It seems to me that you are expecting the same value as you would get from a (decimal) back-of-the-envelope calculation. Which highly suggests that you should use a "proper" decimal type. As for the last question, if you are doing financial calculations, please be aware that most countries have legally binding rounding rules that must be minutely followed (and binary floating-point math is most certainly wrong here, it's either decimal float or decimal fixed-point math). – Arne Vogel Nov 16 '17 at 13:31

2 Answers2

9

This is actually a surprisingly non-trivial problem, unless your problem is merely concerned with formatting output, in which case you can use an appropriate printf-style formatter, or ostream manipulator.

Fundamentally, denary rounding using a binary type doesn't make much sense. Your specific problem is due to the closest IEEE754 double to 0.965 being

0.96499999999999996891375531049561686813831329345703125

Your first port of call is to study Is floating point math broken?. Hopefully that will convince you that adding an arbitrary "epsilon" merely shifts the problem to other inputs, and there is no numerical justification for your using std::numeric_limits<double>::epsilon() either.

std::round works perfectly, due in part that the cutoff point x.5 for integer x is a dyadic rational so can be represented exactly in binary floating point. But alas, you can't use that to round to an arbitrary decimal point.

If you're willing to live with the occasional bad result then the std::round(x * y) / y idiom that you're currently using is probably your best bet.

If you can't live with any spurious errors, then you probably need to use a decimal type and perform the rounding functions on that. See C++ decimal data types

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Thanks for your explanation. The function is to round a value and send it to a third party. Then it looks adding `epsilon` is mandatory for my case. – qduyang Nov 16 '17 at 08:28
0

You could make a new function specifically for that calculation:

#include <iostream>
#include <limits>
#include <cmath>

using namespace std;

double roundDec(double var) {
    var = round((var + std::numeric_limits<double>::epsilon()) * 1000) / 1000;

    return var;
}

int main() {

    double var = 0.8685;

    cout << roundDec(var);

    return 0;
}