4

ライブラリ関数を使用せずに浮動小数点数を出力する方法を見つけようとしています。浮動小数点数の小数部分の出力は非常に簡単であることが判明しました。積分部分を印刷するのは難しい:

static const int base = 2;
static const char hex[] = "0123456789abcdef";

void print_integral_part(float value)
{
    assert(value >= 0);
    char a[129]; // worst case is 128 digits for base 2 plus NUL
    char * p = a + 128;
    *p = 0;
    do
    {
        int digit = fmod(value, base);
        value /= base;
        assert(p > a);
        *--p = hex[digit];
    } while (value >= 1);
    printf("%s", p);
}

FLT_MAXベース 2 とベース 16 を使用して、作品の不可欠な部分を完璧に印刷します。

11111111111111111111111100000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000 (base 2)

ffffff00000000000000000000000000 (base 16)

ただし、基数 10 で印刷すると、最初の 7 桁の後にエラーが発生します。

340282368002860660002286082464244022240 (my own function)
340282346638528859811704183484516925440 (printf)

これは 10 で除算した結果だと思います。float の代わりに double を使用すると、より良くなります。

340282346638528986604286022844204804240 (my own function)
340282346638528859811704183484516925440 (printf)

(信じられないなら、 Wolfram Alphaprintfに入ってください。正解です。)2^128-2^104

では、どうすれprintfば正しい結果を出力できるのでしょうか? 内部でbigint機能を使用していますか?または、私が見逃している浮動小数点のトリックがありますか?

4

6 に答える 6

2

IEEE単精度float実装によると、float変数には常に24ビットのデータのみが格納されます。これは、浮動小数点数に最大7桁の10進数のみが格納されることを意味します。

数の残りの巨大さは指数に格納されます。FLT_MAXは3.402823466e+38Fとして初期化されます。したがって、10番目の精度の後、どの桁を印刷するかはどこにも定義されていません。

Visual C ++ 2010コンパイラから、この出力340282346638528860000000000000000000000.000000を取得します。これは、唯一の有効な出力です。

したがって、最初はこれらの多くの有効な数字があります3402823466したがって、最初の除算の後、0402823466しかありません。したがって、システムは左側の0を取り除き、右側に新しい数字を導入する必要があります。理想的な整数除算では、0です。浮動除算(値/ =基数;)を実行しているため、システムはその場所を埋めるために他の桁を取得しています。

したがって、私の意見では、printfは上記の使用可能な有効数字を整数に割り当て、これを処理している可能性があります。

于 2012-06-06T11:17:47.627 に答える
2

/編集: Unni の回答を最初に読んでください。この結果はhttp://codepad.org/TLqQzLO3からのものです。

void print_integral_part(float value)
{
    printf("input : %f\n", value);
    char a[129]; // worst case is 128 digits for base 2 plus NUL
    char * p = a + 128;
    *p = 0;
    do
    {
        int digit = fmod(value, base);
        value /= base;
        printf("interm: %f\n", value);
        *--p = hex[digit];
    } while (value >= 1);
    printf("result: %s\n", p);
}

print_integral_part(3.40282347e+38F);

操作によって値がどのように混乱するかを確認するにはvalue /= base:

input : 340282346638528859811704183484516925440.000000
interm: 34028234663852885981170418348451692544.000000
interm: 3402823466385288480057879763104038912.000000
interm: 340282359315034876851393457419190272.000000
interm: 34028234346940236846450271659753472.000000
interm: 3402823335658820218996583884128256.000000
interm: 340282327376181848531187106054144.000000
interm: 34028232737618183051678859657216.000000
interm: 3402823225404785588136713388032.000000
interm: 340282334629736780292710989824.000000
interm: 34028231951816403862828351488.000000
interm: 3402823242405304929106264064.000000
interm: 340282336046446683592065024.000000
interm: 34028232866774907300610048.000000
interm: 3402823378911210969759744.000000
interm: 340282332126513595416576.000000
interm: 34028233212651357863936.000000
interm: 3402823276229139890176.000000
interm: 340282333252413489152.000000
interm: 34028234732616232960.000000
interm: 3402823561222553600.000000
interm: 340282356122255360.000000
interm: 34028235612225536.000000
interm: 3402823561222553.500000
interm: 340282366859673.625000
interm: 34028237357056.000000
interm: 3402823735705.600098
interm: 340282363084.799988
interm: 34028237619.200001
interm: 3402823680.000000
interm: 340282368.000000
interm: 34028236.800000
interm: 3402823.600000
interm: 340282.350000
interm: 34028.234375
interm: 3402.823438
interm: 340.282349
interm: 34.028235
interm: 3.402824
interm: 0.340282
result: 340282368002860660002286082464244022240

