[リストへもどる]   [VBレスキュー(花ちゃん)]
一括表示

投稿時間:2005/05/19(Thu) 09:14
投稿者名:Int
Eメール:
URL :
タイトル:
MsgBox Int(0.0282 * 10000)
Sub ttt()
  MsgBox Int(0.0282 * 10000)
End Sub

ってどういうことですか?

投稿時間:2005/05/19(Thu) 09:22
投稿者名:じゃんぬねっと
Eメール:
URL :http://jeanne.wankuma.com/
タイトル:
Re: MsgBox Int(0.0282 * 10000)
こんにちは、じゃんぬ です。

> Sub ttt()
>   MsgBox Int(0.0282 * 10000)
> End Sub
>
> ってどういうことですか?

リファレンスで Int を調べれば済む話ではないでしょうか?
リファレンスで MsgBox を調べれば済む話ではないでしょうか?
その上で、わからないことがあれば「何が」わからないのか、明示化された質問になると思います。

投稿時間:2005/05/19(Thu) 09:52
投稿者名:いな
Eメール:
URL :
タイトル:
Re^2: MsgBox Int(0.0282 * 10000)
きっと、余剰加算、余剰減算による演算誤差による
結果の相違のことを言ってるの?かな?

0,0282 ≒ 0.02819999......

っと・・・。って説明する前に、これらのキーワードでググってくださいな

> ってどういうことですか?
どうも、こうも無いような気がするんだが、
きちんと疑問点を末尾まで日本語にできるようにしましょうね

過去ログなどの事例から、言いたいことは予測はある程度可能ですけれども。
(=過去ログをしっかり見てください。ということです。)

投稿時間:2005/05/19(Thu) 13:39
投稿者名:Int
Eメール:
URL :
タイトル:
Re^3: MsgBox Int(0.0282 * 10000)
大変舌足らずで、非常に申し訳ございませんでした。m(__)m
今後もよろしくお願いします。

Sub Test1()
  MsgBox Int(0.0282 * 10000)
End Sub

281 と表示される。


Sub Test2()
  Dim a As Double
  a = 0.0282 * 10000
  MsgBox Int(a)
End Sub

282 と表示される。


なぜ違うのですか?
プログラムで一般的な浮動小数点数型の問題ですか?
それともマイクロソフトのVBの仕様ですか?

仕様だとして、どういう仕様かご存知の方おられましたらよろしくお願いします。

投稿時間:2005/05/19(Thu) 14:23
投稿者名:いな
Eメール:
URL :
タイトル:
Re^4: MsgBox Int(0.0282 * 10000)
>なぜ違うのですか?
>プログラムで一般的な浮動小数点数型の問題ですか?

正解。一般的な問題で、
プログラマならば一般常識といっても差し支えない範囲内だと思います。

>それともマイクロソフトのVBの仕様ですか?

極論を言えば、仕様といえば仕様ですが、
なぜ仕様としているかを理解しないと、
「仕様らしいです。」といっても通じないです。
なぜかというと・・・。

 コンピュータと2進数から説明しだすとHPが一つ作れてしまうので、割愛します。
 興味があれば調べてください。資料はいくらでも見つかるかと。

それにFAQなので、一般的な回避作も掲載されていますしね?
当然ロジックで、282と表示させる。方法「も」あります。

 また、似たような議題で技術系掲示板内で、あがっている話&解決している話。
 なので、こちらも、調べればすぐに見つかるかと・・・。

投稿時間:2005/05/20(Fri) 13:08
投稿者名:Int
Eメール:
URL :
タイトル:
Re^5: MsgBox Int(0.0282 * 10000)
「VBのInt関数の仕様はおかしい(一般的でない)のではないか?」
という疑念を持っているんですが、
いったん浮動小数点型の変数に入れた時と
直接とで
結果が違うのが一般的なんですか?

投稿時間:2005/05/20(Fri) 20:13
投稿者名:Starfish
Eメール:
URL :
タイトル:
VBのバグです
> 「VBのInt関数の仕様はおかしい(一般的でない)のではないか?」
> という疑念を持っているんですが、
> いったん浮動小数点型の変数に入れた時と
> 直接とで
> 結果が違うのが一般的なんですか?

 Intさんの言われるとおり、これは、浮動小数点の誤差ではなく、VBのバグによるものだと思います。

 ただ、どうしても、浮動小数点による誤差は考慮する必要がありますので、想定の範囲内という
ことで、何らかの対応が必要ではないかと思います。

投稿時間:2005/05/21(Sat) 01:55
投稿者名:Starfish
Eメール:
URL :
タイトル:
Re: VBのバグです
 以下確認用に使用したプログラムです。VB6.0のデバッグモードでかつ2回目以降に
現象が発生します。他は正常です。これって、やっぱりバグでしょう。

 VB6.0起動直後に、リストボックスとコマンドボタンを貼り付けて

Private Sub Command1_Click()
Dim a As Double
Dim b As Single
Dim c As Variant
    a = 0.0282 * 10000
    b = 0.0282! * 10000
    c = 0.0282 * 10000
    List1.AddItem "Int(0.0282 * 10000)=" & CStr(Int(0.0282 * 10000))
    List1.AddItem "Int(a)=" & CStr(Int(a))
    List1.AddItem "Int(b)=" & CStr(Int(b))
    List1.AddItem "Int(c)=" & CStr(Int(c))
