- PR -

Int関数について

投稿者投稿内容
ふるふる
会議室デビュー日: 2008/02/05
投稿数: 19
投稿日時: 2008-04-01 00:04
1回目と2回目で結果が違う、というのはよく分からない仕様ですが、以下の掲示板でちょっと気になる発言があったので、引用します。
http://www2.moug.net/bbs/exvba/20080330000005.htm
以下、引用

たとえば、
Debug.Print VBA.Int(0.3 + 0.7) ' 結果は1
Debug.Print Int(0.3 + 0.7) ' 結果は0

前者は通常のVBAの関数呼び出しですから、
演算結果の値を引数(Variant型)へ格納
する時にDoubleの精度に丸められて、
関数には 1 が渡ることになりますが、

引用ここまで。

Unibonさんのコードで
e = Int(a * b)
で結果が e = 743
になっていましたが、VBA.Intを使うと e = 744 になりました。(VBA: Retail 6.5.1.24)

Currency型などを使う方法がよく対策にありますが、接頭語VBAを使うというのもあるんですね。
もとはーどや
会議室デビュー日: 2008/03/26
投稿数: 2
投稿日時: 2008-04-30 16:39
VB6とVB.NETで計算結果が異なることについて
不思議に思ったので、下記プログラムをVB.net2003および、
同様のプログラムをVB6で実行してみました。

また、VB6の浮動小数点演算では、少なくとも仮数部を64bitで内部演算している
ようです

以下は、VB.net2003で確認しています。VB2005でも動くと思います
コード:
        Dim i As Long, j As Double, k As Double
        Dim l As Long
        Dim b As Boolean
        Dim d As Decimal
        dim x as double,y as double
        '最初のループ
        FileOpen(10, "result.txt", OpenMode.Output)
        k = 0
        For i = 0 To 10000
            PrintLine(10, " i= " & i.ToString(), _
                    "   0.15 * i = " & (i * 0.15).ToString(), " k= " & k.ToString(), _
                    "   0.15 * i = " & (i * 0.15).ToString("E17"), " k= " & k.ToString("E17"), _
                    " k - (i * 0.15)= " & (k - i * 0.15).ToString("E17"))
            k = k + 0.15
        Next
        FileClose(10)

        '2番目のループ
        FileOpen(10, "result2.txt", OpenMode.Output)
        For i = 0 To 10000
            j = 0.15 * i : d = 0.15D * i
            PrintLine(10, " i= " & i.ToString(), "   0.15 * i = " & (i * 0.15).ToString(), _
                    " j(=0.15*i)= " & j.ToString(), " d(=0.15D*i)= " & d.ToString(), _
                    "j - (0.15 * i)= " & (j - (0.15 * i)).ToString(), _
                    "d - (0.15 * i)= " & (d - (0.15 * i)).ToString(), _
                    "j - d= " & (j - d).ToString())
        Next
        FileClose(10)

        '3番目のループ
        FileOpen(10, "result3.txt", OpenMode.Output)
        For i = 0 To 10000
            j = 0.15 * i : d = 0.15D * i
            b = (d = j)
            PrintLine(10, " i= " & i.ToString(), "  j(=0.15*i)= " & j.ToString(), "d(=0.15*i)= " & d.ToString(), _
                     "int(0.15*i)= " & (Int(0.15 * i)).ToString(), "int(j)= " & (Int(j)).ToString())
        Next
        FileClose(10)


最初のforループは、小数点の加算による誤差の累積のチェックと、printしたときの
".ToString()"と."ToString("E17")"の得られる結果の違いをみました。

VB.net2003やVB2005では、
結果を出力する場合(Double型の変数を10進数に変換して表示するとき)、
Doubleの有効桁15.95桁のうち、10進数の16桁目の値を四捨五入して丸めている
ことがわかりました。

