浮動小数点値の精度を維持するためのPrintf幅指定子


103

文字列をスキャンして戻すときに元の浮動小数点値が取得printfされるように、出力を必要な有効桁数に自動的にフォーマットする浮動小数点指定子に適用できる幅指定子はありますか?

たとえば、floataを2小数点以下の精度で出力するとします。

float foobar = 0.9375;
printf("%.2f", foobar);    // prints out 0.94

出力をスキャンするとき0.94、元の0.9375浮動小数点値が返されることを保証する標準準拠の保証はありません(この例では、おそらくそうしません)。

に渡された元の値にスキャンして戻すことができるようにprintf、浮動小数点値を必要な有効桁数に自動的に出力する方法を教えてくださいprintf

に渡す最大幅float.h導出するためにいくつかのマクロを使用することができますprintfが、必要な有効桁数、または少なくとも最大幅に自動的に出力する指定子はすでにありますか?


4
@boboboboでは、ポータブルなアプローチをとるのではなく、仮定を空中で使用することをお勧めして

1
@ H2CO3いいえ、「空中想定」の使用はお勧めしません。printf( "%f", val );すでに移植可能で効率的で、デフォルトであるものを使用することをお勧めします。
bobobobo 2013

2
@boboboboそれを答えに追加するために、精度が指定されていない場合、printfステートメントがデフォルトで最大精度でfloat型を出力することを示すC99標準の節を引用できますか?
Vilhelm Grey

1
@VilhelmGray @chuxに入ると、特定のの実際の精度に関するかなり複雑な計算がいくつかありますdouble。あなたは通りdouble(非常に遠い1.0から)非常に大きくなり、それが実際に取得精度の低い小数部分(1.0未満の値部分)に。あなたの質問は、その中に誤った仮定(すなわち、すべてのことがあるので、あなたは本当に、ここで満足のいく答えを持つことができないので、floatS / doublesが作成されます等しい)
bobobobo

2
@Vilhelm Grey C11dr 5.2.4.2.2 "...小数点以下の桁数、n、p基数b桁の浮動小数点数はn桁の10進数の浮動小数点数に丸め、変更せずに戻すことができる値に対して、p log10 bbは10の累乗1 + p log10b⎤です。それ以外の場合はFLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ... "6,10,10は最小値です。
chux-モニカを2013年

回答:


92

@Jens Gustedtの16進数の解をお勧めします。%aを使用してください。

OPは、「最大の精度で(または少なくとも最上位の10進数まで)印刷する」ことを求めています。

簡単な例は、次のように1/7を印刷することです。

#include <float.h>
int Digs = DECIMAL_DIG;
double OneSeventh = 1.0/7.0;
printf("%.*e\n", Digs, OneSeventh);
// 1.428571428571428492127e-01

しかし、もっと深く掘り下げましょう...

数学的には答えは「0.142857 142857 142857 ...」ですが、ここでは有限精度の浮動小数点数を使用しています。IEEE 754倍精度バイナリを想定しましょう。したがって、OneSeventh = 1.0/7.0結果は以下の値になります。また、前後の表現可能なdouble浮動小数点数も示しています。

OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125
OneSeventh        = 0.1428571428571428 49212692681248881854116916656494140625
OneSeventh after  = 0.1428571428571428 769682682968777953647077083587646484375

aの正確な 10進数表現の印刷は、double用途が限られています。

Cには2つのマクロファミリがあり、<float.h>私たちを支援します。
最初のセットは、10進数で文字列に出力する有効数字の数ので、文字列をスキャンして戻すと、元の浮動小数点が得られます。C仕様の最小値とサンプルの C11コンパイラを示しています。

FLT_DECIMAL_DIG   6,  9 (float)                           (C11)
DBL_DECIMAL_DIG  10, 17 (double)                          (C11)
LDBL_DECIMAL_DIG 10, 21 (long double)                     (C11)
DECIMAL_DIG      10, 21 (widest supported floating type)  (C99)

2番目のセットは、文字列をスキャンして浮動小数点にしてからFPを印刷しても、同じ文字列の表示を維持できる有効桁数です。C仕様の最小値とサンプルの C11コンパイラを示しています。C99より前に入手できると思います。

