3561

次のコードを検討してください。

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

これらの不正確さはなぜ起こるのでしょうか?

4

31 に答える 31

2682

二進浮動小数点演算はこんな感じ。ほとんどのプログラミング言語では、 IEEE 754 標準に基づいています。問題の核心は、数値がこの形式で 2 のべき乗の整数として表されることです。分母が 2 の累乗でない有理数 ( など)0.11/10、正確に表すことができません。

0.1標準binary64形式では、表現は次のように正確に記述できます。

  • 0.100000000000000005551115123125782702118158340454101562510進数、または
  • 0x1.999999999999ap-4C99 hexfloat 表記で。

対照的に、 である有理数0.11/10、正確に次のように記述できます。

  • 0.110進数、または
  • 0x1.99999999999999...p-4C99 hexfloat 表記の類似物で、 は...終わりのない 9 のシーケンスを表します。

プログラム内の定数0.2および0.3も、それらの真の値の近似値になります。最も近いが有理数よりも大きいが、最も近い がdouble有理数よりも小さいことが起こります。との合計が有理数よりも大きくなるため、コード内の定数と一致しなくなります。0.20.2double0.30.30.10.20.3

浮動小数点演算の問題のかなり包括的な扱いは、すべてのコンピューター科学者が浮動小数点演算について知っておくべきことです。わかりやすい説明については、 float-point-gui.de を参照してください

補足: すべての位置 (base-N) 数システムは、この問題を正確に共有しています。

単純な古い 10 進数 (基数 10) にも同じ問題があります。そのため、1/3 のような数値は 0.333333333... になります。

たまたま 10 進法で表すのは簡単な数 (3/10) を偶然見つけましたが、2 進法には適合しません。1/16 は 10 進数 (0.0625) では醜い数値ですが、2 進数では 10,000 分の 10 進数 (0.0001) と同じくらいきれいに見えます**。日常生活で基数 2 の数体系を使用する習慣があると、その数を見て、何かを半分にしたり、半分にしたり、何度も半分にしたりすることで、そこにたどり着くことができることを本能的に理解するでしょう。

** もちろん、これは浮動小数点数がメモリに格納される方法とは異なります (科学的表記法の形式を使用します)。しかし、これは 2 進浮動小数点の精度エラーが発生しやすいという点を示しています。なぜなら、私たちが通常扱うことに関心のある「現実世界の」数値は 10 のべき乗であることが多いためです。今日。これが、「7 分の 5」ではなく 71% のように言う理由でもあります (5/7 は 10 進数で正確に表すことができないため、71% は概算です)。

いいえ: 2 進浮動小数点数は壊れていません。たまたま他のすべての基数 N 数値システムと同じくらい不完全です :)

サイド サイド ノート: プログラミングでフロートを使用する

実際には、この精度の問題は、丸め関数を使用して、浮動小数点数を表示する前に、関心のある小数点以下の桁数に丸める必要があることを意味します。

また、同等性テストを、ある程度の許容範囲を許容する比較に置き換える必要があります。つまり、次のことを意味します。

ないでくださいif (x == y) { ... }

代わりにif (abs(x - y) < myToleranceValue) { ... }.

abs絶対値です。myToleranceValue特定のアプリケーションに合わせて選択する必要があります-そして、それは、許容する準備ができている「小刻みな余裕」の量と、比較する最大数が何であるかに大きく関係します(精度の問題による損失のため) )。選択した言語の「イプシロン」スタイルの定数に注意してください。これらは許容値として使用されません。

于 2009-02-25T21:40:32.900 に答える
356

ここでのほとんどの回答は、この質問を非常にドライで技術的な用語で扱っています。普通の人間が理解できる言葉でこれに対処したいと思います。

ピザをスライスしようとしていると想像してください。ピザのスライスを正確に半分にカットできるロボット ピザ カッターがあります。ピザ全体を半分にすることも、既存のスライスを半分にすることもできますが、いずれにしても、半分にすることは常に正確です。

