I'm having an issue where when I print a double value it is inconsistently either rounding up or down. I am using the format:
printf("%.2lf", double);
what could be the possible issue here? thanks!
CodePudding user response:
Any decimal numeral in source code or input is converted to the binary format your C implementation uses for double
. This changes the value away from the xxx.xx5 value. When it is then printed using %.2lf
, it is the changed value that is rounded, not the original. There is no way to avoid this using only the value in a double
.
With additional information, such as known limits on how many decimal digits the original numerals had or what values they may take, it might be possible to reconstruct the original value and round that. However, such information is not present in the question.
CodePudding user response:
"fraction is exact at the 3rd decimal point"
As double
uses a binary internal representation, most decimal fractions like 0.1, 0.01, 0.001 are not possible to represent exactly. Instead of the exact code value of x.xx5
, a double
near that is used, perhaps a tad lower or higher.
To recover that exact at the 3rd decimal point, scale the double
by 1000.0 and round to nearest whole number.
double d1000 = round(d * 1000.0);
To print to 2 decimal places, round the d1000
to the nearest 10s, round and then divide by 100.0
double d100 = round(d1000 / 10.0) / 100.0;
printf("%.2lf", double);
In effect, code recovers by employing a double rounding.
Example:
#include <stdio.h>
#include <math.h>
void prt(double d) {
double d1000 = round(d * 1000.0);
double d100 = round(d1000 / 10.0) / 100.0;
printf("%.16e %.2lf\n", d, d100);
}
void prt3(double d) {
prt(nextafter(d, 0));
prt(d);
prt(nextafter(d, d*2));
}
int main() {
prt3(1203.505000);
prt3(1203.705000);
}
Output
1.2035049999999999e 03 1203.51
1.2035050000000001e 03 1203.51
1.2035050000000003e 03 1203.51
1.2037049999999997e 03 1203.71
1.2037049999999999e 03 1203.71
1.2037050000000002e 03 1203.71
CodePudding user response:
In a comment you wrote
the fraction is exact at the 3rd decimal point
Your original number might have been, but once it's converted to double
, which uses binary fractions internally, there's no such thing as a "third decimal point", and in fact decimal numbers like 0.001 and 0.005 simply cannot be represented exactly.
Here are some sample numbers, how they're represented as a double
, and how they print as "%.2lf
, that is, converted back to decimal and rounded to two places.
(Note that when I say "how they're represented as a double
", what I'm really showing you is the internal binary representation converted back to decimal again. It's also interesting to look at the actual internal representation, in binary or hexadecimal, but it's harder to display meaningfully and doesn't really bear on today's question, so I'm not going to bother.)
input value | internal double value |
printed "%.2lf" |
---|---|---|
1203.480 | 1203.4800000000000181899 |
1203.48 |
1203.485 | 1203.4849999999998999556 |
1203.48 |
1203.490 | 1203.4900000000000090949 |
1203.49 |
1203.495 | 1203.4949999999998908606 |
1203.49 |
1203.500 | 1203.5000000000000000000 |
1203.50 |
1203.505 | 1203.5050000000001091394 |
1203.51 |
1203.510 | 1203.5099999999999909051 |
1203.51 |
1203.515 | 1203.5150000000001000444 |
1203.52 |
1203.520 | 1203.5199999999999818101 |
1203.52 |
1203.525 | 1203.5250000000000909495 |
1203.53 |
1203.530 | 1203.5299999999999727152 |
1203.53 |
You can see that sometimes .xx5
is represented as .xx50000000001
and so rounds up as you expect, but sometimes it's represented as .xx49999999999
and so rounds down.
You asked "what could be the possible issue here", and that's what I've explained. Other answers contain suggestions on how you could adjust your code to work around the issue.