End Sub

 2回以上コマンドボタンを押してください。

 又、コンパイルして、EXEファイルを実行して、同様に2回以上コマンドボタンを押してください。

 ついでに、VB.netならこんな感じ?(2005のベータ1で確認)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles Button1.Click
        Dim a As Double
        Dim b As Single
        Dim c As Object
        a = 0.0282 * 10000
        b = 0.0282! * 10000
        c = 0.0282 * 10000
        ListBox1.Items.Add("Int(0.0282 * 10000)=" & CStr(Int(0.0282 * 10000)))
        ListBox1.Items.Add("Int(a)=" & CStr(Int(a)))
        ListBox1.Items.Add("Int(b)=" & CStr(Int(b)))
        ListBox1.Items.Add("Int(c)=" & CStr(Int(c)))
    End Sub

投稿時間:2005/05/21(Sat) 08:51
投稿者名:花ちゃん
Eメール:
URL :
タイトル:
Re^2: VBのバグです
List1.AddItem CInt(0.0282 * 10000)
List1.AddItem Int(0.0282! * 10000!)
List1.AddItem Int(0.0282@ * 10000)

> 以下確認用に使用したプログラムです。VB6.0のデバッグモードでかつ2回目以降に
>現象が発生します。他は正常です。これって、やっぱりバグでしょう。

私の環境では、1回目も2回目も同じで 281 です。


こちらの方の抜粋の方が適切でした。
紛らわしい事を書いておりましたので訂正しておきます。
(ご迷惑をお掛けしました。)

------------ 以下 サポート技術情報より抜粋 -------------
[VB4 ] データ型と演算誤差についての注意 より

丸め
Int関数やFix関数での切り捨てや、整数型や文字列への変換時の丸めでも注意が必要です。
例えばけた上げをして切り捨てた結果を表示する次のようなコードがあります

Dim a As Double
Print Fix(a * 10)

ここで変数aに0.3という値が格納されていた場合、結果には3が期待されますが、実際には2が表示されます。これは0.3が近似表現されており、さらにその近似値が0.3より小さいために出る結果です。
このような問題では、精度が高いデータ型を利用したほうが期待通りの結果を出すとは限りません。例えば0.3は単精度では過剰近似値 (0.3000000...) で表されるため、同じ処理で3という結果になりますが、倍精度では不足近似値 (0.299999999999...) で表されるため結果は2になります。切り捨てた結果では単精度のほうが精度が高いように見えますが、切り捨て前の値は倍精度のほうがより0.3に近い値で格納されています。
同じ問題はFormat関数でも発生します。Format関数での丸めは四捨五入ですから、例えば0.35を "0.0"という書式で出力する場合には "0.4" という結果が期待されます。ところが0.35は内部で0.349999... と表現されているため結果は "0.3" になります。
(原文のまま)

尚、VB4.0の当時の記事であり、現在は、Format関数での丸め処理の仕方が変っております。

投稿時間:2005/05/21(Sat) 10:19
投稿者名:Starfish
Eメール:
URL :
タイトル:
Re^3: VBのバグです
> 型変換する場合は List1.AddItem CInt(0.0282 * 10000) Cint 関数を使用するべきかと
> 思うのですが。

 CInt関数は、四捨五入です。Int関数は、切り捨てですので、単純に代わりとして
使用できません。


> > 以下確認用に使用したプログラムです。VB6.0のデバッグモードでかつ2回目以降に
> >現象が発生します。他は正常です。これって、やっぱりバグでしょう。
> 私の環境では、1回目も2回目も同じで 281 です。

 うちでは、1回目のみ282、以降は281になります。書き方が悪かったかもしれませんが、
1回目とは、VB起動直後の1回目のみです。

> ------------ 以下 サポート技術情報より抜粋 -------------
> [VB4 ] データ型と演算誤差についての注意 より
>
> 演算子によっては、同じ型どうしの演算でも違う型の結果を返します。
> 商を求める / 演算子は通常は倍精度浮動小数点数型になるため、次のサンプルでは
> 通貨型を利用していますが誤差が発生して結果は31になります。
> このような場合は、a / bの結果をいったん通貨型の一時変数に代入することにより、
> 期待する結果を得ることができます。
>
> Dim a As Currency, b As Currency
> a = 32
> b = 1000
> a = Fix((a / b) * 1000)
>
> 上記技術情報を見る限り バグ ではないと思います。浮動小数点による誤差からくる
> 計算結果だと思います。
> 尚、2回目以降に現象が発生する件は、確認できないので解りません。

 マイクロソフトといえども、バグとわかっていても仕様どうりということで
逃げられるものは逃げようとしますから。

 変数に代入しようが、必ず発生するのなら、仕様ですというのは理解できますが、
 (1)1回目と2回目で実行結果が異なる。
 (2)開発環境下とEXEにしたもので結果が異なる。
 (3)VB.NETで発生しない。
 この、3点から、VBのバグであるとの判断です。

投稿時間:2005/05/21(Sat) 11:22
投稿者名:花ちゃん
Eメール:
URL :
タイトル:
Re^4: VBのバグです
>  CInt関数は、四捨五入です。Int関数は、切り捨てですので、単純に代わりとして
> 使用できません。

四捨五入 ではなく丸めだと思うのですが(VB6.0 or .NET )
0.0282# だって丸めだし、Debug.Print (100 / 3 * 3) これも丸めの結果では。


>  うちでは、1回目のみ282、以降は281になります。書き方が悪かったかもしれませんが、
> 1回目とは、VB起動直後の1回目のみです。
やはり、同じです。
(EXE の場合の1回目と2回目はどのように ボタンを2回押したのではだめですか)

但し、コンパイルで 最適化なし と あり では結果が違ってきますが。
最適化ありで 282 なし、もしくは  P-Code コンパイルで 281 です。
(EXE にした場合)

.NET では (最適化?)されているからでは。