そのピザカッターは非常に細かい動きをしており、ピザ全体から始めて、それを半分にし、そのたびに最小のスライスを半分にすると、スライスが小さすぎて高精度の能力を発揮できない前に、53回半分にすることができます. . その時点で、その非常に薄いスライスを半分にすることはできなくなり、そのまま含めるか除外する必要があります。

では、ピザの 10 分の 1 (0.1) または 5 分の 1 (0.2) になるように、すべてのスライスをどのように分割しますか? よく考えて、実際にやってみてください。伝説の精密ピザカッターが手元にあれば、本物のピザを使ってみることもできます。:-)


もちろん、ほとんどの経験豊富なプログラマーは本当の答えを知っています。それは、ピザをどんなに細かくスライスしても、これらのスライスを使用してピザの 10 分の 1 または 5 分の 1 を正確につなぎ合わせる方法はないということです。かなり良い概算を行うことができます。0.1 の概算と 0.2 の概算を足すと、0.3 というかなり良い概算が得られますが、それでも概算にすぎません。

For double-precision numbers (which is the precision that allows you to halve your pizza 53 times), the numbers immediately less and greater than 0.1 are 0.09999999999999999167332731531132594682276248931884765625 and 0.1000000000000000055511151231257827021181583404541015625. 後者は前者よりも 0.1 にかなり近いため、数値パーサーは入力が 0.1 の場合、後者を優先します。

(これらの 2 つの数値の差は、「最小のスライス」であり、これを含めると上向きのバイアスが生じるか、除外すると下向きのバイアスが生じます。この最小のスライスの専門用語はulpです。)

0.2 の場合、数値はすべて同じで、2 倍に拡大されています。ここでも、0.2 よりわずかに高い値を優先します。

どちらの場合も、0.1 と 0.2 の近似値にはわずかに上向きのバイアスがあることに注意してください。これらのバイアスを十分に追加すると、数値が必要なものからどんどん遠ざかり、実際、0.1 + 0.2 の場合、バイアスが十分に高くなるため、結果の数値は最も近い数値ではなくなります。 0.3に。

In particular, 0.1 + 0.2 is really 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, whereas the number closest to 0.3 is actually 0.299999999999999988897769753748434595763683319091796875.


PS 一部のプログラミング言語では、スライスを正確に 10 分の 1 に分割できるピザ カッターも提供されています。このようなピザ カッターは一般的ではありませんが、使用できる場合は、スライスのちょうど 10 分の 1 または 5 分の 1 を得ることが重要な場合に使用する必要があります。

(元は Quora に投稿されたものです。)

于 2014-11-20T02:39:59.620 に答える
226

浮動小数点の丸め誤差。0.1 は、素因数 5 がないため、基数 2 では基数 10 のように正確に表すことができません。 0.1 は、基数 2 では無限の桁数を取りますが、基数 10 ではそうではありません。また、コンピューターには無限のメモリがありません。

于 2009-02-25T21:41:23.273 に答える
140

私の答えはかなり長いので、3 つのセクションに分けました。質問は浮動小数点演算に関するものなので、マシンが実際に何をするかに重点を置きました。また、倍精度 (64 ビット) に固有のものにしましたが、引数はすべての浮動小数点演算に等しく適用されます。

前文

IEEE 754 倍精度 2 進浮動小数点形式 (binary64)の数値は、次の形式の数値を表します。

値 = (-1)^s * (1.m 51 m 50 ...m 2 m 1 m 0 ) 2 * 2 e-1023

64 ビット:

  • 最初のビットは符号ビットです。1数値が負の場合、0それ以外の場合は1です。
  • 次の 11 ビットは指数であり、1023だけオフセットされます。つまり、倍精度数から指数ビットを読み取った後、1023 を減算して 2 のべき乗を取得する必要があります。
  • 残りの 52 ビットは仮数(または仮数) です。仮数では、バイナリ値の最上位ビットが であるため、「暗黙の」1.は常に21省略されます。