疑わしい場合は、より多くのprintfsを投げてください;)

于 2012-06-06T10:45:49.787 に答える
2

問題は にあると思いますvalue /= base;。10 はバイナリ システムの有限分数ではないため、この計算は決して正しくないことを忘れないでください。fmodまた、同様の理由で何らかのエラーが発生すると想定しています。

printf最初に整数部分を計算してから、それを10進数に変換します(printf整数部分を正しく取得した場合)。

于 2012-06-06T10:34:35.690 に答える
1

これをもう一度説明しましょう。整数部分が (正確に) 0 に向かって切り刻む以外の丸めなしで出力された後、10 進ビットの時間です。

バイナリ ゼロを含むバイト文字列 (最初は 100 など) から始めます。fp 値の小数点の右側の最初のビットが設定されている場合、それは 0.5 (2^-1 または 1/(2^1)) が分数のコンポーネントであることを意味します。したがって、最初のバイトに 5 を追加します。次のビットが設定されます 0.25 (2^-2 または 1/(2^2)) は分数の一部です 2 番目のバイトに 5 を追加し、最初のバイトに 2 を追加します (ああ、キャリーを忘れないでください、それらは起こります -次のビット セットは 0.125 を意味するため、3 番目のバイトに 5 を追加し、2 番目のバイトに 2 を追加し、最初のバイトに 1 を追加します。

      value          string of binary 0s
start 0              0000000000000000000 ...
bit 1 0.5            5000000000000000000 ...
bit 2 0.25           7500000000000000000 ...
bit 3 0.125          8750000000000000000 ...
bit 4 0.0625         9375000000000000000 ...
bit 5 0.03125        9687500000000000000 ...
bit 6 0.015625       9843750000000000000 ...
bit 7 0.0078125      9921875000000000000 ...
bit 8 0.00390625     9960937500000000000 ...
bit 9 0.001953125    9980468750000000000 ...
...

私はこれを手作業で行ったので、何かを見落としているかもしれませんが、これをコードに実装するのは簡単です。

したがって、ここで何を話しているのかわからない「浮動小数点数を使用して正確な結果を得ることができない」というすべての人にとって、浮動小数点の小数値が完全に正確であることの証明です。とてつもなく正確。でもバイナリー。

これがどのように機能するかを理解するのに時間をかける人にとっては、より高い精度が手の届くところにあります. 他の人については...まあ、彼らはフォーラムを閲覧せずに、以前に何度も回答された質問への回答を探し続けると思いますが、正直なところ、彼らは「壊れた浮動小数点」(またはそれを何と呼ぶか​​)を発見したと信じています。毎日、同じ質問の新しいバリエーションを投稿します。

「魔法に近い」「暗黒の呪文」 笑える!

于 2012-06-07T18:48:44.293 に答える
1

float から string への変換の主役はdtoa()関数のようです。その方法については、 newlibの dtoa.c を参照してください。

では、printf はどのようにして正しい結果を出力するのでしょうか?

魔法に近いと思います。少なくともソースは、ある種の暗い呪文のように見えます。

内部でbigint機能を使用していますか?

はい、_Bigintリンクされたソース ファイルで検索します。

または、私が見逃している浮動小数点のトリックがありますか?

おそらく。

于 2012-06-06T10:50:33.347 に答える