投稿時間:2005/05/21(Sat) 16:04
投稿者名:Starfish
Eメール:
URL :
タイトル:
Re^5: VBのバグです
> >  CInt関数は、四捨五入です。Int関数は、切り捨てですので、単純に代わりとして
> > 使用できません。
>
> 四捨五入 ではなく丸めだと思うのですが(VB6.0 or .NET )
> 0.0282# だって丸めだし、Debug.Print (100 / 3 * 3) これも丸めの結果では。

 丸める方法が、四捨五入で丸めるか、切捨てで丸めるかの違いです。

Debug.Print CInt(0.288 * 100)
Debug.Print Int(0.288 * 100)

 CInt関数は、28.8=>29(小数点以下を四捨五入)
 Int関数は、28.8=>28(小数点以下を切り捨て)

> 但し、コンパイルで 最適化なし と あり では結果が違ってきますが。
> 最適化ありで 282 なし、もしくは  P-Code コンパイルで 281 です。
> (EXE にした場合)
>
> .NET では (最適化?)されているからでは。

 Int(0.0282 * 10000) の結果
 VB6.0 開発環境 1回目 282 2回目以降 281
 VB6.0 EXE(コンパイル)282
 VB6.0 EXE(P-Code)   281
 VB.NET         282

 本来単純な計算ですから誤差があったとしても、同じ値にならないと
おかしいですよね。VB6.0のインタープリッタで動いているときの
動作がおかしいのではないでしょうか。

投稿時間:2005/05/21(Sat) 16:33
投稿者名:ガッ
Eメール:
URL :
タイトル:
Re^6: VBのバグです
ノ<前にもどこかでこういう話題を見つけたのですが、毎回私の誤爆に終わっていますた。
 で、また気になったので少しテスツしてみますた。

'VB6標準EXE作成後、このコードを貼り付けて実行
Private Sub Form_Load()
    
    '問題点:Int(0.0282*10000)=<281|282>?
    Const v1        As Single = 0.0282!     '0.0282!
    Const v2        As Double = 0.0282      '0.0282#
    
    Debug.Print "TypeName(v1)="; TypeName(v1), "TypeName(v2)="; TypeName(
v2)
    'リテラルに問題があるかもしれないので、全てコピペ(orz
    Debug.Print "v1="; Int(v1), "v2="; Int(v2)
    Debug.Print "v1*1&="; Int(v1 * 1&), "v2*1&="; Int(v2 * 1&
amp;)
    Debug.Print "v1*2&="; Int(v1 * 2&), "v2*2&="; Int(v2 * 2&
amp;)
    Debug.Print "v1*10&="; Int(v1 * 10&), "v2*10&="; Int(v2 *
10&)
    Debug.Print "v1*100&="; Int(v1 * 100&), "v2*100&="; Int(v
2 * 100&)
    Debug.Print "v1*1000&="; Int(v1 * 1000&), "v2*1000&="; In
t(v2 * 1000&)
    Debug.Print "v1*10000&="; Int(v1 * 10000&), "v2*10000&=";
Int(v2 * 10000&)
    Debug.Print "v1*100000&="; Int(v1 * 100000), "v2*100000&="; I
nt(v2 * 100000)
    
End Sub

実行結果(イミディエイトウィンドウ)
TypeName(v1)=Single         TypeName(v2)=Double
v1= 0         v2= 0
v1*1&= 0      v2*1&= 0
v1*2&= 0      v2*2&= 0
v1*10&= 0     v2*10&= 0
v1*100&= 2    v2*100&= 2
v1*1000&= 28  v2*1000&= 28
v1*10000&= 282              v2*10000&= 281
v1*100000&= 2820            v2*100000&= 2819