1 - IEEE 754 では、符号付きゼロ+0の概念が認められていますが、-0扱いが異なり1 / (+0)ます。正の無限大です。1 / (-0)負の無限大です。ゼロ値の場合、仮数ビットと指数ビットはすべてゼロです。注: ゼロ値 (+0 および -0) は明示的に denormal 2として分類されません。

2 - これは、ゼロのオフセット指数 (および暗黙の ) を持つ非正規数0.の場合には当てはまりません。非正規化倍精度数の範囲は d min ≤ |x|です。≤ d max。ここで、d min (最小の表現可能なゼロ以外の数) は 2 -1023 - 51 (≈ 4.94 * 10 -324 ) であり、d max (仮数がすべて s で構成される最大の非正規数1) は 2 -1023です。 + 1 - 2 -1023 - 51 (≈ 2.225 * 10 -308 )。


倍精度数を 2 進数に変換する

倍精度浮動小数点数を 2 進数に変換するオンライン コンバーターは多数存在しますが ( binaryconvert.com など)、倍精度数の IEEE 754 表現を取得するサンプル C# コードをいくつか示します (3 つの部分をコロン ( :)で区切ります)。 :

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

要点: 元の質問

(TL;DR バージョンは一番下までスキップしてください)

Cato Johnston (質問者) は、なぜ 0.1 + 0.2 != 0.3 なのかと尋ねました。

バイナリ (3 つの部分をコロンで区切る) で記述された値の IEEE 754 表現は次のとおりです。

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

仮数部は の繰り返し数字で構成されていることに注意してください0011。これは、計算にエラーが発生する理由のです。0.1、0.2、および 0.3 は、バイナリ ビットの有限数では正確に表すことができません。 10 進数

また、指数の累乗を 52 減らし、2 進表現の点を 52 桁右にシフトできることにも注意してください (10 -3 * 1.23 == 10 -5 * 123 のように)。これにより、バイナリ表現を a * 2 pの形式で表す正確な値として表すことができます。ここで、「a」は整数です。

指数を 10 進数に変換し、オフセットを削除し、暗示1(角括弧内) を再度追加すると、0.1 と 0.2 は次のようになります。

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

2 つの数値を加算するには、指数が同じである必要があります。つまり、次のようになります。

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

合計は 2 n * 1.{bbb} の形式ではないため、指数を 1 増やし、小数点 (バイナリ) をシフトして取得します。

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

現在、仮数部には 53 ビットがあります (53 番目は上の行の角括弧内にあります)。IEEE 754のデフォルトの丸めモードは ' Round to Nearest ' です。つまり、数値xが 2 つの値abの間にある場合、最下位ビットが 0 である値が選択されます。

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

abは最後のビットのみが異なることに注意してください。...0011+ 1= ...0100. この場合、最下位ビットがゼロの値はbであるため、合計は次のようになります。

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

一方、0.3 のバイナリ表現は次のとおりです。

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

これは、 0.1 と 0.2 の合計のバイナリ表現と 2 -54だけ異なるだけです。

0.1 と 0.2 のバイナリ表現は、IEEE 754 で許容される数値の最も正確な表現です。デフォルトの丸めモードにより、これらの表現を追加すると、最下位ビットのみが異なる値になります。

TL;DR

IEEE 0.1 + 0.2754 バイナリ表現 (3 つの部分をコロンで区切る) で記述し、それを と比較すると0.3、次のようになります (個別のビットを角括弧で囲みました)。

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

10 進数に戻すと、これらの値は次のようになります。

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

差は正確に 2 -54であり、元の値と比較すると (多くのアプリケーションで) 重要ではありません。

浮動小数点数の最後の数ビットを比較することは本質的に危険です。有名な「すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと」(この回答の主要な部分をすべてカバーしています)を読んだ人なら誰でも知っているでしょう。