最初のループの結果です
コード:
 i= 43    0.15 * i = 6.45                       k= 6.45    
          0.15 * i = 6.45000000000000020E+000   k= 6.45000000000000460E+000
     k - (i * 0.15)= 4.44089209850062620E-015
     
 i= 44    0.15 * i = 6.6                        k= 6.60000000000001
          0.15 * i = 6.59999999999999960E+000   k= 6.60000000000000500E+000
     k - (i * 0.15)= 5.32907051820075140E-015


このように.ToString()でデフォルトの変換を行うと、10進数の16桁目を四捨五入して
丸めているようです。

VB6でも同様なことを実行すると、最初のループのように出力する方法が分かりませんでしたが、
三番目のループで
コード:
 i=  19   j=  2.85  d=  2.85  Int(j(=0.15*i))= 2   int(=0.15*i)=  2   int(d)= 2
 i=  20   j=  3     d=  3     Int(j(=0.15*i))= 3   int(=0.15*i)=  2   int(d)= 3
 i=  21   j=  3.15  d=  3.15  Int(j(=0.15*i))= 3   int(=0.15*i)=  3   int(d)= 3


と、i=20でint(0.15 * i)を直接計算した結果と
int(j) (0.15*iを一旦変数"j"に代入後にint()関数を実行した)の結果が、異なる値となりました。
これは iの値が20の倍数で周期的に起きています。(十進数の計算では、答えが整数になるiの値)
unibonさんの
引用:
すなわち、
c = a * b
d = Int(c)

e = Int(a * b)
で結果が異なるのです。


と同じ様な結果となりました、

VB6で同様に実行した二番目のループでは(コンパイルしないで実行)
コード:
 i= 3  (0.15 * i)=  0.45   j(=0.15*i)= 0.45   d= 0.45   j-(0.15*i)=-2.77555756156289E-17   d-(0.15*i)= 0   d-j= 0
 i= 4  (0.15 * i)=  0.6    j(=0.15*i)= 0.6    d= 0.6    j-(0.15*i)= 0                      d-(0.15*i)= 0   d-j= 0
 i= 5  (0.15 * i)=  0.75   j(=0.15*i)= 0.75   d= 0.75   j-(0.15*i)= 2.77555756156289E-17   d-(0.15*i)= 0   d-j= 0

 i=2052 (0.15 * i)= 307.8  j(=0.15*i)= 307.8  d= 307.8  j-(0.15*i)= 2.27595720048157E-14   d-(0.15*i)= 0   d-j= 0
 i=2053 (0.15 * i)= 307.95 j(=0.15*i)= 307.95 d= 307.95 j-(0.15*i)= 2.77555756156289E-17   d-(0.15*i)= 0   d-j= 0
 i=2054 (0.15 * i)= 308.1  j(=0.15*i)= 308.1  d= 308.1  j-(0.15*i)=-2.27040608535845E-14   d-(0.15*i)= 0   d-j= 0


と、i=5やi=2053 で"j-(0.15*i)= 2.77555756156289E-17"の値になっています。
この値は2進数で
1.111111111111111111111111111111111111111111111111101110011101101b*10b^-111000b
(マイクロソフトのPowerToyの"Power Calculator"の結果より)
(http://www.microsoft.com/windowsxp/downloads/powertoys/xppowertoys.mspx)
≒ 1*2^-55(10進数)

という値です。また、i=2053のときの 0.15*i の整数部は308なので、二進数で
9bitと合計64bitの桁数が必要になります。VBの倍精度浮動小数点型は、MSDN Helpより
引用:
倍精度浮動小数点数型 (Double)
64 ビットの数値として、浮動小数点数を保持するデータ型。
負の値の場合、有効範囲は -1.79769313486232E308 〜 -4.94065645841247E-324 です。
正の値の場合、有効範囲は 4.94065645841247E-324 〜 1.79769313486232E308 です。
Visual Basic では、型宣言文字 # は、倍精度浮動小数点数型 (Double) を表します。


と、IEEEの倍精度と同等の表現のため、Doubleの仮数部は53bit(52bit+1bit)と思われ、
Doubleだけでは、i=2053 で"j-(0.15*i)=-2.77555756156289E-17"となることはありません。

これは、unibonさんの
http://hanatyan.sakura.ne.jp/logbbs1/wforum.cgi?mode=allread&no=3276&page=0
の中にもありましたが、多分 インテルのプロセッサの浮動小数点ユニット(x87 FPU)の
内部精度が、倍精度の64bitより多い 80bit(仮数部64bit)で計算されているためでは
ないかと、思います。

VC++とインラインアセンブラでFPU命令を実行した場合、1)の場合の差分
"j-(0.15*i)"の計算結果と、VB6の計算結果が一致しているようです。

