Open2
プログラム全般で発生した問題・調べたこと
プログラム全般で調べたこと
▼値渡し、参照渡し、ポインタ渡しの違い(例:C++)
▼再帰処理について
参考:再帰処理で呼び出し回数を求める方法
▼ポインタについて
調査中。
▼テストコードについて
プログラム全般で発生した問題
▼小数点を含む計算で誤差が発生
原因
2進浮動小数点数(データ型 Double)を含んだ計算により丸め誤差が発生していた事が判明。
今回、誤差が発生した計算式「1.1 x 200」以外にも、「0.1 + 0.2」など他にも多くのパターンで誤差が発生する。
検証
実際に誤差が発生するのかExcel VBAで検証。
- 誤差あり「1.1 x 200」プログラム(VBA)
Sub SubMarume() Dim dblKitai As Double Dim dblResult As Double Dim dblValue As Double Dim lngValue As Long dblValue = 1.1 lngValue = 200 dblResult = dblValue * lngValue dblKitai = 220 Debug.Print "----- 出力結果 -----" Debug.Print "" If dblKitai = dblResult Then Debug.Print "判定結果:同じ" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print " 期待値 - 計算値 = " & dblKitai - dblResult Else Debug.Print "判定結果:違う" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print "220 - 220 = " & 220 - 220 Debug.Print "期待値 - 計算値 = " & dblKitai - dblResult End If Debug.Print "" Debug.Print "--------------------" Debug.Print "" End Sub
実行結果----- 出力結果 ----- 判定結果:違う 期待値:220 計算値:220 220 - 220 = 0 期待値 - 計算値 = -2.8421709430404E-14 --------------------
- 誤差なし「1.2 x 200」プログラム(VBA)
Sub SubMarume() Dim dblKitai As Double Dim dblResult As Double Dim dblValue As Double Dim lngValue As Long dblValue = 1.2 lngValue = 200 dblResult = dblValue * lngValue dblKitai = 240 Debug.Print "----- 出力結果 -----" Debug.Print "" If dblKitai = dblResult Then Debug.Print "判定結果:同じ" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print " 期待値 - 計算値 = " & dblKitai - dblResult Else Debug.Print "判定結果:違う" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print "240 - 240 = " & 240 - 240 Debug.Print "期待値 - 計算値 = " & dblKitai - dblResult End If Debug.Print "" Debug.Print "--------------------" Debug.Print "" End Sub
出力結果----- 出力結果 ----- 判定結果:同じ 期待値:240 計算値:240 期待値 - 計算値 = 0 --------------------
対処方法
- データ型を見直す対処
データ型の見直し以外にも対処が必要と思われる。
プログラムで小数点を取り扱う場合、有効とする小数点の桁数や、計算結果を判定する判定する場合は許容する誤差の範囲など、
取り決めるべき事項がある。
取り決め後、具体的な対応方法は指定の桁数までの値を四捨五入や切り捨て、切り上げなどが考えられる。- 小数4桁以内の場合、固定小数点数のデータ型(Currency)で対処プログラム(VBA)
Sub SubCurrency() Dim curKitai As Currency Dim curResult As Currency Dim curValue As Currency Dim lngValue As Long curValue = 1.1 lngValue = 200 curResult = curValue * lngValue curKitai = 220 Debug.Print "----- 出力結果 -----" Debug.Print "" If curKitai = curResult Then Debug.Print "判定結果:同じ" Debug.Print " 期待値:" & curKitai Debug.Print " 計算値:" & curResult Debug.Print "" Debug.Print " 期待値 - 計算値 = " & curKitai - curResult Else Debug.Print "判定結果:違う" Debug.Print " 期待値:" & curKitai Debug.Print " 計算値:" & curResult Debug.Print "" Debug.Print "220 - 220 = " & 220 - 220 Debug.Print "期待値 - 計算値 = " & curKitai - curResult End If Debug.Print "" Debug.Print "--------------------" Debug.Print "" End Sub
出力結果----- 出力結果 ----- 判定結果:同じ 期待値:220 計算値:220 期待値 - 計算値 = 0 --------------------
- 10進数の小数点を取り扱うデータ型(Decimal)で対処
VBAでは個別でDecimalがなくCDec
でDecimalに変換し、Variantに代入する必要がある。
C#ではデータ型にDecimalがある。プログラム(VBA)Sub SubDecimal() Dim decKitai As Variant Dim decResult As Variant Dim decValue As Variant Dim lngValue As Long decValue = CDec(1.1) lngValue = 200 decResult = decValue * lngValue decKitai = CDec(220) Debug.Print "----- 出力結果 -----" Debug.Print "" If decKitai = decResult Then Debug.Print "判定結果:同じ" Debug.Print " 期待値:" & decKitai Debug.Print " 計算値:" & decResult Debug.Print "" Debug.Print " 期待値 - 計算値 = " & decKitai - decResult Else Debug.Print "判定結果:違う" Debug.Print " 期待値:" & decKitai Debug.Print " 計算値:" & decResult Debug.Print "" Debug.Print "220 - 220 = " & 220 - 220 Debug.Print "期待値 - 計算値 = " & decKitai - decResult End If Debug.Print "" Debug.Print "--------------------" Debug.Print "" End Sub
出力結果----- 出力結果 ----- 判定結果:同じ 期待値:220 計算値:220 期待値 - 計算値 = 0 --------------------
- 小数4桁以内の場合、固定小数点数のデータ型(Currency)で対処
- データ型の見直し以外の対処方法
- 計算時、一時的に整数する対応
利用方法によって想定外の動きになる可能性があり。プログラム(VBA)Sub SubSeisu() Dim dblKitai As Double Dim dblResult As Double Dim dblValue As Double Dim lngValue As Long dblValue = 1.1 dblValue = dblValue * 10 ' 整数にする lngValue = 200 dblResult = dblValue * lngValue dblResult = dblResult / 10 ' 小数点に戻す dblKitai = 220 Debug.Print "----- 出力結果 -----" Debug.Print "" If dblKitai = dblResult Then Debug.Print "判定結果:同じ" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print " 期待値 - 計算値 = " & dblKitai - dblResult Else Debug.Print "判定結果:違う" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print "220 - 220 = " & 220 - 220 Debug.Print "期待値 - 計算値 = " & dblKitai - dblResult End If Debug.Print "" Debug.Print "--------------------" Debug.Print "" End Sub
出力結果----- 出力結果 ----- 判定結果:同じ 期待値:220 計算値:220 期待値 - 計算値 = 0 --------------------
- 四捨五入して対処プログラム(VBA)
Sub SubShishagonyu() Dim dblKitai As Double Dim dblResult As Double Dim dblValue As Double Dim lngValue As Long dblValue = 1.1 lngValue = 200 dblResult = dblValue * lngValue dblResult = Round(dblResult) ' 四捨五入 dblKitai = 220 Debug.Print "----- 出力結果 -----" Debug.Print "" If dblKitai = dblResult Then Debug.Print "判定結果:同じ" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print " 期待値 - 計算値 = " & dblKitai - dblResult Else Debug.Print "判定結果:違う" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print "220 - 220 = " & 220 - 220 Debug.Print "期待値 - 計算値 = " & dblKitai - dblResult End If Debug.Print "" Debug.Print "--------------------" Debug.Print "" End Sub
出力結果----- 出力結果 ----- 判定結果:同じ 期待値:220 計算値:220 期待値 - 計算値 = 0 --------------------
- 判定する際の許容範囲を設定し判定プログラム(VBA)
Sub SubKyoyou() Dim dblKitai As Double Dim dblResult As Double Dim dblValue As Double Dim lngValue As Long Dim dblKitei As Double ' 許容範囲 Dim dblGosa As Double ' 誤差の値 dblValue = 1.1 lngValue = 200 dblResult = dblValue * lngValue dblKitai = 220 dblKitei = 0.1 ' 許容範囲の設定 dblGosa = Abs(dblKitai - dblResult) ' 差分の絶対値 Debug.Print "----- 出力結果 -----" Debug.Print "" 'If dblKitai = dblResult Then If dblKitei > dblGosa Then ' 許容範囲で判定 Debug.Print "判定結果:同じ" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print " 期待値 - 計算値 = " & dblKitai - dblResult Else Debug.Print "判定結果:違う" Debug.Print " 期待値:" & dblKitai Debug.Print " 計算値:" & dblResult Debug.Print "" Debug.Print "220 - 220 = " & 220 - 220 Debug.Print "期待値 - 計算値 = " & dblKitai - dblResult End If Debug.Print "" Debug.Print "--------------------" Debug.Print "" End Sub
出力結果----- 出力結果 ----- 判定結果:同じ 期待値:220 計算値:220 期待値 - 計算値 = -2.8421709430404E-14 --------------------
- 計算時、一時的に整数する対応
参考記事
- 丸め誤差(わかりやすい解説)
https://blog.apar.jp/program/8900/ - 丸め誤差(浮動小数点数型の計算が期待通りにならない)
https://dobon.net/vb/dotnet/beginner/floatingpointerror.html - ExcelVBA
- データ型 Double
https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/double-data-type - データ型 Currency
https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/currency-data-type - データ型 Decimal
https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/decimal-data-type
- データ型 Double
- C#