ほとんどの計算機は、追加の保護桁を使用してこの問題を回避します。これ0.1 + 0.2により0.3、最後の数ビットが丸められます。

于 2015-02-23T17:15:35.347 に答える
136

他の正解に加えて、浮動小数点演算の問題を回避するために値をスケーリングすることを検討することをお勧めします。

例えば:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... それ以外の:

var result = 0.1 + 0.2;     // result === 0.3 returns false

式は JavaScript で0.1 + 0.2 === 0.3返さfalseれますが、幸いにも浮動小数点の整数演算は正確であるため、10 進数表現のエラーはスケーリングによって回避できます。

実際の例として、精度が最優先される浮動小数点の問題を回避するために、お金をセント数を表す整数として扱うことをお勧めします。つまりドル2550ではなくセントです。25.50


1 Douglas Crockford: JavaScript: 良い部分: 付録 A - 悪い部分 (105 ページ) .

于 2010-04-09T12:25:09.333 に答える
65
于 2016-03-16T05:27:16.597 に答える
35

私の回避策:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

精度とは、加算時に小数点以下を保持する桁数を指します。

于 2011-12-26T06:51:53.710 に答える
33

いいえ、壊れていませんが、ほとんどの小数は概算する必要があります

概要

浮動小数点演算正確ですが、残念ながら、通常の 10 進数表現とはうまく一致しないため、記述したものとはわずかに異なる入力を与えていることがよくあります。

0.01、0.02、0.03、0.04 ... 0.24 のような単純な数値でさえ、2 進数の分数として正確に表すことはできません。0.01、.02、.03 ... を数えると、0.25 になるまで、基数2で表現できる最初の分数が得られません。FP を使用してそれを試みた場合、0.01 はわずかにずれているため、25 個を加算して正確な 0.25 にする唯一の方法は、ガード ビットと丸めを含む長い因果関係の連鎖を必要とすることになります。予測するのは難しいので、 「FP は不正確だ」と言って手を挙げますが、実際にはそうではありません。

私たちは常に FP ハードウェアに、基数 10 では単純に見えるが、基数 2 では繰り返し分数である何かを与えます。

どうしてそうなった?

小数で書くと、すべての分数 (具体的には、すべての終端小数)は次の形式の有理数です。

           a/( 2n × 5m )

バイナリでは、次の2 n項のみを取得します。

           a / 2 n

したがって、10進数では1/3を表すことはできません。基数 10 には素因数として 2 が含まれているため、2 進数の分数として記述できるすべての数値は、基数 10 の分数としても記述できますただし、基数10の分数として記述したものは、バイナリで表現できるものはほとんどありません。0.01、0.02、0.03 ... 0.99 の範囲では、FP 形式で表すことができる数値は 0.25、0.50、および 0.75 の3 つだけです。 2 n項のみを使用する素因数を使用します。

基数10では1/3を表すことはできませしかし、バイナリでは、 1 / 10 または 1 / 3を行うことはできません。

したがって、すべての 2 進数の分数は 10 進数で記述できますが、その逆は当てはまりません。実際、ほとんどの小数は 2 進数で繰り返されます。

それに対処する

開発者は通常、< イプシロンの比較を行うように指示されます。より適切なアドバイスは、整数値に丸め (C ライブラリの場合: round() および roundf()、つまり FP 形式のまま)、比較することです。特定の小数部の長さに丸めると、出力に関するほとんどの問題が解決されます。

また、実数計算問題 (初期の恐ろしく高価なコンピューターで FP が発明された問題) では、宇宙の物理定数と他のすべての測定値は、比較的少数の有効数字でしか知られていないため、問題空間全体とにかく「不正確」でした。この種のアプリケーションでは、FP の「精度」は問題になりません。

ビーンカウントに FP を使用しようとすると、問題全体が実際に発生します。それはそれで機能しますが、整数値に固執する場合にのみ、それを使用するポイントが無効になります。これが、小数部のソフトウェア ライブラリがすべて揃っている理由です。

