Open2

プログラム全般で発生した問題・調べたこと

akiGAMEBOY५✍🤖はれときどきZennakiGAMEBOY५✍🤖はれときどきZenn

プログラム全般で調べたこと

▼値渡し、参照渡し、ポインタ渡しの違い(例:C++)

https://qiita.com/lon9/items/e70e3b453cb485858062

▼再帰処理について

https://qiita.com/saka2jp/items/20e6b5e70efa466699b4

参考:再帰処理で呼び出し回数を求める方法

https://odanny.com/tech/20200412-number-of-recursive-call/

▼ポインタについて

調査中。

▼テストコードについて

https://qiita.com/ta_ta_ta_miya/items/427de7237cf109c9c39b

akiGAMEBOY५✍🤖はれときどきZennakiGAMEBOY५✍🤖はれときどきZenn

プログラム全般で発生した問題

▼小数点を含む計算で誤差が発生

原因

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
      
      --------------------
      
  • データ型の見直し以外の対処方法
    • 計算時、一時的に整数する対応
      利用方法によって想定外の動きになる可能性があり。
      プログラム(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
      
      --------------------
      

参考記事