Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C compatible printf output for Java

I'd want to convert float/double to string, in Java and C, such that the outputs are both consistent and user friendly.

By "user friendly", I mean the string should be human readable and sound: a maximum number of significant digits, and some automatic switching to scientific notation when appropiate (the double could span all the valid range).

By "consistent" I mean that the strings should be exactly the same in Java and C (I'd tolerate some exceptions if they are really rare).

Why not use simply some printf format string, as "%.5g"? That works... almost. But sadly the meaning of the precision field is quite different in Java and C. Also, the switching from-to scientific notation is not very consistent, nor even the format itself (2 or 3 digits for the exponent...). And different C compilers sometimes produce different results.

Examples of differences for "%.5g"

double                  Java %.5g         gcc %.5g      tcc %.5g
1234.0                  1234.0            1234          1234 
123.45678               123.46            123.45678     123.46
0.000123456             0.00012346        0.00012346    0.00012346
0.000000000000123456    1.2346e-13        1.2346e-13    1.2346e-013

I can code a function in C or Java (or both), but I wonder if someone has already dealt with this. I'm not very concerned with performance, but yes with portability across C compilers.

like image 888
leonbloy Avatar asked Dec 12 '22 20:12

leonbloy


1 Answers

If you really want base-10 floating-point output, it's probably easiest to write a JNI wrapper for C's printf here. The Java folks decided they needed to do printf themselves. Apart from what you've already noticed about %g, they decided to change the rounding behaviour and truncate output in a curious way. To wit:

System.out.printf("%.5g\n", 1.03125);
System.out.printf("%.5g\n", 1.09375);
1.0313
1.0938

gcc correctly rounds to even:

printf("%.5g\n", 1.03125);
printf("%.5g\n", 1.09375);
1.0312
1.0938

Notice that 1.03125 and 1.09375 are exactly representable as doubles since 1/32 = 0.3125.

Java's printf %g format wrongly truncates its output:

double d = 1;
for (int i = 0; i < 1035; i++) d /= 2;
System.out.printf("%.20g\n%.20a\n", d, d);
2.7161546124360000000e-312
0x0.00080000000000000000p-1022

Here's the right answer:

double d = 1;
for (int i = 0; i < 1035; i++) d /= 2;
printf("%.20g\n%.20a\n", d, d);
2.7161546124355485633e-312
0x0.00080000000000000000p-1022

1.0e-200 is normal but not exactly representable. Java pretends not to notice:

System.out.printf("%.20g\n%.20a\n", 1.0e-200, 1.0e-200);
1.0000000000000000000e-200
0x1.87e92154ef7ac0000000p-665

Here's the right answer:

printf("%.20g\n%.20a\n", 1.0e-200, 1.0e-200);
9.999999999999999821e-201
0x1.87e92154ef7ac0000000p-665

So you've either got to live with bizarro rounding behaviour in your printf or you piggyback off gcc and glibc's work. I can't recommend trying to print out floating-point numbers by yourself. Or you can just use %a, which AFAIK works perfectly fine in Java.

like image 143
tmyklebu Avatar answered Jan 18 '23 22:01

tmyklebu