「不正確さ」についての通常の手を振るだけでなく、実際の問題を説明しているため、クリスによるピザの回答が大好きです。FP が単に「不正確」である場合、それを修正することができ、何十年も前にそれを行っていたでしょう。私たちが持っていない理由は、FP 形式がコンパクトで高速であり、多くの数値を処理するための最良の方法であるためです。また、これは宇宙時代と軍拡競争の遺産であり、小さなメモリ システムを使用して非常に遅いコンピューターで大きな問題を解決しようとした初期の試みです。(場合によっては、 1 ビットのストレージ用の個々の磁気コアもありますが、それは別の話です。 )

結論

銀行で豆を数えるだけなら、そもそも 10 進数の文字列表現を使用するソフトウェア ソリューションが問題なく機能します。しかし、量子色力学や空気力学をそのように行うことはできません。

于 2016-02-02T23:49:41.243 に答える
32

良い回答がたくさん投稿されていますが、もう1つ追加したいと思います。

すべての数値をfloat / doubleで表現できるわけではありません 。たとえば、数値「0.2」は、IEEE754 浮動小数点標準では単精度で「0.200000003」と表現されます。

内部に実数を格納するためのモデルは、浮動小数点数を次のように表します。

ここに画像の説明を入力

0.2簡単にFLT_RADIX入力できるのに、 DBL_RADIX2; 「IEEE Standard for Binary Floating-Point Arithmetic (ISO/IEEE Std 754-1985)」を使用する FPU を搭載したコンピューターの場合、10 ではありません。

そのため、そのような数値を正確に表すのは少し難しいです。この変数を中間計算なしで明示的に指定したとしても。

于 2014-10-05T18:39:35.863 に答える
31

この有名な倍精度問題に関連するいくつかの統計。

0.1 (0.1 から 100 まで) のステップを使用してすべての値 ( a + b ) を加算すると、約 15% の精度エラーの可能性があります。この誤差により、値がわずかに大きくなったり小さくなったりする可能性があることに注意してください。ここではいくつかの例を示します。

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

0.1 のステップ (100 から 0.1 まで) を使用してすべての値 ( a - bここでa > b ) を減算すると、精度エラーの可能性は ~34% になります。ここではいくつかの例を示します。

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

*15% と 34% は確かに大きいので、精度が非常に重要な場合は常に BigDecimal を使用してください。10 進数が 2 桁の場合 (ステップ 0.01)、状況はさらに悪化します (18% と 36%)。

于 2015-01-03T12:12:14.820 に答える
19

誰もこれについて言及していないことを考えると...

Python や Java などの一部の高水準言語には、バイナリ浮動小数点の制限を克服するためのツールが付属しています。例えば:

  • Python のdecimalモジュールと Java のBigDecimalクラスは、数値を内部的に (2 進表記ではなく) 10 進表記で表します。どちらも精度が限られているため、エラーが発生しやすくなりますが、バイナリ浮動小数点演算で最も一般的な問題を解決します。

    お金を扱う場合、小数は非常に便利です。10 セントと 20 セントは、常にちょうど 30 セントです。

    >>> 0.1 + 0.2 == 0.3
    False
    >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
    True
    

    Python のモジュールは、 IEEE 標準 854-1987decimalに基づいています。

  • Python のfractionsモジュールと Apache Common のBigFractionクラス. どちらも有理数を(numerator, denominator)ペアとして表し、10 進浮動小数点演算よりも正確な結果が得られる場合があります。

これらの解決策はどちらも完璧ではありませんが (特にパフォーマンスを見る場合、または非常に高い精度が必要な場合)、2 進浮動小数点演算に関する多くの問題を解決します。

于 2015-08-21T14:53:07.447 に答える
18