家のPCだと、どうやら単純にSingleとDoubleの制度の違いのように思える。
&新しい検証方法示してすんません(orz

投稿時間:2005/05/21(Sat) 17:23
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^6: VBのバグです
> > 四捨五入 ではなく丸めだと思うのですが(VB6.0 or .NET )
>  丸める方法が、四捨五入で丸めるか、切捨てで丸めるかの違いです。

Intは切捨て処理ですが、CIntは四捨五入ではありませんよ。

Debug.Print CInt(1.5)   '→ 2
Debug.Print CInt(2.5)   '→ 2
Debug.Print CInt(3.5)   '→ 4
Debug.Print CInt(4.5)   '→ 4

これはどうみても、四捨五入ではありませんよね。


>  VB6.0 開発環境 1回目 282 2回目以降 281
面白い結果になっていますね。
こちらでも検証してみたいので、MSVBVM60.DLL のバージョンと、
OleAut32.DLL のバージョンを教えていただけますでしょうか。

投稿時間:2005/05/22(Sun) 02:12
投稿者名:Starfish
Eメール:
URL :
タイトル:
Re^7: VBのバグです
> > > 四捨五入 ではなく丸めだと思うのですが(VB6.0 or .NET )
> >  丸める方法が、四捨五入で丸めるか、切捨てで丸めるかの違いです。
>
> Intは切捨て処理ですが、CIntは四捨五入ではありませんよ。
>
> Debug.Print CInt(1.5)   '→ 2
> Debug.Print CInt(2.5)   '→ 2
> Debug.Print CInt(3.5)   '→ 4
> Debug.Print CInt(4.5)   '→ 4
>
> これはどうみても、四捨五入ではありませんよね。

 正確には、算術的な四捨五入ではなく、マイクロソフトの言う銀行型丸めというやつです。
.5のときは一方的に切り上げるのではなく偶数になるように切り上げまたは切捨てを行います。

hhttp://support.microsoft.com/default.aspx?scid=kb;ja;196652

>
> >  VB6.0 開発環境 1回目 282 2回目以降 281
> 面白い結果になっていますね。
> こちらでも検証してみたいので、MSVBVM60.DLL のバージョンと、
> OleAut32.DLL のバージョンを教えていただけますでしょうか。

 うちの環境が腐ってますかねぇ
 MSVBVM60.DLL 6.0.97.82
 OleAut32.DLL 5.1.2600.2180

投稿時間:2005/05/22(Sun) 09:05
投稿者名:Say
Eメール:
URL :
タイトル:
Re^8: VBのバグです
>  うちの環境が腐ってますかねぇ

挙動を確認しました。
うちの環境も腐ってるようです。

「開発環境初回起動時のみ」とそれ以外で動作が異なるというのがおもしろいですね。
テストにあたっては、毎回、開発環境から起動し直さないといけないのでちょっと面倒ですが・・・。

「初回動作と2回目以降の動作が異なる」というのは、明らかにバグと言っていいでしょう。
数値の型や数値の末尾が偶数か奇数かによっても異なるようですが、
「誤差」とか「仕様」とか呼ぶにはかなり無理があります。

テスト結果を見ると、Int()に小数を食わせたときの後始末に問題があるために
初回のみ正常値を返し、2回目以降は異常値を返しているようにみえます。

テスト結果(抜粋)
テスト1
Debug.Print Int(28.2 * 10)          282
Debug.Print Int(28.2 * 10)          281
テスト2
Debug.Print Int(CDbl(28.2) * 10)    282
Debug.Print Int(CDbl(28.2) * 10)    281
テスト3
Debug.Print Int(CSng(28.2) * 10)    282
Debug.Print Int(CSng(28.2) * 10)    282
テスト4
Debug.Print Int(CCur(28.2) * 10)    282
Debug.Print Int(CCur(28.2) * 10)    282
テスト5
Debug.Print Int(CDec(28.2) * 10)    282
Debug.Print Int(CDec(28.2) * 10)    282
テスト6
Debug.Print Int(28.1 * 10)          281
Debug.Print Int(28.1 * 10)          281
テスト7
Debug.Print Int(28.3 * 10)          283
Debug.Print Int(28.3 * 10)          283
テスト8
Debug.Print Int(28.4 * 10)          284
Debug.Print Int(28.4 * 10)          283
テスト9
Debug.Print Int(1)                  1
Debug.Print Int(28.2 * 10)          282
Debug.Print Int(28.2 * 10)          281
テスト10
Debug.Print Int(1.2)                1
Debug.Print Int(28.2 * 10)          281
Debug.Print Int(28.2 * 10)          281
テスト11
Debug.Print Int(1#)                 1
Debug.Print Int(28.2 * 10)          282
Debug.Print Int(28.2 * 10)          281
テスト12
Debug.Print Int(1.2!)               1
Debug.Print Int(28.2 * 10)          281
Debug.Print Int(28.2 * 10)          281
テスト13
Debug.Print Int(CCur(1.2))          1
Debug.Print Int(28.2 * 10)          281
Debug.Print Int(28.2 * 10)          281
テスト14
Debug.Print Int(CDec(1.2))          1
Debug.Print Int(28.2 * 10)          282
Debug.Print Int(28.2 * 10)          281

投稿時間:2005/05/22(Sun) 19:30
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^8: VBのバグです
> > > > 四捨五入 ではなく丸めだと思うのですが(VB6.0 or .NET )
> > >  丸める方法が、四捨五入で丸めるか、切捨てで丸めるかの違いです。
> > Intは切捨て処理ですが、CIntは四捨五入ではありませんよ。
>  正確には、算術的な四捨五入ではなく、マイクロソフトの言う銀行型丸めというやつです。

あ。四捨五入ではない事は、ご存知だったんですね。(^^;

演算精度の話が出ている時なので、「四捨五入」≠「Banker's Rounding」である点には、
どうしてもツッコミたかったのです。失礼しました。


ところで、IEEE754(2進浮動小数点演算規格)の資料を探していたら、
このような記述を見つけました。
hhttp://www.ma.is.saga-u.ac.jp/minamoto/doc/kyudai.pdf

》6.3 Double rounding(2重丸め)
(中略)
》 Pentium4 では,なぜ,このような結果になるのでしょうか? それは,Pentium4 では,
》 まず拡張倍精度で計算をして,その後,倍精度に丸めているからです.


そういえば、実際に浮動小数演算を行うのは、CPUの仕事でした。
もしかして、OS等のソフトウェア環境だけでなく、CPU等の物理的環境によっても
動作が異なる可能性があるのかな……。

# それにしても、1回目と2回目で演算結果が異なるのは不思議。

投稿時間:2005/05/21(Sat) 16:00
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
バグかどうかはさておき
VBのバグ、と断定してしまうのも如何なものでしょう。
(本文部では、「と思います」とあるのに、タイトルでは「バグです」と断定していますよね)


もしもそれが(仕様ではなく)障害だと認定されるとしても、その原因が
本当にVB側にあるのか、それとも他の理由なのか(たとえば、OLEに問題があるとか)と
いった情報は、Microsoft側できちんと検証してから決定される事であり、
我々が軽々しく決め付けるべきでは無いかと思っています。

# 浮動小数点の誤差補正が、VB側で行われているのか、OS側で行われているのかは、
# 私も知りません。(^^; まぁ、知っててどうなる物でも無いですけれどね。


で、それはそれとして。


> 型変換する場合は
何故ここで、型変換、という言葉が出てきたのでしょうか。
そもそも、Int関数は型変換関数ではありませんよね(この関数は、データ型を変化させません)。


> ------------ 以下 サポート技術情報より抜粋 -------------
個人的には、この情報も正確なものとは言えない気がしています。

VBの[/ 演算子]においては、通貨型同士の除算は、倍精度浮動小数点型で演算されます。
確かに、演算誤差が発生した後、一時的に変数に格納する事で補正は行われるようですが、
根本的に除算時の誤差を減じたいのであれば、通貨型ではなく10進型を使うべきだと思うのです。
(10進型同士の除算であれば、10進型のままの精度で演算されますから)

そのため、サポート技術情報にある
》このような場合は、a / bの結果をいったん通貨型の一時変数に
》代入することにより、期待する結果を得ることができます。
という回避策については、個人的には不安を感じています。

結果を浮動小数点型で求めておいてから、その誤差を補正しようとするよりも、
そもそも浮動小数点が使われないような演算を使うようにした方が安全だと思うからです。


――という点を踏まえて。

まず、上記サポート技術情報で論じられている除算時の誤差に関しては、
これは VBの仕様というよりも、OLEの(VarDiv API等の)仕様かと思っています。

「10進型」の除算ならば、専用の VarDecDiv APIが用意されていますが、
それ以外の型(通貨型など)については、専用の除算APIが見当たりません。
おそらく、すべて VarDiv API にて処理され、結果が Double(またはSingle)の精度で
演算されるのでしょう。だとすれば、どうしてもこの時点で 2進数処理のための
誤差が発生してしまう可能性があるでしょう。


で。
ここで問題となるのは、話題に上がっているInt関数についてですが……

これも予想ですが、Double型に対するInt関数では、OLEの VarInt API が
使われているだと思います。VB6での動作は、このOLE APIの実装に
依存しているのでは無いでしょうか。

# とはいえ、VarInt において、浮動小数点の誤差がどのように処理されるのかは、やはり
# わかりません。(なので、バグか仕様かといった話には、ここでは触れないでおきます)

だからといって、Int関数の動作が全て問題になるのか、といえば、そうでも無い様な気がします。
10進型や通貨型に対して Int関数を発行した場合には、それぞれ、10進型専用の VarDecInt API、
通貨型専用の VarCyInt APIが用意されているからです。

10進型は内部的には10進数、通貨型は内部的には整数(固定小数点型)なので、もしも
これらの関数が使われるようにすれば、誤差が出る事は無いかと思うのです。

# なお、VB.NET の場合、Int(Double)/Int(Single)関数は、内部的には
# Math.Floor(Double)と全く同等の呼び出しが行われているようです。
# また、Int(Decimal)関数は、Decimal.Floor(Decimal)と同等の意味を持ちます。

投稿時間:2005/05/22(Sun) 03:56
投稿者名:Starfish
Eメール:
URL :
タイトル:
Re: バグかどうかはさておき
> VBのバグ、と断定してしまうのも如何なものでしょう。
> (本文部では、「と思います」とあるのに、タイトルでは「バグです」と断定していますよね)
>
> もしもそれが(仕様ではなく)障害だと認定されるとしても、その原因が
> 本当にVB側にあるのか、それとも他の理由なのか(たとえば、OLEに問題があるとか)と
> いった情報は、Microsoft側できちんと検証してから決定される事であり、
> 我々が軽々しく決め付けるべきでは無いかと思っています。

 これは、VBのバグだと思っています。マイクロソフトが障害だと認定するかどうかは
関係ないです。自分が、バグだと思えばバグだと言います。


> > ------------ 以下 サポート技術情報より抜粋 -------------
> 個人的には、この情報も正確なものとは言えない気がしています。

 この情報は、VB4.0の情報ですから。以下の結果も32になりますし。
確か、VB4.0のころは10進型はなかったと記憶しています。VB6.0では、
これはそのまま当てはまらないと思います。

Dim a As Currency, b As Currency
a = 32
b = 1000
a = Fix((a / b) * 1000)

> VBの[/ 演算子]においては、通貨型同士の除算は、倍精度浮動小数点型で演算されます。
> 確かに、演算誤差が発生した後、一時的に変数に格納する事で補正は行われるようですが、
> 根本的に除算時の誤差を減じたいのであれば、通貨型ではなく10進型を使うべきだと思うのです。
> (10進型同士の除算であれば、10進型のままの精度で演算されますから)

 VB6.0では、通貨型同士の除算は、結果は10進型になるとおもいます。
(ヘルプのキーワード 「/ (演算子)」)

> そのため、サポート技術情報にある
> 》このような場合は、a / bの結果をいったん通貨型の一時変数に
> 》代入することにより、期待する結果を得ることができます。
> という回避策については、個人的には不安を感じています。
>
> 結果を浮動小数点型で求めておいてから、その誤差を補正しようとするよりも、
> そもそも浮動小数点が使われないような演算を使うようにした方が安全だと思うからです。
>
>
> ――という点を踏まえて。
>
> まず、上記サポート技術情報で論じられている除算時の誤差に関しては、
> これは VBの仕様というよりも、OLEの(VarDiv API等の)仕様かと思っています。
>
> 「10進型」の除算ならば、専用の VarDecDiv APIが用意されていますが、
> それ以外の型(通貨型など)については、専用の除算APIが見当たりません。
> おそらく、すべて VarDiv API にて処理され、結果が Double(またはSingle)の精度で
> 演算されるのでしょう。だとすれば、どうしてもこの時点で 2進数処理のための
> 誤差が発生してしまう可能性があるでしょう。
>
>
> で。
> ここで問題となるのは、話題に上がっているInt関数についてですが……
>
> これも予想ですが、Double型に対するInt関数では、OLEの VarInt API が
> 使われているだと思います。VB6での動作は、このOLE APIの実装に
> 依存しているのでは無いでしょうか。
>
> # とはいえ、VarInt において、浮動小数点の誤差がどのように処理されるのかは、やはり
> # わかりません。(なので、バグか仕様かといった話には、ここでは触れないでおきます)
>
> だからといって、Int関数の動作が全て問題になるのか、といえば、そうでも無い様な気がします。
> 10進型や通貨型に対して Int関数を発行した場合には、それぞれ、10進型専用の VarDecInt API、
> 通貨型専用の VarCyInt APIが用意されているからです。
>
> 10進型は内部的には10進数、通貨型は内部的には整数(固定小数点型)なので、もしも
> これらの関数が使われるようにすれば、誤差が出る事は無いかと思うのです。
>
> # なお、VB.NET の場合、Int(Double)/Int(Single)関数は、内部的には
> # Math.Floor(Double)と全く同等の呼び出しが行われているようです。
> # また、Int(Decimal)関数は、Decimal.Floor(Decimal)と同等の意味を持ちます。

 わたしは、Int(0.0282 * 10000)が必ず281を返すのなら、それは問題ないと思っています。
今回は、281と282を返す場合があり、それがバグだといっているのです。状況から判断して
282を返すつもりが281を返す様に見えます。
 Int さんの質問のInt(0.0282 * 10000)とa = 0.0282 * 10000=> Int(a) の結果の
違いの原因が何かというと、誤差が原因ではなく、このバグにより発生しているというのが
私の認識です。

投稿時間:2005/05/22(Sun) 17:02
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re:
# 以下は、スレッドの本題とは無関係なので、読まなくてもOKです。(^^;


>  これは、VBのバグだと思っています。
「思っている」という表現ならば、特に反論はありません。(^-^)

個人的には、「○○のような理由から、Intのバグだと思われる」とか、
「△△なので、それは浮動小数点の誤差の範囲内ではないか」といった
『考察』は望んでいますが、現時点ではまだ『結論』を出せる段階では無いと思うのです。

# 個人的には、こういう調査/考察ネタは大好きなのですが、もしも
# 管理人さんが、こういうスレッドを望まないなら、これ以上の考察は止めておきます。


> マイクロソフトが障害だと認定するかどうかは関係ないです。
> 自分が、バグだと思えばバグだと言います。
「バグだ」とMSに伝えるのであれば、それは良いことだと思います。が、
その事実を確認する前に、コミュニティでバグだと断定するような発言を
するのは、(たとえ本当にバグだとしても)止めたほうが良いとも思っています。

MSが認定したかどうかが、あなたには関係無いのと同様、第三者が尚早にバグと
認定したとしても、本当の製品障害かどうかとは関係無い事になってしまうからです。

それよりもむしろ、この事実をMSに伝えて、最終的には「MSに障害だと認めさせる」
ように働きかけた方が建設的ですよね。なので、障害と認めさせる根拠を得るために
(あるいは、仕様として納得できる根拠を聞きだすために)も、再現する人たちから
詳しい環境等をお聞きして、当方でもそれを確認したいと思っています。
(ある程度なら、当方でも複数の実験環境を用意する事ができますし)


> 確か、VB4.0のころは10進型はなかったと記憶しています。
あぁ、そういえばそうでしたね。
正確には「なかった」というより、「サポートしていなかった」ですけれども。

# 実際、VB4でも『Debug.Print VarType(10進型の値)』は 14 を返しますが、
# 「Debug.Print 10進型の値 / 10進型の値」は、サポート外としてエラー458が返されます。
## この辺りは、今回の話からは外れますので、どうでも良い事ですけど。


>  VB6.0では、通貨型同士の除算は、結果は10進型になるとおもいます。
『Debug.Print TypeName(10@ / 1@)』は、「Double」を返しませんか?
少なくとも当方では、VB6 でも Office 2003 VBA でも Double になりますが……。


> (ヘルプのキーワード 「/ (演算子)」)
それは、下記のトピックの事かと思いましたが…具体的にはどの部分でしょうか。
hhttp://www.microsoft.com/JAPAN/developer/library/Vbenlr98/vaoprdivide.htm


> 今回は、281と282を返す場合があり、それがバグだといっているのです。
なるほど。Starfishさんのスタンスはわかりました。

一方、私の方の考えを述べると、本当にバグならばその根拠を示したいと
思っていますし、仕様だとしてもその具体的な理由を知りたいと考えています。
(その根拠を調査するのは、MSでも第三者でも誰でも構わないとは思いますが)

しかし現状では、「1回目と2回目の計算結果が異なると言う問題」の責任さえ、
Int関数にあるのか、それともInt関数に渡す前の計算時点で異なっているのか、
そして、特定の環境だけで起きるのか、といった問題が何一つ切り分けられていないので、
現時点では「Intのバグだ」「仕様だ」などと断定する事はできないな、と思っています。

# たとえば下記では、CDate("01/02 03:04:05") を実行した時に、特定の環境で
# プログラムが異常終了してしまうという問題を取り上げています。
#    [文字列からDate型への変換に注意する]
#    hhttp://www.gj.il24.net/~nakasima/vb/trap/index.htm#VBTRAP19
# このURLに示した例を、CDate関数のバグだ! と言ってしまうのは簡単ですが、
# 実際には IsDateには非は無く、これは OLE側のDLLに問題があったわけです。
## 今回の件も同様で、事実を完全に確認しないまま、「Intのバグだ」
##「浮動小数点型の仕様だ」と断定するような事はしたくないと思ったのです。
## 断定してしまうと、そこから先の考察を止めてしまいますから、本当に問題が
## そこにあったのかどうか、わからなくなってしまいそうで……。

投稿時間:2005/05/22(Sun) 23:07
投稿者名:Starfish
Eメール:
URL :
タイトル:
Re^2:
> >  VB6.0では、通貨型同士の除算は、結果は10進型になるとおもいます。
> 『Debug.Print TypeName(10@ / 1@)』は、「Double」を返しませんか?
> 少なくとも当方では、VB6 でも Office 2003 VBA でも Double になりますが……。
>
>
> > (ヘルプのキーワード 「/ (演算子)」)
> それは、下記のトピックの事かと思いましたが…具体的にはどの部分でしょうか。
> hhttp://www.microsoft.com/JAPAN/developer/library/Vbenlr98/vaoprdivide.htm

 通貨型同士の除算は、結果は10進型になるという思っていたので、「10 進型 (Decimal)
と他のデータ型を含む除算」にはいると読み違えたようです。どうもすみません。

 しかしながら、計算した結果が制度の悪い型になるというのは考えられません。

 『Debug.Print TypeName(10@ / 1@)』は、「Double」を返しますが、以下のコードを
実行してみると、c3=c5 とはならず、c3には通貨型の精度で値が表示されます。
不思議ですねぇ。

Private Sub Command1_Click()
Dim c1 As Currency
Dim c2 As Currency
Dim c3 As Currency
Dim d4 As Double
Dim c5 As Currency
Dim x As Currency
Dim i As Long

c1 = 922337203685465.5807@
x = 1.23
    
For i = 0 To 10
    c1 = c1 + 1
    c2 = (c1 / x)
    c3 = c2 * x
    d4 = (c1 / x)
    c5 = d4 * x
    Debug.Print c1, c3, c5
Next
    
End Sub

投稿時間:2005/05/23(Mon) 03:19
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^3:
>  しかしながら、計算した結果が制度の悪い型になるというのは考えられません。
制度→精度、の事だと解釈しますが、この場合の「精度が悪い」というのは、
どのような意味で書かれていますか?
「精度が悪い」という表現には、幾通りかの解釈があるかと思います。

たとえば、「数値として表現可能な範囲が狭い」(最小値と最大値の差が小さい)とか、
「値と値の幅が広く、細かい値を表現できない」(整数型なら1単位、通貨型なら0.0001単位)とか。

>  『Debug.Print TypeName(10@ / 1@)』は、「Double」を返しますが、以下のコードを
> 実行してみると、c3=c5 とはならず、c3には通貨型の精度で値が表示されます。
上記の、「通貨型の精度」という表現の意味がわかりませんでした。

そらから、このコードが何を表しているのかも、良く分からなかったのですが、これは、

 途中で、2進数の倍精度浮動小数点型処理が加わっているので、
 通貨型(4桁固定小数点型)←→倍精度浮動小数点型の変換に伴う誤差が発生する

 c3 と c5 では、途中の演算での丸め処理の発生位置が異なっているので、
 両者は必ずしも同値を返すわけでは無い

……という2つの点を実験したものなのでしょうか?
(すみません、コードの意図が読み取れませんでした)

投稿時間:2005/05/24(Tue) 01:29
投稿者名:Starfish
Eメール:
URL :
タイトル:
Re^4:
 わかりにくくてすみません。

> >  しかしながら、計算した結果が制度の悪い型になるというのは考えられません。
> 制度→精度、の事だと解釈しますが、この場合の「精度が悪い」というのは、
> どのような意味で書かれていますか?
> 「精度が悪い」という表現には、幾通りかの解釈があるかと思います。
>
> たとえば、「数値として表現可能な範囲が狭い」(最小値と最大値の差が小さい)とか、
> 「値と値の幅が広く、細かい値を表現できない」(整数型なら1単位、通貨型なら0.0001単位)とか。

 「値と値の幅が広く、細かい値を表現できない」こちらの意味です。通貨型は、8バイトですが
符号部が1ビットで残りの63ビットで0.0001単位の数値を表します。double型は、同じ8バイトですが
1ビットの符号部、11ビットの指数部、52ビットの仮数部ですので、通貨型の最大値に近い値を
double型に代入すると、63ビット-52ビットの11ビットの情報がなくなってしまいます。
10進数でいうと、通貨型は整数部が15桁で小数部が4桁ですが、double型の有効な桁数は15桁です。
従って、通貨型の最大値をdouble型に代入すると、ちょうど小数部分の情報が不正確な値になって
しまいます。

> >  『Debug.Print TypeName(10@ / 1@)』は、「Double」を返しますが、以下のコードを
> > 実行してみると、c3=c5 とはならず、c3には通貨型の精度で値が表示されます。
> 上記の、「通貨型の精度」という表現の意味がわかりませんでした。
>
> そらから、このコードが何を表しているのかも、良く分からなかったのですが、これは、

 通貨型/通貨型が何型の答えを返すかを調べようとしていました。(^^;

 簡単にすると、

Private Sub Command1_Click()
Dim a As Currency
Dim b As Double
    a = 922337203685465.5807@ / 1@
    Debug.Print a
    b = 922337203685465.5807@ / 1@
    a = b
    Debug.Print a
End Sub

 通貨型/通貨型が、倍精度浮動小数点数型で返るなら、aとbは同じ(微妙に違ってくる
可能性はあるかもしれませんが)結果になるはずです。しかしながら、結果は、
a: 922337203685465.5807
b: 922337203685465.625
となり、aは正しく表示されますが、bは小数以下の値が不正確なものとなっています。
 通貨型/通貨型の結果は、10進型で計算されていると思います。ただし、
割り算の結果をVariant型に入れると、倍精度浮動小数点数型を含むバリアント型に
なるみたいですが。

投稿時間:2005/05/24(Tue) 13:13
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^5:
# 以下で書いているのは、「Intの結果が変わることがある」という問題についてではなく、
# 「浮動小数点型の誤差」についての話題です。m(_ _)m


> double型の有効な桁数は15桁です。

これは、10進数表現での15桁、という意味ですよね。

間違ってはいないと思いますが、15桁以内ならば誤差が出ないというわけではないので、
元質問者の方(まだ見てるのかな)に対しては、この表現だと誤解を生むかも……。


たとえば「0.6」という値でも誤差は発生しますよね。「0.5」なら誤差はでませんが。

通貨型は、内部的には64bit整数(1万倍した値として格納)であり、0.5 も 0.6 も格納誤差は出ません。
10進型は、内部的には10進数であり、10進数表現の 0.5 や 0.6 を、やはり誤差無く格納できます。

しかし倍精度浮動小数点型は、内部的には2進数で処理されます。10進数表現の 0.5 であれば、
2進数表現の 0.1 として誤差無く格納できますが、10進数表現の 0.6 になってくると、
2進数では 0.10011001100110011…… という循環小数になってしまい、52ビットの仮数部に
収める際に、近似値ゆえの丸め誤差が生じます。

  Dim A As Double, B As Double, C As Double, D As Double
  A = 6.0#
  B = 10.0#
  C = 0.6#
  Debug.Print (A / B) - C      '誤差を含み、「2.22261445359528E-17」になってしまった
  Debug.Print CDbl(A / B) - C  'この場合は、CDbl()で変換補正がかかり、「0」になる模様
  D = A / B
  Debug.Print D - C            'これも変換補正がかかるのか、「0」になるらしい

# あれ……。変換補正という用語でよかったんでしたっけ?


>  通貨型/通貨型が何型の答えを返すかを調べようとしていました。(^^;
う〜ん。「何型で返すか」は、先の TypeName で明らかでは無いでしょうか。
内部的にどのように演算されているかは、確認できませんけれども。

# 拡張倍精度浮動小数点演算を利用して、演算時は80bitで処理されているかもしれませんし、
# あるいはStarfishさんの説のように、Decimalで処理されているのかも知れませんが、
# それを確認することは、私にはできません……(マシン語読めないし)


> 割り算の結果をVariant型に入れると、倍精度浮動小数点数型を含むバリアント型に
それはつまり、実は10進型で返されているのだけれども、VarType / TypeName で
検証した際にはVariantを経由するため、Double になってしまう……という事でしょうか。
そのような例外があるとは思いませんでしたが、もしも本当にそうなのだとしたら、
10進型で返されている事を検証するのは、面倒そうですね。

投稿時間:2005/05/19(Thu) 14:42
投稿者名:花ちゃん
Eメール:
URL :
タイトル:
Re^4: MsgBox Int(0.0282 * 10000)
質問される前に、ここの初めにお読み下さい はご覧になられたのでしょうか?
hhttp://www.bcap.co.jp/hanafusa/hazimeni.htm#no2
そこにも書いてありますが、質問される前に必ず自分で下調べをするように
して下さい。(宣言 して投稿して頂いているはずですから)
詳しくは書いておりませんが、ここの逆引きヘルプにも書いてありますし、調べれば
色々出てくるかと思います。

とりあえず、VB 付属のヘルプで [浮動小数点]をキーワードに検索して、場所 が
サポート技術情報のところを読み漁って下さい。

# 初めにお読み下さい の中にヘルプの便利な使い方も書いていますのでご覧下さい。

投稿時間:2005/05/20(Fri) 13:12
投稿者名:Int
Eメール:
URL :
タイトル:
Re^5: MsgBox Int(0.0282 * 10000)
> 質問される前に、ここの初めにお読み下さい はご覧になられたのでしょうか?
すみません。読んでいませんでした。
重ね重ね、申し訳ございません。m(__)m
どうかこれからもよろしくお願いいたします。m(__)m

> とりあえず、VB 付属のヘルプで [浮動小数点]をキーワードに検索して、場所 が
> サポート技術情報のところを読み漁って下さい。
目的の情報がありそうな雰囲気です。
ありがとうございます。

投稿時間:2005/05/22(Sun) 09:22
投稿者名:花ちゃん
Eメール:
URL :
タイトル:
バージョンは同じですが、 281 です。
VB6.0(SP6) Windows XP(Pro SP2)
OleAut32.DLL =5.1.2600.2180
MSVBVM60.DLL =6.00.9782

VB6.0(SP6) Windows XP(home SP2)
VB6.0(SP5) Windows98(SE)
VB5.0(SP3) Windows95
以上 4 台で確認しましたが結果、初回から 281 にしかなりません。

VB を起動して 下記コードをペーストして実行
尚、PCを再起動しても同じでした。

投稿時間:2005/05/22(Sun) 16:37
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re: バージョンは同じですが、 281 です。
# スレッドが深くなってきた……。

「Debug.Print Int(0.0282 * 10000)」の実験結果ですが、

> VB6.0(SP6) Windows XP(Pro SP2)
> OleAut32.DLL =5.1.2600.2180
> MSVBVM60.DLL =6.00.9782
当方もこれですね。やはり、再現しません(常に281でした)。

同環境で、Excel 2003 VBA で試してみても「常に281」でした。
う〜む。とすると、後は何が違うのだろう?


# 蛇足までに書いておくと、Win2000 Pro/SP4 (OLEAUTO32.DLL 2.40.4522.0) の環境にて、
# VB4.0a (32bit) や Access 97/SR-1などで試した場合も、やはり「常に281」でした。