web-dev-qa-db-ja.com

精度を損なうことなく倍精度印刷

ダブルをストリームに出力して、読み込まれたときに精度が失われないようにするにはどうすればよいですか?

私は試した:

std::stringstream ss;

double v = 0.1 * 0.1;
ss << std::setprecision(std::numeric_limits<T>::digits10) << v << " ";

double u;
ss >> u;
std::cout << "precision " << ((u == v) ? "retained" : "lost") << std::endl;

これは私が期待したようには機能しませんでした。

しかし、精度を上げることはできます(digits10が必要な最大値だと思ったので驚きました)。

ss << std::setprecision(std::numeric_limits<T>::digits10 + 2) << v << " ";
                                                 //    ^^^^^^ +2

これは有効桁数と関係があり、最初の2桁は(0.01)には含まれません。

では、浮動小数点数を正確に表すことを検討した人はいますか?私がしなければならないストリームの正確な魔法の呪文は何ですか?

いくつかの実験の後:

問題は私の元のバージョンにありました。精度に影響を与える小数点以下の文字列に有効数字がありました。

したがって、これを補うために、科学的記数法を使用して補うことができます。

ss << std::scientific
   << std::setprecision(std::numeric_limits<double>::digits10 + 1)
   << v;

しかし、これはまだ+1の必要性を説明していません。

また、数字をより正確に印刷すると、より正確に印刷されます!

std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10) << v << "\n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v << "\n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits) << v << "\n";

その結果:

1.000000000000000e-02
1.0000000000000002e-02
1.00000000000000019428902930940239457413554200000000000e-02

以下の@StephenCanonの回答に基づく:

Printf()フォーマッタ「%a」または「%A」を使用して正確に印刷できます。 C++でこれを実現するには、固定および科学的マニピュレータを使用する必要があります(n3225:22.4.2.2.2p5表88を参照)。

std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
std::cout << v;

今のところ私は定義しました:

template<typename T>
std::ostream& precise(std::ostream& stream)
{
    std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
    return stream;
}

std::ostream& preciselngd(std::ostream& stream){ return precise<long double>(stream);}
std::ostream& precisedbl(std::ostream& stream) { return precise<double>(stream);}
std::ostream& preciseflt(std::ostream& stream) { return precise<float>(stream);}

次へ:NaN/Infをどのように処理しますか?

32
Martin York

精度を失いたくない場合は、浮動小数点値を10進数で出力しないでください。数値を正確に表すのに十分な桁数を出力したとしても、すべての実装が浮動小数点範囲全体で10進文字列との間で正しく丸められた変換を行うわけではないため、精度が失われる可能性があります。

代わりに16進浮動小数点を使用してください。 Cの場合:

printf("%a\n", yourNumber);

C++ 0xは、同じことを行うiostream用のhexfloatマニピュレータを提供します(一部のプラットフォームでは、std::hex修飾子は同じ結果になりますが、これは移植可能な仮定ではありません)。

いくつかの理由から、16進浮動小数点の使用が推奨されます。

まず、印刷される値は常に正確です。この方法でフォーマットされた値の書き込みまたは読み取りでは、丸めは発生しません。これは、精度の利点を超えて、適切に調整されたI/Oライブラリを使用すると、このような値の読み取りと書き込みを高速化できることを意味します。また、値を正確に表すために必要な桁数も少なくなります。

14
Stephen Canon

「浮動小数点が不正確である」と言うのは正しくありませんが、それは有用な単純化であると認めます。実生活で基数8または16を使用した場合、このあたりの人々は「基数10の小数パッケージは不正確です。なぜ誰かがそれらを調理したのですか?」と言うでしょう。

問題は、積分値が1つの基数から別の基数に正確に変換されることですが、分数値は積分ステップの分数を表し、使用されるのはごくわずかであるため、分数値は変換されません。

浮動小数点演算は技術的に完全に正確です。すべての計算には、唯一の可能な結果があります。 is問題があり、most 10進数の分数には2を底とする表現が繰り返されます。実際、0.01、0.02、... 0.99のシーケンスでは、正確なバイナリ表現を持つのは3つの値だけです。 (0.25、0.50、および0.75。)繰り返される96の値があるため、明らかに正確に表されていません。

現在、1ビットを失うことなく浮動小数点数を読み書きする方法はいくつかあります。アイデアは、基数10の分数で2進数を表現しようとするのを避けることです。

  • それらをバイナリとして記述します。最近では、バイトオーダーを選択し、そのバイトオーダーのみを書き込みまたは読み取りする限り、誰もがIEEE-754形式を実装しているため、数値は移植可能になります。
  • それらを64ビット整数値として書き込みます。ここでは、通常の基数10を使用できます(52ビットの小数部ではなく64ビットのエイリアス整数を表しているためです)。

