这段代码
#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); }
给我
1234 123.46 0.00012346 1.2346e-13
和这段代码
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)); } }
1234,0 123,46 0,00012346 1,2346e-13
逗号是因为我的区域设置。所以只有一个区别。
好吧,我结束了我自己的功能编码。在所有double范围内使用gcc和tcc进行测试,得到完全相同的输出(除非极少数非常小的值,小于1E-319)
我发布它以防有人发现它有用。
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); }
C:
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); }
测试代码。
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)); }
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)); }
如果你真的想要base-10浮点输出,那么为C编写一个JNI包装器可能最简单 printf 这里。 Java人员决定他们需要这样做 printf 他们自己。除了你已经注意到的 %g ,他们决定改变舍入行为并以奇怪的方式截断输出。以机智:
printf
%g
System.out.printf("%.5g\n", 1.03125); System.out.printf("%.5g\n", 1.09375); 1.0313 1.0938
gcc 正确地舍入到偶数:
gcc
printf("%.5g\n", 1.03125); printf("%.5g\n", 1.09375); 1.0312 1.0938
请注意,自1/32 = 0.3125以来,1.03125和1.09375可以完全表示为双精度。
Java的printf%g格式错误地截断了它的输出:
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
这是正确的答案:
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 是正常但不完全可代表。 Java假装不注意:
1.0e-200
System.out.printf("%.20g\n%.20a\n", 1.0e-200, 1.0e-200); 1.0000000000000000000e-200 0x1.87e92154ef7ac0000000p-665
printf("%.20g\n%.20a\n", 1.0e-200, 1.0e-200); 9.999999999999999821e-201 0x1.87e92154ef7ac0000000p-665
所以你要么在你的printf中接受奇怪的舍入行为,要么你背着 gcc 和 glibc 的工作。我不建议您自己尝试打印浮点数。或者你可以使用 %a ,AFAIK在Java中完美运行。
glibc
%a