これらの奇妙な数字が表示されるのは、コンピューターが計算目的で 2 進法 (基数 2) を使用するのに対し、私たちは 10 進法 (基数 10) を使用するためです。

2 進数、10 進数、またはその両方で正確に表現できない小数が多数あります。結果 - 切り上げられた (ただし正確な) 数値結果。

于 2013-10-14T16:45:17.880 に答える
18

ダクトテープソリューションを試しましたか?

エラーがいつ発生したかを判断し、短い if ステートメントで修正してみてください。きれいではありませんが、問題によってはそれが唯一の解決策であり、これがその 1 つです。

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

私は c# の科学シミュレーション プロジェクトで同じ問題を抱えていました。バタフライ効果を無視すると、大きな太ったドラゴンになって a**

于 2012-08-01T07:02:44.423 に答える
17

追加できますか?人々はいつもこれをコンピューターの問題だと思い込んでいますが、手で数えると (底 10)、(1/3+1/3=2/3)=true0.333... を 0.333... に足す無限大でなければ得られないので(1/10+2/10)!==3/10、底の問題と同じです。 2、0.333 + 0.333 = 0.666 に切り捨て、おそらく 0.667 に丸めますが、これも技術的に不正確です。

3 進法で数えますが、3 分の 1 は問題ではありません - 両手に 15 本の指があるレースでは、なぜ 10 進法が壊れているのかと尋ねられるかもしれません...

于 2016-03-18T00:38:44.017 に答える
17

この質問の多数の重複の多くは、特定の数値に対する浮動小数点の丸めの影響について尋ねています。実際には、単に読むよりも、関心のある計算の正確な結果を見る方が、どのように機能するかを簡単に把握できます。一部の言語では、Java でafloatまたはdoubleに変換するなど、それを行う方法が提供されています。BigDecimal

これは言語に依存しない質問であるため、10 進数から浮動小数点へのコンバーターなど、言語に依存しないツールが必要です。

double として扱われる質問の数字に適用します。

0.1 は 0.1000000000000000055511151231257827021181583404541015625 に変換されます。

0.2 は 0.200000000000000011102230246251565404236316680908203125 に変換されます。

0.3 は 0.299999999999999988897769753748434595763683319091796875 に変換されます。

0.30000000000000004 は 0.300000000000000444089209850062616169452667236328125 に変換されます。

最初の 2 つの数値を手動で、またはFull Precision Calculatorなどの小数計算機で追加すると、実際の入力の正確な合計が 0.300000000000000166533453693773481063544750213623046875 であることがわかります。

0.3 に相当する値に切り捨てられた場合、丸め誤差は 0.000000000000000277555756156289135105907917022705078125 になります。0.30000000000000004 に相当する値に切り上げると、丸め誤差 0.000000000000000277555756156289135105907917022705078125 も発生します。偶数への丸めのタイ ブレーカーが適用されます。

浮動小数点コンバーターに戻ると、0.30000000000000004 の生の 16 進数は 3fd3333333333334 であり、偶数で終わるため、正しい結果になります。

于 2015-12-21T11:15:49.590 に答える
10

デジタル コンピューターで実装できる種類の浮動小数点演算では、必然的に実数の近似値とその演算が使用されます。(標準バージョンは 50 ページを超えるドキュメントであり、正誤表とさらなる改良を扱う委員会があります。)

この近似は、異なる種類の近似の混合であり、正確さからの偏差の特定の方法により、それぞれを無視するか、慎重に考慮することができます。また、ハードウェア レベルとソフトウェア レベルの両方で、ほとんどの人が気付かないふりをしながら通り過ぎてしまう、多くの明示的な例外的なケースも含まれます。

無限の精度が必要な場合 (たとえば、多数の短いスタンドインの代わりに数 π を使用する場合)、代わりに記号演算プログラムを作成または使用する必要があります。

しかし、浮動小数点演算の値とロジックがあいまいで、エラーがすぐに蓄積されることがあるという考えに問題がなく、それを可能にする要件とテストを作成できる場合、コードは頻繁に内容を処理できます。あなたのFPU。