FLT_DIG   6, 6 (float)
DBL_DIG  10, 15 (double)
LDBL_DIG 10, 18 (long double)

マクロの最初のセットは、有効数字のOPの目標を満たしているようです。しかし、そのマクロは常に利用できるわけではありません。

#ifdef DBL_DECIMAL_DIG
  #define OP_DBL_Digs (DBL_DECIMAL_DIG)
#else  
  #ifdef DECIMAL_DIG
    #define OP_DBL_Digs (DECIMAL_DIG)
  #else  
    #define OP_DBL_Digs (DBL_DIG + 3)
  #endif
#endif

「+ 3」は、私の以前の答えの要点でした。ラウンドトリップ変換文字列-FP文字列(C89で利用可能なセット#2マクロ)を知っている場合、その中心は、FP文字列-FP(C89以降で利用可能なセット#1マクロ)の桁数をどのように決定するでしょうか?一般的に、結果はadd 3でした。

これで、印刷する有効桁数がわかり、を介して駆動され<float.h>ます。

N印刷するにはかなりの小数点以下の桁を1には様々なフォーマットを使用することができます。

を使用する"%e"と、精度フィールドは、先頭の桁と小数点のの桁数になります。順調- 1です。注:これ-1は初期段階ではありませんint Digs = DECIMAL_DIG;

printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh);
// 1.4285714285714285e-01

では"%f"高精度のフィールドは、桁数です後に小数点。のような数値の場合OneSeventh/1000000.0OP_DBL_Digs + 6すべての有効数字を確認する必要があります。

printf("%.*f\n", OP_DBL_Digs    , OneSeventh);
// 0.14285714285714285
printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0);
// 0.00000014285714285714285

注:多くはに使用され"%f"ます。小数点以下6桁が表示されます。6はデフォルトの表示であり、数値の精度ではありません。


1.428571428571428492127e-01であり、1.428571428571428492127e-0 0 1ではないのはなぜですか? 'e'の後の桁数は3でなければなりません。
user1024

12.12.5浮動小数点変換では、のデフォルトの精度%fは6です
Jingguo Yao

1
@Jingguo Yao「この精度は、 '%f'の小数点文字に続く桁数を指定します」と述べています。そこにある「精度」という単語は、数学的に使用されていませんが、単に小数点以下の桁数を定義するために使用されています。1234567890.123、数学的には13桁の精度または有効桁数。0.000000000123には、13ではなく3桁の数学的精度があります。浮動小数点数は対数的に分布しています。この回答では、有効数字と数学的精度を使用しています。
chux-モニカを2015年

1
@Slipp D. Thompson「C仕様の最小値とサンプルの C11コンパイラが表示されています。」
chux-モニカを2015年

