4

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

C または Java (またはその両方) で関数をコーディングできますが、誰かが既にこれを扱っているのではないかと思います。私はパフォーマンスにはあまり関心がありませんが、C コンパイラ間の移植性には関心があります。

4

4 に答える 4

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

gcc正しく偶数に丸めます:

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

1/32 = 0.3125 であるため、1.03125 と 1.09375 は double として正確に表現できることに注意してください。

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 は気付かないふりをします:

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 で奇妙な丸め動作に対処するか、gccandglibcの作業に便乗する必要があります。浮動小数点数を自分で印刷しようとすることはお勧めできません。%aまたは、Javaで完全に正常に動作する を使用することもできます。

于 2012-12-07T15:45:10.007 に答える
5

さて、私は自分の関数のコーディングを終了しました。double のすべての範囲で gcc および tcc を使用してテストすると、まったく同じ出力が得られます (ただし、1E-319 未満の非常に小さな値はほとんどありません)。

誰かが役に立つと思った場合に投稿します。

ジャワ:

     /**
     * 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);
    }

子:

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);
}

テストコード。

ジャワ

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));
}
于 2012-12-06T22:01:32.057 に答える
1

このコード

#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

コンマは私の地域設定によるものです。したがって、違いは 1 つだけです。

于 2012-12-06T18:37:27.337 に答える