于 2015-10-05T15:55:03.597 に答える
10

楽しみのために、標準 C99 の定義に従って float の表現をいじり、以下のコードを書きました。

コードは、3 つのグループに分かれた float のバイナリ表現を出力します。

SIGN EXPONENT FRACTION

その後、合計を出力します。十分な精度で合計すると、ハードウェアに実際に存在する値が表示されます。

したがって、 を記述すると、コンパイラは、関数によって出力される合計が指定された数値と等しくなるように、float x = 999...その数値を関数によって出力されるビット表現に変換します。xxyy

実際には、この合計は概算にすぎません。数値 999,999,999 の場合、コンパイラは float のビット表現に数値 1,000,000,000 を挿入します。

コードの後に​​、ハードウェアに実際に存在し、コンパイラによってそこに挿入された両方の定数 (マイナス PI と 999999999) の項の合計を計算するコンソール セッションを添付します。

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

これは、ハードウェアに存在する float の実際の値を計算するコンソール セッションです。bcメインプログラムが出力する項の和を表示していました。その合計をpythonreplまたは同様のものに挿入することもできます。

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

それでおしまい。999999999 の値は実際には

999999999.999999446351872

bcまた、-3.14 も摂動していることを確認できます。scaleに係数を設定することを忘れないでくださいbc

表示される合計は、ハードウェアの内部です。それを計算して得られる値は、設定したスケールによって異なります。係数を 15に設定しscaleました。数学的には、無限の精度で、1,000,000,000 のようです。

于 2016-12-29T10:29:32.123 に答える
5

、、、などの 10 進数0.1は、バイナリ エンコードされた浮動小数点型では正確に表現されません0.2。と の近似0.3の合計は に使用される近似0.10.2は異なります。0.30.1 + 0.2 == 0.3

#include <stdio.h>

int main() {
    printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
    printf("0.1 is %.23f\n", 0.1);
    printf("0.2 is %.23f\n", 0.2);
    printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
    printf("0.3 is %.23f\n", 0.3);
    printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
    return 0;
}

出力:

0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17

これらの計算をより確実に評価するには、浮動小数点値に 10 進数ベースの表現を使用する必要があります。C 標準では、そのような型はデフォルトでは指定されていませんが、テクニカル レポートで説明されている拡張として指定されています。

、およびタイプがシステムで使用できる_Decimal32場合があります (たとえば、GCCは選択されたターゲットでそれらをサポートしますが、ClangはOS Xでそれらをサポートしません)。_Decimal64_Decimal128

于 2019-04-22T01:02:32.437 に答える
2

浮動小数点に関するこの興味深い問題を見たところです。

次の結果を考慮してください。

error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1

2**53+1ブレークポイント when -all works untilがはっきりとわかります2**53

>>> (2**53) - int(float(2**53))
0

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

これは、倍精度バイナリが原因で発生します: IEEE 754 倍精度バイナリ浮動小数点形式: binary64

倍精度浮動小数点形式の Wikipedia ページから:

倍精度 2 進浮動小数点は、パフォーマンスと帯域幅のコストにもかかわらず、単精度浮動小数点よりも範囲が広いため、PC で一般的に使用される形式です。単精度浮動小数点形式と同様に、同じサイズの整数形式と比較すると、整数の精度が不足しています。一般的には単にダブルとして知られています。IEEE 754 標準では、binary64 は次のように指定されています。

  • 符号ビット: 1 ビット
  • 指数: 11 ビット
  • 有効精度: 53 ビット (明示的に格納された 52 ビット)

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

与えられたバイアスされた指数と 52 ビットの小数部を持つ、与えられた 64 ビットの倍精度データムによって想定される実数値は次のとおりです。

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

また

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

それを指摘してくれた@a_guestに感謝します。

于 2019-10-05T21:46:11.000 に答える