用于Java的C兼容printf输出

leo*_*loy 4 c java floating-point printf

我想在Java和C中将float/double转换为字符串,这样输出既一致用户友好.

通过"用户友好",我的意思是字符串应该是人类可读和声音:有效数字的最大数量,以及一些适当时自动切换到科学记数法(双倍可以跨越所有有效范围).

通过"一致",我的意思是Java和C 中的字符串应该完全相同(如果它们非常罕见,我会容忍一些例外).

为什么不简单地使用一些printf格式字符串"%.5g"?这几乎有效.但遗憾的是,精确字段的含义在Java和C中是完全不同的.此外,从科学记数到科学记数法的转换不是很一致,甚至格式本身也不是(指数的2或3位数......).不同的C编译器有时会产生不同的结果.

差异的例子 "%.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
Run Code Online (Sandbox Code Playgroud)

我可以使用C或Java(或两者)编写函数,但我想知道是否有人已经处理过这个问题.我不太关心性能,但是对于C编译器的可移植性是肯定的.

tmy*_*ebu 7

如果你真的想要基数为10的浮点输出,那么printf在这里为C编写一个JNI包装器可能是最简单的.Java人员决定他们需要自己做printf.除了你已经注意到的%g,他们决定改变舍入行为并以奇怪的方式截断输出.以机智:

System.out.printf("%.5g\n", 1.03125);
System.out.printf("%.5g\n", 1.09375);
1.0313
1.0938
Run Code Online (Sandbox Code Playgroud)

gcc 正确地舍入到偶数:

printf("%.5g\n", 1.03125);
printf("%.5g\n", 1.09375);
1.0312
1.0938
Run Code Online (Sandbox Code Playgroud)

请注意,自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
Run Code Online (Sandbox Code Playgroud)

这是正确的答案:

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
Run Code Online (Sandbox Code Playgroud)

1.0e-200是正常但不完全可代表.Java假装不注意:

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

这是正确的答案:

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

所以,你要么得到了与比扎罗舍入行为,住在你的printf或者您搭载关闭gccglibc的工作.我不建议您自己尝试打印浮点数.或者您可以使用%a,AFAIK在Java中完美运行.


leo*_*loy 5

好吧,我结束了我自己的功能编码.在所有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);
    }
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

测试代码.

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));
}
Run Code Online (Sandbox Code Playgroud)

C:

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));
}
Run Code Online (Sandbox Code Playgroud)