タイトル : Re^6: SINGLE型の切捨てについて 投稿日 : 2016/11/09(Wed) 19:07 投稿者 : 魔界の仮面弁士
>>>> 下記はSINGLE型にて小数点N位以下を切捨てするロジックです Single 型は「2進小数」で管理されています。 そのため、「10進数の小数点」として扱う場合、近似値表現になることがあります。 正確性を求めるなら、BitConverter.GetBytes(single値) で内部バイナリ表現を得て、 それを直接操作するという切り捨て方もなくは無いですが…望む結果にできるかは別問題ですね。 > > > 0.965999961 この 10 進小数は、2進小数として割り切れるものではないため、 入力値を Single 値とする場合、内部的には 0.96599996089935302734375 相当の値となります。 > > Singleの有効桁数は7桁なので > > 0.9659999までが有効桁となります。 少し補足させてください。 まずは今回の「0.965999961」の近似値となる Single 値を見てみましょう。 左が Single 型の内部バイナリの2進数表現、右がそれを 10進数にしたものです。 比較のため、前後値あわせて 5 つを表示してみます。 (a) 00111111011101110100101111000100=0.965999841690063476562500 (b) 00111111011101110100101111000101=0.965999901294708251953125 (c) 00111111011101110100101111000110=0.965999960899353027343750 (d) 00111111011101110100101111000111=0.966000020503997802734375 (e) 00111111011101110100101111001000=0.966000080108642578125000 これを見ると、「0.965999961」に最も近いのは c のバイナリ表現であり、 実際には本来の値よりも僅かに小さい値として格納されていたことが分かります。 ただ、今回はたまたま小さい値に寄っただけで、値によっては大きい方に 丸められることもあるという点には注意が必要です。たとえば、 CSng("0.96599993") なら、少し小さい b が近似値として選択されますが、 CSng("0.96599994") なら、少し大きい c が近似値として選択されます。 このように、大小どちら側の近似値になるかがブレルることで、 .…999… と .…000… の境目にあたる値を切り捨てした場合において、 望まない結果になる可能性があるということになります。 > DOUBLE型で検討します そもそも、Single 型の変数サイズは 32bit 幅ですが、 その中の 1bitが正負の符号、8bitが小数桁数(-126〜127)として使われています。 残る23bitが仮数部であり、これが2進小数の有効桁を表す部分となります。 Single 型の仮数部表現では、2進小数で 23+1 桁が最大精度であるため、 それを 10進数で表せば 7.22471989… 桁相当になります。 これが『Singleの有効桁数は約7桁である』と言われる所以です。 そして Double 型を選択すれば、2進小数で 52+1 桁が最大精度になりますので、 10進数で 15.95458977… 桁相当にすることができ、問題の発生を抑えられる可能性が高まります。 ですが有効桁数が増えたとしても、大小どちら側の近似値になるかという問題が 消えたわけではありませんので、値次第ではやはり望まない結果になる可能性は残ります。 この問題を完全に防ぐには、Single や Double といった二進小数なデータ型ではなく、 十進小数を管理できる型(基本的には Decimal)を使うことです。これならば 2進小数で管理されていないため、10進数単位の桁で切り捨てしても問題は起きません。 ただし、計算途中で一度でも Single や Double を経由させないというのが大前提となります。 最初の入力値が Single や Double なのだとしたら、その時点ですでに近似値になっているため、 そのあとでいくら Decimal にしたとしても、正確な結果は望めません。 > VB6。0の仕様では無かった現象なので混乱しました Single や Double および "10進型" の精度は、VB6 と VB.NET とで完全に同一です。 (数値を数字化する際の文字列表現に差が生じることはありえますが) たとえば CDec("0.965999960899353027343750") という値は、 VB6 であれ VB.NET であれ、誤差の無い小数値を得られるはずです。 VB6 であれ VB.NET であれ、有効桁数という観点で見れば、 Single なら 23bit、Double なら 52bit、"10進型" なら 96bit の精度のはず。 ちなみに VB6 の "通貨型" は 64bit の精度です。 |