浮動小数点数として保存すると、一部の数値の精度が低下するのはなぜですか?
たとえば、10進数9.2
は、2つの10進整数(92/10
)の比率として正確に表現でき、どちらも2進数(0b1011100/0b1010
)で正確に表現できます。ただし、浮動小数点数として保存された同じ比率が9.2
と正確に等しくなることはありません。
32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875
どうやらこのような単純な数は、メモリの64ビットで表現するには「大きすぎる」のでしょうか。
ほとんどのプログラミング言語では、浮動小数点数は scientific notation のように表現されます。指数と仮数(仮数とも呼ばれます)を使用します。 9.2
などの非常に単純な数は、実際にはこの分数です:
5179139571476070 * 2 -49
ここで、指数は-49
であり、仮数は5179139571476070
です。このように some 10進数を表すことができない理由は、指数と仮数の両方が整数でなければならないからです。つまり、すべてのフロートは、 integer に integer power of 2 を掛けたものでなければなりません。
9.2
は単に92/10
の場合がありますが、10は2として表現できませんnifnは整数値に制限されます。
最初に、32ビットおよび64ビットfloat
を作成するコンポーネントを see する関数がいくつかあります。出力のみに関心がある場合は、これらをグロスします(Pythonの例):
def float_to_bin_parts(number, bits=64):
if bits == 32: # single precision
int_pack = 'I'
float_pack = 'f'
exponent_bits = 8
mantissa_bits = 23
exponent_bias = 127
Elif bits == 64: # double precision. all python floats are this
int_pack = 'Q'
float_pack = 'd'
exponent_bits = 11
mantissa_bits = 52
exponent_bias = 1023
else:
raise ValueError, 'bits argument must be 32 or 64'
bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]
その関数の背後には多くの複雑さがあり、説明するのはかなり接線ですが、興味があるなら、私たちの目的のための重要なリソースは struct モジュールです。
Pythonのfloat
は、64ビットの倍精度数です。 C、C++、Java、C#などの他の言語では、倍精度には別の型double
があり、多くの場合64ビットとして実装されます。
9.2
の例でその関数を呼び出すと、次のようになります。
>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
戻り値を3つのコンポーネントに分割したことがわかります。これらのコンポーネントは次のとおりです。
符号は、最初のコンポーネントに単一ビットとして保存されます。説明は簡単です:0
は、フロートが正数であることを意味します。 1
は、負であることを意味します。 9.2
は正であるため、符号値は0
です。
指数は、11ビットとして中央のコンポーネントに格納されます。この場合、0b10000000010
。 10進数では、値は1026
を表します。このコンポーネントの癖は、2に等しい数を引く必要があることです(ビット数)-1 -1は真の指数を取得します。この例では、0b1111111111
(10進数1023
)を減算して、真の指数0b00000000011
(10進数3)を取得します。
仮数は、52ビットとして3番目のコンポーネントに格納されます。ただし、このコンポーネントにも癖があります。この癖を理解するには、次のような科学表記法の数字を考えてください。
6.0221413x1023
仮数は6.0221413
になります。科学表記法の仮数は、常にゼロ以外の単一の数字で始まることを思い出してください。同じことがバイナリにも当てはまりますが、バイナリには0
と1
の2桁しかありません。したがって、バイナリ仮数 always は1
!で始まります!浮動小数点数が格納されると、スペースを節約するために、バイナリ仮数の前にある1
が省略されます。 true 仮数を取得するには、3番目の要素の前に戻す必要があります。
1.00100110011001100110011001100110011001100110011001100110
これは、3番目のコンポーネントに格納されたビットが実際に仮数の fractional 部分を表し、 radix point 。
10進数を扱う場合、10の累乗で乗算または除算することにより「小数点を移動」します。2進数では、2の累乗で乗算または除算することで同じことができます。 25252箇所右に移動するには:
0.00100110011001100110011001100110011001100110011001100110
10進表記では、675539944105574
を4503599627370496
で除算して0.1499999999999999
を取得するのと同じです。 (これは、2進数で正確に表現できる比率の一例ですが、おおよそ10進数でしか表現できません。詳細については、 675539944105574/4503599627370496 を参照してください。)
3番目の成分を小数に変換したので、1
を追加すると真の仮数が得られます。
0
が正、1
が負1
を追加して、真の仮数を取得します3つの部分をすべてまとめると、次の2進数が与えられます。
1.00100110011001100110011001100110011001100110011001100110 x 1011
その後、バイナリから10進数に変換できます。
1.1499999999999999 x 23 (不正確!)
乗算して、浮動小数点値として格納された後の(9.2
)で始まる数値の最終的な表現を明らかにします。
9.1999999999999993
数値を作成したので、単純な小数に再構成することができます。
1.00100110011001100110011001100110011001100110011001100110 x 1011
仮数を整数にシフトします。
10010011001100110011001100110011001100110011001100110 x 1011-110100
10進数に変換:
5179139571476070 x 23-52
指数を引きます:
5179139571476070 x 2-49
負の指数を除算に変換します。
5179139571476070/249
指数の乗算:
5179139571476070/562949953421312
等しい:
9.1999999999999993
>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']
仮数が4桁で、その後にゼロが多数続くことがわかります。しかし、ペースを見ていきましょう。
バイナリ科学表記法を組み立てます。
1.0011 x 1011
小数点をシフトします。
10011 x 1011-100
指数を引きます:
10011 x 10-1
2進数から10進数:
19×2-1
除算に対する負の指数:
19/21
指数の乗算:
19/2
等しい:
9.5
これは完全な答えではありません( mhlester 私は重複しない多くの良い地をすでにカバーしています)が、数字の表現が作業しているベースにどれだけ依存するかを強調したいに。
Good-ol 'base 10では、通常、次のように記述します
これらの表現を見ると、最初の表現のみが数学的に分数に等しい場合でも、それぞれを分数2/3に関連付ける傾向があります。 2番目と3番目の表現/近似には0.001のオーダーのエラーがありますが、実際には9.2と9.1999999999999993の間のエラーよりもはるかに悪いです。実際、2番目の表現は正しく丸められていません! それにもかかわらず、数値2/3の近似値として0.666の問題はありません。したがって、9.2がどのように問題になるかは実際にはありませんほとんどのプログラムで概算されます。(はい、一部のプログラムでは重要です。)
したがって、ここで数値の基数が重要です。基数3で2/3を表現しようとした場合、
(2/3)10 = 0.23
言い換えれば、基数を切り替えることにより、同じ数値を正確かつ有限に表現できます!重要な点は、任意の数を任意の基数に変換できる場合でも、すべての有理数は一部の基数では正確な有限表現を持ちますが、他の基数ではできません。
このポイントを家に戻すために、1/2を見てみましょう。この完全に単純な数値は、10進数と2進数で正確な表現を持っているにもかかわらず、3進数で繰り返し表現が必要なことに驚くかもしれません。
(1/2)10 = 0.510 = 0.12 = 0.1111 ...3
多くの場合、それらは基数2で有限に表現できない有理数を近似している(桁が繰り返される)ため、一般にanyベース。
他の答えはすべて良いですが、まだ一つ欠けていることがあります:
無理数(たとえば、π、sqrt(2)
、log(3)
など)を正確に表すことは不可能です!
そして、それが実際に彼らが不合理と呼ばれる理由です。世界のビットストレージの量は、それらのいずれかを保持するのに十分ではありません。 symbolic算術のみが精度を保持できます。
数学の必要性を有理数に制限する場合、精度の問題のみが管理可能になりますが。 (おそらく非常に大きな)整数のペアa
とb
を格納して、分数a/b
で表される数値を保持する必要があります。すべての算術演算は、高校の数学のように分数で行われる必要があります(例:a/b * c/d = ac/bd
)。
しかし、もちろんpi
、sqrt
、log
、sin
などが関係する場合、同じ種類の問題に直面します。
TL; DR
ハードウェアアクセラレーションによる算術演算では、限られた量の有理数のみを表現できます。表現できない数値はすべて概算されます。一部の数値(つまり、無理数)は、システムに関係なく表現できません。
実数は無限に多く(列挙できないほど多く)、有理数も無限に多くあります(それらを列挙することは可能です)。
浮動小数点表現は有限であるため(コンピューターのあらゆるものと同様)、多くの数を表現することは不可避です。特に、64ビットでは、18,446,744,073,709,551,616の異なる値のみを区別できます(無限と比較すると何もありません)。標準の規則では、9.2はそれらの1つではありません。整数mおよびeの場合、m.2 ^ eの形式になります。
たとえば、10に基づいた異なる数え上げシステムを考え出すことができます。9.2では、正確な表現になります。しかし、1/3など他の数値を表すことは依然として不可能です。
また、倍精度の浮動小数点数は極端に正確であることに注意してください。 15桁の正確な数字で、非常に広い範囲の任意の数値を表すことができます。日常生活の計算では、4桁または5桁で十分です。ライフタイムのミリ秒ごとにカウントする場合を除き、これらの15は本当に必要ありません。
9.2をバイナリ浮動小数点で表現できないのはなぜですか?
浮動小数点数は、制限された桁数と可動基数ポイントを持つ位置番号付けシステムです(わずかに簡略化されています)。
分数の素因数(分数がその最低項で表現される場合)が底の因子である場合、分数は位置番号システムで有限桁数を使用してのみ正確に表現できます。
10の素因数は5と2であるため、基数10では、a /(2b5c)。
一方、2の素因数は2のみであるため、基数2では、a /(2b)
なぜコンピューターはこの表現を使用するのですか?
単純な形式であり、ほとんどの目的に対して十分に正確であるためです。基本的に科学者が「科学表記法」を使用し、各ステップで結果を妥当な桁数に丸めるのと同じ理由です。
(たとえば)32ビットの分子と32ビットの分母で分数形式を定義することは確かに可能です。 IEEEの倍精度浮動小数点では表現できなかった数値を表現できますが、同様に、そのような固定サイズの小数形式では表現できない倍精度浮動小数点で表現できる数値が多数あります。
しかし、大きな問題は、そのような形式では計算が面倒になることです。 2つの理由があります。
一部の言語は分数型を提供しますが、通常は任意の精度と組み合わせて提供します。これにより、分数の近似を心配する必要がなくなりますが、分母のサイズの数の計算ステップを多数通過する場合、独自の問題が発生しますしたがって、フラクションに必要なストレージが爆発する可能性があります。
一部の言語は10進浮動小数点型も提供します。これらは主に、コンピューターが取得する結果が人間を念頭に置いて作成された既存の丸め規則(主に財務計算)と一致することが重要なシナリオで使用されます。これらは、バイナリ浮動小数点よりも作業が少し難しくなりますが、最大の問題は、ほとんどのコンピューターがそれらのハードウェアサポートを提供していないことです。