# Locale-independent float print function

The objective was to write a locale-independent function that prints floating-point numbers (i.e. one that always uses . as a decimal point regardless of the locale) that was going to be used in a library that was meant to produce JSON. I was aiming for the following:

• Implementation simplicity
• Ability to be used on different sorts of streams (writing into a file, a string, etc)
• Reasonable corrrectness

Here's the main code:

#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>

typedef int (*printfn_t)(void *, const char *, ...);

#define print(...) if (printfn(stream, __VA_ARGS__) < 0) {\
return false;\
}

static const int PRECISION = 15;

static int count_digits(double n) {
return floor(log10(n) + 1);
}

bool print_number(
printfn_t printfn,
void *stream,
double number
) {
if (number == 0) {
print("0");

return true;
}

if (number < 0) {
print("-");

number = -number;
}

uint64_t integral = 0, decimal = 0;
int exponent = 0;

if (number > 9007199254740991) {
exponent = count_digits(number) - 1;

number = number / pow(10, exponent);
} else if (number < 1 / pow(10, PRECISION)) {
exponent = count_digits(number) - 1;

number = number * pow(10, -exponent);
}

integral = number;
decimal = round(fmod(number, 1) * pow(10, PRECISION));

print("%" PRId64, integral);

if (decimal != 0) {
print(".");

int decimal_length = count_digits(decimal);

for (int i = 0; i < PRECISION - decimal_length; i++) {
print("0");
}

print("%" PRId64, decimal);
}

if (exponent != 0) {
print("e%i", exponent);
}

return true;
}


And here's how you use it:

int main() {
double input = 0;

scanf("%lf", &input);

print_number(
(printfn_t) fprintf,
stdout,
input
);
}

• If it's acceptable to support only single-threaded applications, consider avoiding all the complexity by temporarily changing LC_NUMERIC to C locale (use setlocale(LC_NUMERIC, NULL) to obtain the current locale to revert back to). Apr 19 at 7:16

Implementation simplicity

As code is incorrect in too many cases, simplicity assessment is moot.

Ability to be used on different sorts of streams (writing into a file, a string, etc)

Code assumes it can pass a void * like a FILE *. Reasonable, but not specified by C.

Writing to a string deserves a size parameter.

Rather than attempt to use fprintf(), sprintf(), consider providing interface functions.

Reasonable corrrectness

corrrectness --> correctness

OP is brave. Quality conversion of a string to a double is hard.
To assess, I set up a test harness.

void test(double x) {
// Reference output
printf("R%.*e %20a\n", DBL_DECIMAL_DIG - 1, x, x);
// OP's output
printf("<");
print_number((printfn_t) fprintf, stdout, x);
puts(">\n");
}

int main(void) {
const double x[] = {1.0, 1.0 / 7, DBL_MIN, DBL_TRUE_MIN, DBL_MAX, INFINITY,
NAN, -DBL_MAX, -0.0, 1.0 + DBL_EPSILON, 1.0 - DBL_EPSILON};
size_t n = sizeof x / sizeof *x;
for (size_t i = 0; i < n; i++) {
test(x[i]);
}
}


Outright discrepancies included

R4.9406564584124654e-324            0x1p-1074
<0.-9223372036854775808e-324>                          ???

Rinf                  inf
<-9223372036854775808.-9223372036854775808e2147483647> ???

Rnan                  nan
<-9223372036854775808.-9223372036854775808>            ???

R-0.0000000000000000e+00              -0x0p+0
<0>                                                    Missing sign

R9.9999999999999978e-01 0x1.ffffffffffffep-1
<0.1000000000000000>                                   Off by 10x!


Non-finite

For NAN and infinity, consider a !isfinite(x) test and then handle those special cases.

Sign

For -0.0 correctness,

//if (number < 0) {
if (signbit(number)) {
print("-");
...


Precision

double deserves up to DBL_DECIMAL_DIG (e.g. 17) places of output to properly distinguish all double from other double. OP's precisions output wobbles with inconsistent significant precision output.

I adjusted OP's code to int PRECISION = DBL_DECIMAL_DIG;, then used select values like 1.0 - DBL_EPSILON (and many others) are not quite correct.

R9.9999999999999978e-01 0x1.ffffffffffffep-1
<0.99999999999999984>


As a reference, to print the exact value of a finite double. See Function to print a double - exactly

Numeric correctness.

Each *, / and some pow(10, ) inject 0.5 [ULP] or more error into the math and there is little code compensating for these accumulating errors. Without internal extended math, I see OP's approach as unable to achieve a high quality result.

Code assumes double precision < 64 bits. Reasonable, but not specified by C.

Recommendation

Test with the usual suspects as in the test harness, with values near powers of 10 and with various random values.