さらに小数の数字を書くこともできます。これがビットごとに正確であるかどうかは、変換ライブラリの品質に依存し、ここで完全な精度(ソフトウェアから)を期待できるかどうかはわかりません。ただし、エラーは非常に小さく、元のデータには確かに下位ビットの情報がありません。 (物理学と化学の定数はいずれも52ビットで知られておらず、地球上の距離が52ビットの精度で測定されたことはありません。)しかし、ビットごとの精度が自動的に比較される可能性があるバックアップまたは復元の場合、これは明らかに理想的ではありません。

17
DigitalRoss

JSONとの間でデータを(逆)シリアル化しようとしているので、この質問に興味を持ちました。

元の数値をロスレスで再構築するには、10進数の17桁で十分である理由について、より明確な説明があると思います(手を離さないで)。

enter image description here

3つの数直線を想像してみてください。
1。元の基数2の数
2。丸められた基数10の表現
3。再構成された数の場合(両方とも基数2であるため、#1と同じ)

基数10にグラフィカルに変換する場合、1番目の目盛りに最も近い2番目の数直線上の目盛りを選択します。同様に、丸められた基数10の値から元の値を再構築する場合。

私が持っていた重要な観察は、正確な再構成を可能にするために、基数10のステップサイズ(量子)は基数2の量子でなければならないということでした。そうしないと、必然的に赤で示されている悪い再構成が発生します。

Base2表現の指数が0の場合の特定のケースを取り上げます。その場合、base2クォンタムは2 ^ -52〜 = 2.22 * 10 ^ -16になります。これよりも小さい最も近い基数10の量子は10 ^ -16です。必要な基数10のクォンタムがわかったので、すべての可能な値をエンコードするには何桁が必要ですか?指数= 0の場合のみを考慮していることを考えると、表現する必要のある値のダイナミックレンジは[1.0、2.0)です。したがって、17桁が必要になります(分数の場合は16桁、整数部分の場合は1桁)。

0以外の指数の場合、同じロジックを使用できます。

指数base2quant。 base10quant。必要なダイナミックレンジの数字
 ----------------------------------------- ---------------------------- 
 1 2 ^ -51 10 ^ -16 [2、4)17 
 2 2 ^ -50 10 ^ -16 [4、8)17 
 3 2 ^ -49 10 ^ -15 [8、16)17 
 ... 
 32 2 ^ -20 10 ^ -7 [2 ^ 32、2 ^ 33)17 
 1022 9.98e291 1.0e291 [4.49e307,8.99e307)17 

網羅的ではありませんが、この表は17桁で十分であるという傾向を示しています。

私の説明が気に入っていただければ幸いです。

9
Yale Zhang

ダブルの精度は、52の2進数または15.95の10進数です。 http://en.wikipedia.org/wiki/IEEE_754-2008 を参照してください。すべての場合にdoubleの完全な精度を記録するには、少なくとも10進数の16桁が必要です。 [ただし、以下の4番目の編集を参照してください]。

ちなみに、これは有効数字を意味します。

OP編集への回答:

10進文字列ランタイムへの浮動小数点は、重要な桁よりもはるかに多くの桁を出力しています。ダブルは52ビットの仮数しか保持できません(実際には、保存されていない「非表示」1を数えると53ビット)。つまり、解像度は2 ^ -53 = 1.11e-16以下です。

例:1 + 2 ^ -52 = 1.0000000000000002220446049250313。 。 。 。

それらの10進数、.0000000000000002220446049250313。 。 。 。 10進数に変換したときのdoubleの最小のバイナリ「ステップ」です。

ダブル内の「ステップ」は次のとおりです。

.0000000000000000000000000000000000000000000000000001バイナリで。

2進ステップは正確ですが、10進ステップは不正確であることに注意してください。

したがって、上記の10進表現は

1.0000000000000002220446049250313。 。 。

正確な2進数の不正確な表現です。

1.0000000000000000000000000000000000000000000000000001。

3回目の編集:

Doubleの次の可能な値。正確なバイナリは次のとおりです。

1.0000000000000000000000000000000000000000000000000010

10進数で不正確に変換します

1.0000000000000004440892098500626。 。 。 。

したがって、10進数のこれらの余分な数字はすべて実際には重要ではなく、単なる基数変換アーティファクトです。

4番目の編集:

Doubleは最大16桁の10進数を格納しますが、数値を表すために17桁の10進数が必要になる場合があります。その理由は、桁のスライスと関係があります。

上で述べたように、doubleには52 +1の2進数があります。 「+1」は先行1と見なされ、保存も重要もありません。整数の場合、これらの52の2進数は、0から2 ^ 53-1までの数値を形成します。このような数値を格納するには、10進数がいくつ必要ですか。さて、log_10(2 ^ 53-1)は約15.95です。したがって、最大16桁の10進数が必要です。これらのd_0にd_15のラベルを付けましょう。

ここで、IEEE浮動小数点数にもバイナリ指数があることを考慮してください。たとえば、exponetを2インクリメントするとどうなりますか? 52ビットの数値に4を掛けました。これで、52桁の2桁が10進数のd_0からd_15に完全に一致する代わりに、いくつかの重要な2桁がd_16で表されます。ただし、10未満の値を掛けたため、d_0で表される重要な2進数が残っています。したがって、15.95の10進数は、d_1からd_15に加えて、d_0の上位ビットとd_16の下位ビットを占有します。これが、IEEEdoubleを表すために17桁の10進数が必要になる場合がある理由です。

5番目の編集

数値エラーを修正

5
ThomasMcLeod

ラウンドトリップ変換を保証する最も簡単な方法(IEEE 754 doubleの場合)は、常に有効数字17桁を使用することです。ただし、これには、不要なノイズ桁(0.1→ "0.10000000000000001")が含まれる場合があるという欠点があります。

私にとってうまくいったアプローチは、15桁の精度で数値をsprintfしてから、atofが元の値を返すかどうかを確認することです。そうでない場合は、16桁を試してください。 thatが機能しない場合は、17を使用します。

David Gayのアルゴリズム (Python 3.1でfloat.__repr__を実装するために使用))を試してみてください。

