A problem with re-inventing double
to string conversion is the many corner cases.
Consider printing values near 99995.0 to 4 places
void foo(double value, int precision) {
if (value == 0.) {
printf("%g\n", 0.0);
}
int exponent = (int) floor(log10(fabs(value)));
double base = value / pow(10, exponent);
base /= 10.0;
int p = precision;
base = round(base * pow(10, p)) / pow(10, p);
exponent++;
printf("%.*fE%d\n", p, base, exponent);
bar(value, precision);
}
foo(99994.99, 4);
foo(99995.01, 4);
Prints
0.9999E5
1.0000E5
Rather than the hoped for
0.9999E5
0.1000E6
Rather than try to out-compute the library conversion of floating point to a string, simply post process the string to the desired format.
Below is C, yet OP is looking for C++, so take this as a guide.
void bar(double value, int precision) {
precision--;
char buf[400];
snprintf(buf, sizeof buf, "%.*e", precision, value);
if (isfinite(value)) {
char *e = strchr(buf, 'e');
char *first_digit = e - precision - 2;
int expo = atoi(e + 1) + 1;
printf("%.*s0.%c%.*sE%d\n", !isdigit((unsigned char )buf[0]), buf,
*first_digit, precision, first_digit + 2, expo);
} else {
printf("%s\n", buf);
}
}
bar(99994.99, 4);
bar(99995.01, 4);
Prints
0.9999E5
0.1000E6
"If I set precision to 4 it does not round and gives 0.1234E5 however it should have been 0.1235E5."
This is a result of the default rounding mode is "round to nearest, ties to even" and OP is hoping for "round to nearest, ties to away".
Code such as base = round(base * pow(10, p)) / pow(10, p);
may achieve OP's goal in select cases as the multiplication and division here can incur rounding due to imprecision, sometimes in the desired direction. This is not reliable across all double
to achieve "round to nearest, ties to away".