Reputation: 439
I need to output float point numbers with two digits after the decimal point. In addition, I also need to round off the numbers. However, sometimes I don't get the results I need. Below is an example.
#include <iomanip>
#include <iostream>
using namespace std;
int main(){
cout << setprecision(2);
cout << fixed;
cout << (1.7/20) << endl;
cout << (1.1/20) << endl;
}
The results are:
0.08
0.06
Since 1.7/20=0.085 and 1.1/20=0.055. In theory I should get 0.09 and 0.06. I know it has something to do with the binary expression of floating point numbers. My questions is how can I get the right results when fixing the number of digits after the decimal point with rounding off?
EDIT: This is not a duplicate of another question. Using fesetround(FE_UPWARD)
will not solve the problem. fesetround(FE_UPWARD)
will round (1.0/30) to 0.04 while the correct results should be 0.03. In addition, fesetround(FE_TONEAREST)
doesn't help either. (1.7/20) still round to 0.08.
EDIT 2: Now I understand that this behavior might be due to the half-to-even rounding. But how can I avoid this? Namely, if the result is exact half, it should round up.
Upvotes: 3
Views: 904
Reputation: 3476
The issue is due to binary floating-point representation and floating-point constants in C. The fact is that 1.7 and 1.1 are not exactly representable in binary. The ISO C standard says (I suppose that this is similar in C++): "Floating constants are converted to internal format as if at translation-time." This means that the active rounding mode (set by fesetround
) will not have any influence at all for the constant (it may have an influence for the roundings that occur at run time).
The division by 20 will introduce another rounding error. Depending on the full code and compiler options, it may or may not be done at compile time, so that the active rounding mode may be ignored. In any case, if you expect 0.085 and 0.055 exactly, this is not possible because these values are not representable exactly in binary.
So, even if you have perfect code that rounds double
values on 2 decimal digits, this may not work as you want, because of the rounding errors that occurred before, and it is too late to recover the information in a way that works in all cases.
If you want to be able to handle "midpoint" values such as 0.085 exactly, you need to use a number system that can represent them exactly, such as decimal arithmetic (but you may still get rounding errors in other kinds of operations). You may also want to use integers scaled by a power of 10. There is no general answer because this really depends on the application, as any workaround will have drawbacks.
For more information, see all the general articles on floating point and Goldberg's article (PDF version).
Upvotes: 0
Reputation: 13974
You can define round_with_precision() method of your own, which would invoke tgmath.h provided round() method passing modified value, and then returning the value after dividing with same factor.
#include <tgmath.h>
double round_with_precision(double d, const size_t &prec)
{
d *= pow(10, prec);
return (std::round(d) / pow(10, prec));
}
int main(){
const size_t prec = 2;
cout << round_with_precision(1.7/20, prec) << endl; //prints 0.09
cout << round_with_precision(1.1/20, prec) << endl; //prints 0.06
}
Upvotes: 2
Reputation: 308520
Yes, you're right - it has to do with the representation in base 2, and the fact that sometimes the base 2 value will be higher than the base 10 number and sometimes it will be lower. But never by much!
If you want something that matches expectations more often, you can do two stage rounding. A double
is generally accurate to at least 15 digits (total, including those to the left of the decimal point). Your first rounding will leave you with a number that has more stability for the second phase of rounding. No rounding is going to match the results you would get in decimal 100%, but it's possible to get very close.
double round_2digits(double d)
{
double intermediate = floor(d * 100000000000000.0 + 0.5); // round to 14 digits
return floor(intermediate / 1000000000000.0 + 0.5) / 100.0;
}
See it in action.
nextafter
before rounding.
double round_2digits(double d)
{
return floor(100.0 * std::nextafter(d, std::numeric_limits<double>::max())) / 100.0;
}
Upvotes: 3