3
dan04

テーブル計算のエラーを指摘してくれたThomasMcLeodに感謝します

15桁または16桁または17桁を使用した往復変換を保証することは、比較的少数の場合にのみ可能です。数値15.95は、2 ^ 53(1つの暗黙のビット+仮数/「仮数」の52ビット)を取得することで得られ、10 ^ 15から10 ^ 16(10 ^ 16に近い)の範囲の整数になります。

指数が0の倍精度値xについて考えてみます。つまり、浮動小数点の範囲1.0 <= x <2.0に該当します。暗黙のビットは、xの2 ^ 0コンポーネント(部分)をマークします。仮数の最も高い明示的なビットは、次に低い指数(0から)<=> -1 => 2 ^ -1または0.5成分を示します。

次のビット0.25、0.125、0.0625、0.03125、0.015625などの後のビット(下の表を参照)。したがって、値1.5は、1.0を示す暗黙のビットと0.5を示す最も高い明示的な仮数ビットの2つのコンポーネントを足し合わせて表されます。

これは、暗黙のビットから下に向かって、最小が0(指数)-52(有効数字の明示ビット)= -52 => 2 ^ -52である可能性のあるコンポーネントを表す52の追加の明示ビットがあることを示しています。これは、以下の表によるとは...まあ、それが15.95の有効数字(正確には37)よりかなり多くなることがあなた自身で見ることができます。別の言い方をすれば、2 ^ 0の範囲の最小数である!= 1.0自体は2 ^ 0 + 2 ^ -52であり、これは1.0 + 2 ^ -52の隣の数(下)=(正確に)1.0000000000000002220446049250313080847263336181640625、私が53桁の有効数字として数える値。 17桁のフォーマット「精度」では、数値は1.0000000000000002と表示されます。これは、ライブラリが正しく変換されているかどうかによって異なります。

したがって、「17桁の往復変換」は、実際には有効な(十分な)概念ではない可能性があります。