なお、FPU命令を直接実行した時のi=20のint(0.15*i)の結果は、
コード:
 1)一旦、VC++の倍精度型に0.15を代入後、FPUで
    int(0.15*i)を計算、                                  結果: int(0.15*i)=2
 2) 1)と同様に、倍精度型に0.15を代入後、FPUで 0.15*iを
    計算後、倍精度型変数に代入の後、代入した値を
    再びFPUレジスタに格納し、FPUでint()を実行、          結果: int(0.15*i)=3
 3) FPUで、15/100を実行して0.15を計算後、FPU内で
    int(0.15*i)を計算、                                  結果: int(0.15*i)=3
 4) FPUのコントロールレジスタをIEEEの倍精度相当に設定し、
    1)を実行                                             結果: int(0.15*i)=3


1)と2)で結果が異なるのは、FPU内では4)のように計算精度を指定しない場合は、常に
80bit(仮数部64bit)で計算が行われ、倍制度型の変数に代入するときに「丸め」が、
行われるためと思われます。
1),2)では、i=20での0.15*iの値は、"3"より小さい値ですが、倍精度への丸めにより
"3"より大きな値となるためと思われます。(同様なことがVB6でも行われているものと
思われます)
コード:
               0        1         2         3         4         5         6
               12345678901234567890123456789012345678901234567890123456789012345
 0.15=     0.00100110011001100110011001100110011001100110011001100110011001101b
                                  54bit目が"0"なので切り捨てられる  ^ 
     =        1.0011001100110011001100110011001100110011001100110011b*10b^-11b
 0.15*20= 0.15*2^2 + 0.15*2^4
    0.15*2^4 =1.0011001100110011001100110011001100110011001100110011b*10b^1b
 +) 0.15*2^2 =0.010011001100110011001100110011001100110011001100110011b*10b^1b
 ----------------------------------------------------------------------------
              1.011111111111111111111111111111111111111111111111111111b*10^1b
              ここでint()関数を実行すると、答えは "2"
              倍精度に丸めると、IEEEでは                            ^
              「Unbiased 最も近い値に丸める。表現可能なふたつの値のちょうど
              中間だった場合、LSBがゼロ(つまり偶数)の値を採用する。
              このモードをデフォルトにすべきであることが規定されている。」
              1.1000000000000000000000000000000000000000000000000000b*10b^1b
              となりint()関数を実行すると、答えが"3"になる


いままで、十進数の"0.15"を倍精度に変換するとき、有効bit数に収まらない場合は
切り下げていると思っていましたが、上述のように、IEEE 754 標準規格に沿った
"Unbiased"で丸めているようです。
http://ja.wikipedia.org/wiki/IEEE_754
http://ja.wikipedia.org/wiki/%E7%AB%AF%E6%95%B0%E5%87%A6%E7%90%86
を、参照

その他、下記のURLも、参考になりました。
http://docs.sun.com/source/806-4847/ncg_goldberg.html

あと、Decimal型とCurrency型の内部表現の考え方はおもしろいですね、
最初はBCDで表現していると思いましたが、整数型を拡張した型なのですね。

スキルアップ/キャリアアップ(JOB@IT)