tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルSINGLE型の切捨てについて
記事No11755
投稿日: 2016/11/08(Tue) 16:43
投稿者SUZUKI
また壁にぶつかってしまいました
下記はSINGLE型にて小数点N位以下を切捨てするロジックです
(今回は小数点第4位以下を切り捨て)
型を色々変えていますがうまく行っていません
DOUBLE型は色々検索に出て来るのですが
SINGLE型はうまくヒットしませんでした?
Math.Floor関数がDOUBLE型なので変換誤差をうまく吸収できずにいます
Math.Floor関数あきらめたほうがよいか教えていただきたく
よろしくお願いします
  


Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim a As Single
        Dim b As Single
        a = 1.00401
        b = CSng(csp_double_syousuu(CDbl(a), 3, 0))
        MsgBox(b)    ' 1.004  OK
        b = CSng(csp_single_syousuu(a, 3, 0))
        MsgBox(b)    ' 1.004  OK
        b = CSng(csp_decimal_syousuu(CDec(a), 3, 0))
        MsgBox(b)    ' 1.004  OK
        a = 1.004
        b = CSng(csp_double_syousuu(CDbl(a), 3, 0))
        MsgBox(b)    '1.003   NO
        b = CSng(csp_single_syousuu(a, 3, 0))
        MsgBox(b)    ' 1.003  NO
        b = CSng(csp_decimal_syousuu(CDec(a), 3, 0))
        MsgBox(b)    ' 1.004  OK
        a = 1.00301
        b = CSng(csp_double_syousuu(CDbl(a), 3, 0))
        MsgBox(b)    ' 1.003  OK
        b = CSng(csp_single_syousuu(a, 3, 0))
        MsgBox(b)    ' 1.003  OK
        b = CSng(csp_decimal_syousuu(CDec(a), 3, 0))
        MsgBox(b)    ' 1.003  OK
        a = 1.003
        b = CSng(csp_double_syousuu(CDbl(a), 3, 0))
        MsgBox(b)    '1.003  OK
        b = CSng(csp_single_syousuu(a, 3, 0))
        MsgBox(b)    ' 1.003 OK
        b = CSng(csp_decimal_syousuu(CDec(a), 3, 0))
        MsgBox(b)    ' 1.002  NO

    End Sub

    ''' 見ずらく申し訳ありませんがdValueの型を色々変えています
    Function csp_single_syousuu(ByRef dValue As Single, ByRef iDigits As Integer, ByRef isw As Integer) As Double
        Dim dCoef As Double = System.Math.Pow(10, iDigits)
        Select Case isw
            Case 0
                Return System.Math.Floor(dValue * dCoef) / dCoef
        End Select
    End Function

    Function csp_double_syousuu(ByRef dValue As Double, ByRef iDigits As Integer, ByRef isw As Integer) As Double
        Dim dCoef As Double = System.Math.Pow(10, iDigits)
        Select Case isw
            Case 0
                Return System.Math.Floor(dValue * dCoef) / dCoef
        End Select
    End Function

    Function csp_decimal_syousuu(ByRef dValue As Decimal, ByRef iDigits As Integer, ByRef isw As Integer) As Double
        Dim dCoef As Double = System.Math.Pow(10, iDigits)
        Select Case isw
            Case 0
                Return System.Math.Floor(dValue * dCoef) / dCoef
        End Select
    End Function

[ツリー表示へ]
タイトルRe: SINGLE型の切捨てについて
記事No11756
投稿日: 2016/11/09(Wed) 08:01
投稿者shu
Single型で正確な数を要求すること自体やめた方がよいかと思いますが
希望のことをされるのでしたら
切り上げの最低値をSingleで求めIf判定された方が解決しやすいかもしれません。
掛け算した時点で誤差の数値を拾ってきてしまうので提示された方法で
行うならDecimalに変換してからDecimalで計算してSingleに戻すとうまくいくかも
しれません。

[ツリー表示へ]
タイトルRe^2: SINGLE型の切捨てについて
記事No11757
投稿日: 2016/11/09(Wed) 11:03
投稿者SUZUKI
> Single型で正確な数を要求すること自体やめた方がよいかと思いますが
> 希望のことをされるのでしたら
> 切り上げの最低値をSingleで求めIf判定された方が解決しやすいかもしれません。
> 掛け算した時点で誤差の数値を拾ってきてしまうので提示された方法で
> 行うならDecimalに変換してからDecimalで計算してSingleに戻すとうまくいくかも
> しれません。
shuさん回答ありがとうございます
Decimalで統一するというのをやっていませんでした
  Function csp_decimal_syousuu(ByRef dValue As Decimal, ByRef iDigits As Integer, ByRef isw As Integer) As Decimal
        Dim dCoef As Decimal = CDec(System.Math.Pow(10, iDigits))
        Select Case isw
            Case 0
                Return System.Math.Floor(dValue * dCoef) / dCoef
        End Select
    End Function
のようにDecimal に統一した所
1.00401 -> 1.004
1.004   -> 1.004
1.00301 -> 1.003
1.003   -> 1.003 となりました これで色々試して見たいと思います
ありがとうございました

[ツリー表示へ]
タイトルRe^3: SINGLE型の切捨てについて
記事No11758
投稿日: 2016/11/09(Wed) 13:00
投稿者SUZUKI
いいと思ったのですが
a=0.965999961 という値が来る時があるのですが
これはdecimal化した時点でcdec(a)= 0.966D となってしまい

0.965999961 -> 0.965 になってくれません

decimal化した時のまるまる、まるまらないは
何が基準か迷ってしまいました
やはりmath関数を使わない方法をあみ出した方がよいと
思って来ました

[ツリー表示へ]
タイトルRe^4: SINGLE型の切捨てについて
記事No11759
投稿日: 2016/11/09(Wed) 13:32
投稿者shu
> いいと思ったのですが
> a=0.965999961 という値が来る時があるのですが
> これはdecimal化した時点でcdec(a)= 0.966D となってしまい
>
Singleの有効桁数は7桁なので
0.9659999までが有効桁となります。
なので続く961の部分は切り上げて考えないと駄目です。
これが駄目ならSingleは使うべきではありません。

[ツリー表示へ]
タイトルRe^5: SINGLE型の切捨てについて
記事No11760
投稿日: 2016/11/09(Wed) 15:22
投稿者SUZUKI
> >
> Singleの有効桁数は7桁なので
> 0.9659999までが有効桁となります。

理解致しましたが
VB6。0の仕様では無かった現象なので混乱しました
DOUBLE型で検討します
ありがとうございました

[ツリー表示へ]
タイトルRe^6: SINGLE型の切捨てについて
記事No11761
投稿日: 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 の精度です。

[ツリー表示へ]
タイトルRe^7: SINGLE型の切捨てについて
記事No11762
投稿日: 2016/11/10(Thu) 09:43
投稿者SUZUKI
なるほど
  Dim A As Single
  Dim B As Single
    A = 0.96599993
    B = CDec(A)
    MsgBox (A)   =>0.9659999
    MsgBox (B)   =>0.9659999
    A = 0.96599994
    B = CDec(A)
    MsgBox (A)  =>0.966
    MsgBox (B)  =>0.966
    A = 0.96599995
    B = CDec(A)
    MsgBox (A) =>0.966
    MsgBox (B) =>0.966
 これでなぜ4で切り上がるのが不思議でしたがなぞが解けました
  msgboxで確認するのも要注意ですね
 この現象はVB6.0 VB2005 も同じで、勘違いでした
 vb2005からシビアに見るようになったという事でした

 丁寧なご説明ありがとうございました
 

[ツリー表示へ]