1
確かにあなたは正しいです-私のトリックは1.0と1.0eDBL_DIGの間の大きさの値に対してのみ有効です。これは間違いなく、での印刷に本当に適切な唯一の範囲"%f"です。"%e"あなたが示したように使用することは、もちろん、より良いアプローチであり、事実上まともな回答です(おそらく、それが使用"%a"可能な場合は使用するほど良くなく"%a"、 `DBL_DECIMAL_DIGが使用可能な場合は使用できるはずです)。(ハードコーディングされた小数点以下6桁の代わりに)常に正確に最大精度に丸める書式指定子を常に望んでいました。
グレッグA.ウッズ

65

浮動小数点数をロスレスで出力するための短い答え(NaNとInfinityを除いて、まったく同じ数に読み戻すことができるように):

  • タイプがfloatの場合:を使用しますprintf("%.9g", number)
  • タイプがdoubleの場合:を使用しますprintf("%.17g", number)

は使用しないでください%f。これは、小数点以下の有効桁数を指定するだけであり、小さい数値を切り捨てるためです。参考までに、マジックナンバー9および17は、float.hで定義FLT_DECIMAL_DIGおよびにありDBL_DECIMAL_DIGます。


6
%g指定子を説明できますか?
Vilhelm Grey 2014

14
%gは、精度のために必要な桁数で数値を出力します。数値が小さいまたは巨大な場合(.00005ではなく1e-5)の指数構文を優先し、末尾のゼロ(1.00000ではなく1)をスキップします。
ccxvii 2014年

4
@truthseeker IEEE 754のbinary64コードを表すには、実際に少なくとも 15の小数位を印刷する必要があります。ただし、2進数(2、4、8など)と10進数(10、100、1000など)の精度が同じ数値(1.0を除く)になることはないため、あいまいさをなくすには17が必要です。例:上記の2つのdouble0.1:は1.000_0000_0000_0000_2e-011.000_0000_0000_0000_3e-01区別するために17桁が必要です。
chux-モニカを2015年

3
@chux-%.16gの動作について誤解しています。それはだない 1.000_0000_0000_0000_3e-01から1.000_0000_0000_0000_2e-01を区別するあなたの例には十分。%.17gが必要です。
Don Hatch

1
@ドンハッチ私は同意し"%.16g"、不十分で"%.17g"あり "%.16e"、十分です。の詳細は%g、私が覚えていなかった。
chux-モニカを2016年

23

ビット(それぞれ16進パターン)のみに関心がある場合は、この%a形式を使用できます。これにより、次のことが保証されます。

デフォルトの精度は、基数2の正確な表現が存在する場合は値の正確な表現で十分であり、それ以外の場合はdoubleタイプの値を区別するのに十分な大きさです。

これはC99以降でのみ利用できることを追加する必要があります。


16

いいえ、最大精度で浮動小数点を印刷するそのようなprintf幅指定子はありません。その理由を説明させてください。

最大精度floatdouble可変とに依存する実際の値float、またはdouble

リコールfloatとはdoubleに格納されているsign.exponent.mantissaのフォーマット。これは、大きな数よりも小さな数の小数部に使用されるビットがはるかに多いことを意味します。

ここに画像の説明を入力してください

たとえば、float0.0と0.1を簡単に区別できます。

float r = 0;
printf( "%.6f\n", r ) ; // 0.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // 0.100000

しかし、float差のない考えがない1e27とします1e27 + 0.1

r = 1e27;
printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000

これは、すべての精度(仮数ビットの数によって制限されます)が、小数点の左側の数値の大部分に使用されるためです。

%.f修飾子はちょうどあなたが限りフロート番号から印刷したいどのように多くの小数点以下の値と言うの書式が行きます。利用可能な精度が数値のサイズに依存するという事実は、プログラマーが処理するあなた次第ですprintfあなたのためにそれを処理することはできません/しません。


2
これは、浮動小数点値を特定の小数点以下の桁数に正確に出力することの制限についての優れた説明です。ただし、元の単語の選択があいまいであると思うので、混乱を解消するために「最大精度」という用語を避けるために質問を更新しました。
Vilhelm Grey

それでも、印刷する数値の値によって異なります。
bobobobo 2013年

3
これは部分的には真実ですが、質問には答えず、OPが何を求めているかについて混乱します。彼は、aがfloat提供する有効な[10進数]桁の数をクエリできるかどうかを尋ねています。あなたはそのようなことはない(つまり、がないFLT_DIG)と主張しますが、これは誤りです。

@ H2CO3たぶん、私の投稿を編集して、反対票を投じる必要があります(j / k)。この答えFLT_DIGは何も意味しないと断言します。この回答は、使用可能な小数点以下の桁数がfloat内の値に依存すると断言します。
bobobobo 2013

1
フォーマット文字が「f」である必要があると想定していますか?それは必須ではないと思います。私の質問の読みは、OPが非可逆ラウンドトリップを生成する一部の printfフォーマット指定子を探しているため、@ ccxviiの回答(floatの場合は「%.9g」、doubleの場合は「%.17g」)はいいもの。おそらく、質問から「幅」という単語を削除することで、質問の表現が改善されます。
ドンハッチ

11

fromマクロ<float.h>と可変幅変換指定子(".*")を使用するだけです。

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);

2
@OliCharlesworthそんな意味ですか:printf("%." FLT_DIG "f\n", f);
Vilhelm Grey

3
+1しかし、これはに最適ですが%e、あまり効果的ではありません。%f印刷する値が近いことがわかっている場合のみです1.0
Pascal Cuoq 2013年

3
%e非常に小さい数値に対して有効数字を印刷し、印刷%fしません。例えばx = 1e-100%.5fプリント0.00000(歳差の完全な喪失)。 %.5eプリント1.00000e-100
chux-2013年

1
@boboboboまた、「より正確な理由が得られる」という点で間違っています。理由のためにFLT_DIG定義されている値に定義されています6の場合float、6桁を超える精度を保持できないためです。を使用して印刷する場合%.7f、最後の桁は意味がありません。反対票を投じる前に考えてください。

5
@boboboboいいえ、必ずしも6とは限らない%.6fため、同等でFLT_DIGはありません。そして、効率を気にする人はいますか?I / Oはすでに地獄のように高価です。1桁多かれ少なかれ精度がボトルネックになることはありません。

5

小さな実験を行って、での印刷DBL_DECIMAL_DIGが実際に数値のバイナリ表現を正確に保持していることを確認します。私が試したコンパイラとCライブラリのDBL_DECIMAL_DIG場合、実際に必要な桁数であり、1桁でも印刷しないと重大な問題が発生することがわかりました。

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

union {
    short s[4];
    double d;
} u;

void
test(int digits)
{
    int i, j;
    char buff[40];
    double d2;
    int n, num_equal, bin_equal;

    srand(17);
    n = num_equal = bin_equal = 0;
    for (i = 0; i < 1000000; i++) {
        for (j = 0; j < 4; j++)
            u.s[j] = (rand() << 8) ^ rand();
        if (isnan(u.d))
            continue;
        n++;
        sprintf(buff, "%.*g", digits, u.d);
        sscanf(buff, "%lg", &d2);
        if (u.d == d2)
            num_equal++;
        if (memcmp(&u.d, &d2, sizeof(double)) == 0)
            bin_equal++;
    }
    printf("Tested %d values with %d digits: %d found numericaly equal, %d found binary equal\n", n, digits, num_equal, bin_equal);
}

int
main()
{
    test(DBL_DECIMAL_DIG);
    test(DBL_DECIMAL_DIG - 1);
    return 0;
}

これをMicrosoftのCコンパイラ19.00.24215.1とgccバージョン7.4.0 20170516(Debian 6.3.0-18 + deb9u1)で実行します。1桁少ない10進数を使用すると、正確に等しい数値の数が半分になります。(rand()実際に使用した場合、約100万の異なる数値が生成されることも確認しました。)詳細な結果は次のとおりです。

マイクロソフトC

17桁で999507の値をテストした:999507は数値的に等しい、999507は2進数に等しい
16桁で999507の値をテストしました:545389は数値的に等しい、545389は2進的に等しい

GCC

17桁の999485値をテストした:999485は数値的に等しい、999485はバイナリーとして等しい
16桁の999485値をテストしました:545402は数値的に等しい、545402は2進数に等しい

1
「MicrosoftのCコンパイラでこれを実行する」->そのコンパイラにはがあるかもしれませんRAND_MAX == 32767。考えてみてu.s[j] = (rand() << 8) ^ rand();、特定のすべてのビットが0または1であることでチャンスを得ることを確認するなど
chux -復活モニカ

実際、そのRAND_MAXは32767なので、提案は正しいです。
Diomidis Spinellis

1
@ chux-ReinstateMonicaの提案に従って、RAND_MAXを処理するように投稿を更新しました。結果は以前に得られたものと同様です。
Diomidis Spinellis

3

回答に対する私のコメントの1つで、浮動小数点値のすべての有効数字を10進形式で印刷する方法がずっと欲しかったと嘆きました。質問とほぼ同じ方法です。さて、ようやく座って書いた。これは完全ではありません。これは追加情報を出力するデモコードですが、ほとんどの場合私のテストで機能します。テスト用に駆動するラッパープログラム全体のコピーが必要かどうかをお知らせください。

static unsigned int
ilog10(uintmax_t v);

/*
 * Note:  As presented this demo code prints a whole line including information
 * about how the form was arrived with, as well as in certain cases a couple of
 * interesting details about the number, such as the number of decimal places,
 * and possibley the magnitude of the value and the number of significant
 * digits.
 */
void
print_decimal(double d)
{
        size_t sigdig;
        int dplaces;
        double flintmax;

        /*
         * If we really want to see a plain decimal presentation with all of
         * the possible significant digits of precision for a floating point
         * number, then we must calculate the correct number of decimal places
         * to show with "%.*f" as follows.
         *
         * This is in lieu of always using either full on scientific notation
         * with "%e" (where the presentation is always in decimal format so we
         * can directly print the maximum number of significant digits
         * supported by the representation, taking into acount the one digit
         * represented by by the leading digit)
         *
         *        printf("%1.*e", DBL_DECIMAL_DIG - 1, d)
         *
         * or using the built-in human-friendly formatting with "%g" (where a
         * '*' parameter is used as the number of significant digits to print
         * and so we can just print exactly the maximum number supported by the
         * representation)
         *
         *         printf("%.*g", DBL_DECIMAL_DIG, d)
         *
         *
         * N.B.:  If we want the printed result to again survive a round-trip
         * conversion to binary and back, and to be rounded to a human-friendly
         * number, then we can only print DBL_DIG significant digits (instead
         * of the larger DBL_DECIMAL_DIG digits).
         *
         * Note:  "flintmax" here refers to the largest consecutive integer
         * that can be safely stored in a floating point variable without
         * losing precision.
         */
#ifdef PRINT_ROUND_TRIP_SAFE
# ifdef DBL_DIG
        sigdig = DBL_DIG;
# else
        sigdig = ilog10(uipow(FLT_RADIX, DBL_MANT_DIG - 1));
# endif
#else
# ifdef DBL_DECIMAL_DIG
        sigdig = DBL_DECIMAL_DIG;
# else
        sigdig = (size_t) lrint(ceil(DBL_MANT_DIG * log10((double) FLT_RADIX))) + 1;
# endif
#endif
        flintmax = pow((double) FLT_RADIX, (double) DBL_MANT_DIG); /* xxx use uipow() */
        if (d == 0.0) {
                printf("z = %.*s\n", (int) sigdig + 1, "0.000000000000000000000"); /* 21 */
        } else if (fabs(d) >= 0.1 &&
                   fabs(d) <= flintmax) {
                dplaces = (int) (sigdig - (size_t) lrint(ceil(log10(ceil(fabs(d))))));
                if (dplaces < 0) {
                        /* XXX this is likely never less than -1 */
                        /*
                         * XXX the last digit is not significant!!! XXX
                         *
                         * This should also be printed with sprintf() and edited...
                         */
                        printf("R = %.0f [%d too many significant digits!!!, zero decimal places]\n", d, abs(dplaces));
                } else if (dplaces == 0) {
                        /*
                         * The decimal fraction here is not significant and
                         * should always be zero  (XXX I've never seen this)
                         */
                        printf("R = %.0f [zero decimal places]\n", d);
                } else {
                        if (fabs(d) == 1.0) {
                                /*
                                 * This is a special case where the calculation
                                 * is off by one because log10(1.0) is 0, but
                                 * we still have the leading '1' whole digit to
                                 * count as a significant digit.
                                 */
#if 0
                                printf("ceil(1.0) = %f, log10(ceil(1.0)) = %f, ceil(log10(ceil(1.0))) = %f\n",
                                       ceil(fabs(d)), log10(ceil(fabs(d))), ceil(log10(ceil(fabs(d)))));
#endif
                                dplaces--;
                        }
                        /* this is really the "useful" range of %f */
                        printf("r = %.*f [%d decimal places]\n", dplaces, d, dplaces);
                }
        } else {
                if (fabs(d) < 1.0) {
                        int lz;

                        lz = abs((int) lrint(floor(log10(fabs(d)))));
                        /* i.e. add # of leading zeros to the precision */
                        dplaces = (int) sigdig - 1 + lz;
                        printf("f = %.*f [%d decimal places]\n", dplaces, d, dplaces);
                } else {                /* d > flintmax */
                        size_t n;
                        size_t i;
                        char *df;

                        /*
                         * hmmmm...  the easy way to suppress the "invalid",
                         * i.e. non-significant digits is to do a string
                         * replacement of all dgits after the first
                         * DBL_DECIMAL_DIG to convert them to zeros, and to
                         * round the least significant digit.
                         */
                        df = malloc((size_t) 1);
                        n = (size_t) snprintf(df, (size_t) 1, "%.1f", d);
                        n++;                /* for the NUL */
                        df = realloc(df, n);
                        (void) snprintf(df, n, "%.1f", d);
                        if ((n - 2) > sigdig) {
                                /*
                                 * XXX rounding the integer part here is "hard"
                                 * -- we would have to convert the digits up to
                                 * this point back into a binary format and
                                 * round that value appropriately in order to
                                 * do it correctly.
                                 */
                                if (df[sigdig] >= '5' && df[sigdig] <= '9') {
                                        if (df[sigdig - 1] == '9') {
                                                /*
                                                 * xxx fixing this is left as
                                                 * an exercise to the reader!
                                                 */
                                                printf("F = *** failed to round integer part at the least significant digit!!! ***\n");
                                                free(df);
                                                return;
                                        } else {
                                                df[sigdig - 1]++;
                                        }
                                }
                                for (i = sigdig; df[i] != '.'; i++) {
                                        df[i] = '0';
                                }
                        } else {
                                i = n - 1; /* less the NUL */
                                if (isnan(d) || isinf(d)) {
                                        sigdig = 0; /* "nan" or "inf" */
                                }
                        }
                        printf("F = %.*s. [0 decimal places, %lu digits, %lu digits significant]\n",
                               (int) i, df, (unsigned long int) i, (unsigned long int) sigdig);
                        free(df);
                }
        }

        return;
}


static unsigned int
msb(uintmax_t v)
{
        unsigned int mb = 0;

        while (v >>= 1) { /* unroll for more speed...  (see ilog2()) */
                mb++;
        }

        return mb;
}

static unsigned int
ilog10(uintmax_t v)
{
        unsigned int r;
        static unsigned long long int const PowersOf10[] =
                { 1LLU, 10LLU, 100LLU, 1000LLU, 10000LLU, 100000LLU, 1000000LLU,
                  10000000LLU, 100000000LLU, 1000000000LLU, 10000000000LLU,
                  100000000000LLU, 1000000000000LLU, 10000000000000LLU,
                  100000000000000LLU, 1000000000000000LLU, 10000000000000000LLU,
                  100000000000000000LLU, 1000000000000000000LLU,
                  10000000000000000000LLU };

        if (!v) {
                return ~0U;
        }
        /*
         * By the relationship "log10(v) = log2(v) / log2(10)", we need to
         * multiply "log2(v)" by "1 / log2(10)", which is approximately
         * 1233/4096, or (1233, followed by a right shift of 12).
         *
         * Finally, since the result is only an approximation that may be off
         * by one, the exact value is found by subtracting "v < PowersOf10[r]"
         * from the result.
         */
        r = ((msb(v) * 1233) >> 12) + 1;

        return r - (v < PowersOf10[r]);
}

それが質問に答えるかどうかは気にしません-これは本当に印象的です。それはいくつかの考えを取り、認められ、賞賛されるべきです。テスト用の完全なコードを何らかの方法で(ここにあるかどうかにかかわらず)含めればよいでしょうが、それがなくても、これは本当に良い仕事です。+1してください!
プリフタン、

0

私の知る限りでは、十分に拡散することが可能なアルゴリズムがある有効桁の必要な数に出力するように、オリジナルの浮動小数点値を取得した文字列のバックスキャンする際dtoa.c利用可能であるダニエル・ゲイ、によって書かれ、ここで(参照にNetlib上を関連する論文)。このコードは、Python、MySQL、Scilab、その他多くで使用されています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.