C compatible printf output for Java

I would like to convert float / double to string in Java and C so that the outputs are consistent and user friendly .

By "user-friendly", I mean that the line should be human-readable and sound: the maximum number of significant digits and some automatic switching to scientific notation when used (double can cover the entire allowable range).

By "sequential" I mean that strings must be exactly the same in Java and C (I would allow some exceptions if they are really rare).

Why not just use a printf format printf like "%.5g" ? It works ... almost. But, unfortunately, the value of the accuracy field is quite different in Java and C. In addition, switching from scientific notation is not very consistent and even the format itself (2 or 3 digits for the exponent ...). And different C compilers sometimes give different results.

Difference Examples 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 the function in C or Java (or both), but I wonder if someone has already dealt with this. I'm not very interested in performance, but yes with portability on C. compilers

+4
source share
3 answers

Well, I ended up coding my own functions. Tested with gcc and tcc over the entire double range, gives exactly the same result (except for very small very small values, less than 1E-319)

I publish it if someone finds this useful.

Java:

  /** * Returns a double with an adhoc formatting, compatible with its C counterpart * * If the absolute value is not too small or too big (thresholdLow-thresholdHigh) * the floating format is used, elsewhere the scientific. * In addition * - trailing zeros in fractional part are removed * - if the value (or mantisa) is integer, a trailing .0 is always included * - the exponent in sci notation is two or three digits * - positive and negative zero returns "0.0" * - special vals: "NaN" "Infinite" "-Infinite" * * Remember to set Locale.setDefault(Locale.US) in your program. * * @param v double * @param formatFloat floating point format, suggested: "%.5f" * @param formatSci scientific format, must use lowercase 'e' : "%.5e" * @param thresholdLow * @param thresholdHigh * @return formatted string */ public static String sprintfDouble(double v, String formatFloat, String formatSci, double thresholdLow, double thresholdHigh) { if(v==0.0) return "0.0"; //dont care about negative zero if(Double.isInfinite(v) || Double.isNaN(v)) return String.format(formatFloat,v); boolean neg = false; if (v < 0) { v = -v; neg = true; } String e = ""; String res; if (v > thresholdLow && v < thresholdHigh) { res = String.format(formatFloat, v); } else { res = String.format(formatSci, v); int sp = res.indexOf('e'); e = res.substring(sp); res = res.substring(0, sp); } if (res.indexOf('.') < 0) res += "."; // add decimal point if not present res = res.replaceAll("0+$", ""); // trim trailing zeros if (res.endsWith(".")) res += "0"; // add traiing zero if nec res += e; if (neg) res = "-" + res; return res; } public static String sprintfDouble5(double v){ return sprintfDouble(v, "%.5f","%.5e",0.01,1000000.0); } 

WITH

 char * sprintfDouble(char *buf, double v, const char *floatFormat, const char *sciFormat, double thresholdLow, double thresholdHigh) { char *p; char *pd; /* pointer to '.' */ char *pe; /* pd=, pe=pointer to 'e' (or null terminator) */ char *buforig; int trimmed; if(v != v) { /* nan */ sprintf(buf,"NaN"); return buf; } if(v == v && (v - v) != 0.0) { /* infinity */ sprintf(buf, v < 0 ? "-Infinity" :"Infinity"); return buf; } if(v==0) { /* positive or negative zero, dont distinguish*/ sprintf(buf, "0.0"); return buf; } buforig = buf; if(v <0) { v = -v; buf[0] = '-'; buf++; } if( v > thresholdLow && v < thresholdHigh ) { sprintf(buf,floatFormat, v); pe = buf+strlen(buf); pd = (char *) strchr(buf,'.'); if(pd == NULL) { /* no decimal point? add it */ pd = pe; *pe++ = '.'; *pe++ = '0'; *pe = 0; } } else { sprintf(buf,sciFormat, v); pe = (char *)strchr(buf,'e'); pd = (char *)strchr(buf,'.'); if(pd ==NULL) { /* no decimal point with scientific notation? rare but... */ p= buf+ strlen(buf); while(p>=pe) { *p = *(p-2); p--; } pd = pe; *pe++ = '.'; *pe++ = '0'; *pe = 0; } /* three digits exponent with leading zero? trim it */ if( (*(pe+2) == '0' ) && ( strlen(buf) - (pe-buf))==5) { *(pe+2)=*(pe+3); *(pe+3)=*(pe+4); *(pe+4)=*(pe+5); } } /* now trim trailing zeros */ trimmed = 0; p=pe-1; while(*p =='0' ) { p--; trimmed++; } if(*p=='.') { trimmed--; // dont trim the zero after the decimal point p++; } if(trimmed>0) { p = pe; while(1) { *(p-trimmed) = *p; if(*p==0) break; p++; } } return buforig; } char * sprintfDouble5(char *buf,double v) { return sprintfDouble(buf, v, "%.5f", "%.5e", 0.01, 1000000.0); } 

Test code.

Java

 static void test() { Locale.setDefault(Locale.US); double start = 1.0; double x=start; for(int i=0;i<367;i++) { System.out.println(sprintfDouble5(x)); x*= -7.0; } x=start; for(int i=0;i<6;i++) { System.out.println(sprintfDouble5(x)); x/= -5; } for(int i=0;i<200;i++) { System.out.println(sprintfDouble5(x)); x/= -42.01; } x=Math.PI*0.0000001; for(int i=0;i<20;i++) { System.out.println(sprintfDouble5(x)); x*=10; } System.out.println(sprintfDouble5(0.0)); System.out.println(sprintfDouble5(-0.0)); System.out.println(sprintfDouble5(0.0/0.0)); } 

WITH

 void test1() { char buf[64]; double start,x; int i; start = 1.0; x = start; for(i=0;i<367;i++) { printf("%s\n",sprintfDouble5(buf,x)); x *= -7.0; } x = start; for(i=0;i<6;i++) { printf("%s\n",sprintfDouble5(buf,x)); x /= -5; } for(i=0;i<200;i++) { printf("%s\n",sprintfDouble5(buf,x)); x/= -42.01; } x = atan(1.0) * 4 * 0.0000001; /* PI */ for(i=0;i<20;i++) { printf("%s\n",sprintfDouble5(buf,x)); x *= 10; } printf("%s\n",sprintfDouble5(buf,0.0)); printf("%s\n",sprintfDouble5(buf,-0.0)); printf("%s\n",sprintfDouble5(buf,0.0/0.0)); } 
+5
source

If you really need base-10 floating point output, here is probably the easiest way to write a JNI wrapper for C printf here. The people from Java decided that they needed to do printf themselves. Besides what you already noticed in %g , they decided to change the rounding behavior and cut the 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 parity:

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

Note that 1.03125 and 1.09375 exactly represent doubles with 1/32 = 0.3125.

The printf% g Java format incorrectly 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 is the correct 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 entirely 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 is the correct answer:

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

Thus, you need to either live in the rounding mode of the bizarro spell in your printf, or work with gcc and glibc . I cannot recommend trying to print floating point numbers. Or you can just use %a , which AFAIK works fine in Java.

+7
source

This code

 #include <stdio.h> int main() { double v; char format[] = "%.5g\n"; v = 1234.0; printf(format, v); v = 123.45678; printf(format, v); v = 0.000123456; printf(format, v); v = 0.000000000000123456; printf(format, v); } 

gave me

 1234 123.46 0.00012346 1.2346e-13 

and this code

 public class App13749802 { /** * @param args */ public static void main(String[] args) { double v; String format = "%.5g"; v = 1234.0; System.out.println(String.format(format, v)); v = 123.45678; System.out.println(String.format(format, v)); v = 0.000123456; System.out.println(String.format(format, v)); v = 0.000000000000123456; System.out.println(String.format(format, v)); } } 

gave me

 1234,0 123,46 0,00012346 1,2346e-13 

comma due to my regional settings. So there is only one difference.

+1
source

All Articles