2^ -1 = 0.5000000000000000000000000000000000000000000000000000
2^ -2 = 0.2500000000000000000000000000000000000000000000000000
2^ -3 = 0.1250000000000000000000000000000000000000000000000000
2^ -4 = 0.0625000000000000000000000000000000000000000000000000
2^ -5 = 0.0312500000000000000000000000000000000000000000000000
2^ -6 = 0.0156250000000000000000000000000000000000000000000000
2^ -7 = 0.0078125000000000000000000000000000000000000000000000
2^ -8 = 0.0039062500000000000000000000000000000000000000000000
2^ -9 = 0.0019531250000000000000000000000000000000000000000000
2^-10 = 0.0009765625000000000000000000000000000000000000000000
2^-11 = 0.0004882812500000000000000000000000000000000000000000
2^-12 = 0.0002441406250000000000000000000000000000000000000000
2^-13 = 0.0001220703125000000000000000000000000000000000000000
2^-14 = 0.0000610351562500000000000000000000000000000000000000
2^-15 = 0.0000305175781250000000000000000000000000000000000000
2^-16 = 0.0000152587890625000000000000000000000000000000000000
2^-17 = 0.0000076293945312500000000000000000000000000000000000
2^-18 = 0.0000038146972656250000000000000000000000000000000000
2^-19 = 0.0000019073486328125000000000000000000000000000000000
2^-20 = 0.0000009536743164062500000000000000000000000000000000
2^-21 = 0.0000004768371582031250000000000000000000000000000000
2^-22 = 0.0000002384185791015625000000000000000000000000000000
2^-23 = 0.0000001192092895507812500000000000000000000000000000
2^-24 = 0.0000000596046447753906250000000000000000000000000000
2^-25 = 0.0000000298023223876953125000000000000000000000000000
2^-26 = 0.0000000149011611938476562500000000000000000000000000
2^-27 = 0.0000000074505805969238281250000000000000000000000000
2^-28 = 0.0000000037252902984619140625000000000000000000000000
2^-29 = 0.0000000018626451492309570312500000000000000000000000
2^-30 = 0.0000000009313225746154785156250000000000000000000000
2^-31 = 0.0000000004656612873077392578125000000000000000000000
2^-32 = 0.0000000002328306436538696289062500000000000000000000
2^-33 = 0.0000000001164153218269348144531250000000000000000000
2^-34 = 0.0000000000582076609134674072265625000000000000000000
2^-35 = 0.0000000000291038304567337036132812500000000000000000
2^-36 = 0.0000000000145519152283668518066406250000000000000000
2^-37 = 0.0000000000072759576141834259033203125000000000000000
2^-38 = 0.0000000000036379788070917129516601562500000000000000
2^-39 = 0.0000000000018189894035458564758300781250000000000000
2^-40 = 0.0000000000009094947017729282379150390625000000000000
2^-41 = 0.0000000000004547473508864641189575195312500000000000
2^-42 = 0.0000000000002273736754432320594787597656250000000000
2^-43 = 0.0000000000001136868377216160297393798828125000000000
2^-44 = 0.0000000000000568434188608080148696899414062500000000
2^-45 = 0.0000000000000284217094304040074348449707031250000000
2^-46 = 0.0000000000000142108547152020037174224853515625000000
2^-47 = 0.0000000000000071054273576010018587112426757812500000
2^-48 = 0.0000000000000035527136788005009293556213378906250000
2^-49 = 0.0000000000000017763568394002504646778106689453125000
2^-50 = 0.0000000000000008881784197001252323389053344726562500
2^-51 = 0.0000000000000004440892098500626161694526672363281250
2^-52 = 0.0000000000000002220446049250313080847263336181640625
1
Olof Forshell

@ThomasMcLeod:有効数字のルールは私の分野である物理学に由来し、より微妙な意味を持っていると思います。

値1.52を取得する測定値があり、スケールからこれ以上詳細を読み取ることができず、別の数値(たとえば、このスケールが小さすぎるために別の測定値)を追加することになっている場合、たとえば2の場合、結果は(明らかに)小数点以下2桁、つまり3.52になります。しかし、同様に、値1.52に1.1111111111を追加すると、値2.63が得られます(それ以上はありません!)。

このルールの理由は、測定によって入力したよりも多くの情報が計算から得られたと自分をからかうのを防ぐためです(これは不可能ですが、ゴミを埋めることでそのように見えます。上記を参照)。

とは言うものの、この特定のルールは加算専用です(加算:結果のエラーは2つのエラーの合計です-したがって、1つだけを正しく測定しないと、運が良ければ精度が上がります...)。

他のルールを取得する方法:aが測定された数であり、δaが誤差であるとしましょう。元の式が次のようになっているとしましょう。f:= m aエラーδmでmも測定するとします(これを正の側とします)。その場合、実際の制限は次のようになります。f_up=(m +δm)(a +δa)およびf_down =(m-δm)(a-δa)したがって、f_up = m a +δmδa+(δma+mδa)f_down = m a +δmδa-(δma+mδa)したがって、有効桁数はさらに少なくなります。f_up〜m a +(δma+mδa)f_down〜m a-(δma+mδa)したがって、δf=δm a +mδa相対誤差を見ると、次のようになります。δf/ f =δm/ m +δa/ a

除算の場合、それはδf/ f =δm/m-δa/ aです。

それが要点を理解し、私があまり多くの間違いをしなかったことを願っています、それはここで遅いです:-)

tl、dr:有効桁数は、出力の桁数が実際に入力の桁数からのものであることを意味します(実世界では、浮動小数点数のような歪んだ画像ではありません)。測定値が「いいえ」エラーで1、「いいえ」エラーで3であり、関数が1/3であると想定される場合、はい、すべての無限桁は実際の有効桁です。そうしないと、逆の操作が機能しないため、明らかに機能する必要があります。

有効数字の規則が別の分野でまったく異なる何かを意味する場合は、続けてください:-)

0