Excel (VBA)

Excel VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(指定なし : 指定なし)
同じ時間差なのに、結果が違う
投稿日時: 22/10/14 21:37:10
投稿者: taichi

 
Sub text()
    Dim T As Date, ABC As String
   
    T = CDate("18:00") - CDate("12:00") '差は Just 6:00 だから Cが表示される 当然!
' T = CDate("14:00") - CDate("08:00") '差は同じ 6:00 なのに B が表示される ---> 何故?
            
    Select Case T
    Case Is <= CDate("6:00")
        ABC = "C"
    Case Is > CDate("6:00"), Is <= CDate("8:00")
        ABC = "B"
    Case Is > CDate("8:00")
        ABC = "A"
    End Select
     
    MsgBox ABC
 
End Sub

回答
投稿日時: 22/10/14 23:13:54
投稿者: WinArrow
投稿者のウェブサイトに移動

時刻の計算で、気を付けなければいけないこ。
 
コンピュータは2進数で計算しているので、
目視できない部分にも、数値があります。
 
6:00を数値に変換すると、0.25になります。
Excelでは、15ケタの数値しかきないので、小数点15ケタ以下に
どのような数値があるのか分かりませんが、有効桁で丸めることが必要です。
 
例えば、↓こんな感じです。
 
Dim T As Double
    T = CDbl(CDate("14:00") - CDate("8:00"))
    T = WorksheetFunction.Round(T, 10)
     
    If T <= CDbl(CDate("6:00")) Then
        MsgBox "A"
 

回答
投稿日時: 22/10/15 00:06:12
投稿者: 半平太

小数演算誤差の話になるんですが、
本件は、差が6:00(0.25)なので、切りが良すぎ (特殊)。
一般論で考えるには、不適切でしょう。
 
たとえば、こんな例で考える方がいい気がします。(4:00差)
 T = CDate("18:00") - CDate("14:00")  
 'T = CDate("14:00") - CDate("10:00")
 
さて、どんな対策がいいでしょうかねぇ・・

回答
投稿日時: 22/10/15 02:34:08
投稿者: MMYS

すでにレスがある通り、コンピュータは2進数ですので、小数点誤差があります。
つまり、小数点の比較にイコールは使用出来ません
 
 
ところで、コードから条件は以下の通りです。
 

・6時間以下       ⇒ C
・6時間を超え8時間以下 ⇒ B
・8時間を超える     ⇒ A

ただし、上記では、以上を含むので条件式にイコールが必要です。
しかし、この条件は以下のように解釈出来ます。
 
・8時間を超える      ⇒ A
・6時間を超え(8時間以下) ⇒ B
・上記以外         ⇒ C

このように判定条件を変えるとイコールは不要です。また、時刻変数を直接計算はおすすめしません。専用関数を使用すべきです。
 
 
Sub text()
    Dim T As Date, ABC As String
    Dim s As Long
    
    s = DateDiff("s", CDate("12:00"), CDate("18:00"))
   's = DateDiff("s", CDate("08:00"), CDate("14:00"))
    
    Select Case TimeSerial(0, 0, s)
        Case Is > CDate("8:00")
            ABC = "A"
        Case Is > CDate("6:00")
            ABC = "B"
        Case Else
            ABC = "C"
    End Select
     
    MsgBox ABC
 
End Sub

なお、例えば、
9時間の時は、A条件が「真」なのでA条件で終了。BやCは実行さされません。同じく、
8時間の時は、A条件は「偽」次のB条件が「真」なのでB判定で終了
6時間の時は、A条件は「偽」、B条件は「偽」です。結果、C判定となります。
 
つまり、大きな数値から判定することで、〇時間以下の条件判定は不要です。
 
 

回答
投稿日時: 22/10/15 13:07:36
投稿者: 半平太

ちょっとしっくり来ないなぁ・・

    Select Case TimeSerial(0, 0, s)
        Case Is > CDate("8:00")
            ABC = "A"
        Case Is > CDate("6:00")
            ABC = "B"
        Case Else
            ABC = "C"
    End Select
それが成立するなら、これでも同じであり、イコールは使用可能。
    Select Case TimeSerial(0, 0, s)
        Case Is <= CDate("6:00")
            ABC = "C"
        Case Is <= CDate("8:00")
            ABC = "B"
        Case Else
            ABC = "A"
    End Select

以下、試案
Sub Trial()
    Dim T As Date, ABC As String
    
    T = CDate("18:00") - CDate("12:00")
'    T = CDate("14:00") - CDate("08:00")
'    T = CDate("18:00") - CDate("14:00")
'    T = CDate("14:00") - CDate("10:00")
    
    'h:mmの時刻文字化後、再度時刻データ化
    T = CDate(Format(T, "h:nn"))
    
    Select Case T
        Case Is <= CDate("6:00") 'CDate("4:00")
            ABC = "C"
        Case Is <= CDate("8:00")
            ABC = "B"
        Case Else
            ABC = "A"
    End Select
     
    MsgBox ABC
End Sub

回答
投稿日時: 22/10/15 22:34:36
投稿者: MMYS

半平太 さんの引用:
ちょっとしっくり来ないなぁ・・

 
日付型って、実態は浮動小数点数ですよね。
 
イコールは全ビットが同じ時、「真」です。ほとんど同じだけど、わずかでも違うと、「偽」になります。浮動小数点数も、完全一致の時、「真」です。下記の例は「偽」ですが、なぜか理解してますか。
 
Sub test()
  If 0.1 + 0.2 = 0.3 Then
    MsgBox "真"
  Else
    MsgBox "偽"
  End If
End Sub

 
時刻は、秒と分が60進法。時は24進法です。開示の例では、専用関数を使用して、計算誤差を出ないようにしています。提示の例では、判定に日付型を渡してますから、VBA側の内部が、日付として論理計算されていれば、問題は起きないでしょう。
 
しかし、日付型の実態は浮動小数点数ですから、判定にイコールを使うと、特定の条件でバグに悩まされるかもしれません。バグがおきるかもしれないコードは、避けるのが無難だと思います。
 
 
なお、参考までに、Excelの数式も小数点判定に、イコールは使用出来ません。
 
Excelにも誤差がある? 浮動小数点演算誤差の正しい回避法
https://www.newssalt.com/32247
 
 

回答
投稿日時: 22/10/15 23:21:21
投稿者: 半平太

私がしっくりこないのは、この部分です。
 
>それが成立するなら、これでも同じであり、イコールは使用可能。
 
わざわざ不等号だけのものに変更する必要がないと言うことです。

投稿日時: 22/10/16 07:40:16
投稿者: taichi

>コンピュータは2進数で計算しているので、
>目視できない部分にも、数値があります。
私の知らない事ばかりです (-_-;)
 
目的は予定労働時間に対して法定の休憩時間を取らせることです。
45分と1時間の休憩ですが、それがB、Cに相当します。
 
労働時間は5分単位で記録されているので
敢えて判定の式に1秒を加えて現実の問題を回避する
方法を思いついたのですが、だめでしようか?
 
一応、下の結果は 2 つとも C が表示されているのですが、
他の場合でも正しい判定になるか今一つ自信が持てていません。
 
Sub Test()
    Dim T As Date, ABC As String
     
   T = CDate("18:00:00") - CDate("12:00:00")
' T = CDate("14:00:00") - CDate("08:00:00")
     
    Select Case T
    Case Is < CDate("6:00:01")
        ABC = "C"
    Case Is > CDate("6:00:02"), Is < CDate("8:00:01")
        ABC = "B"
    Case Is > CDate("8:00:02")
        ABC = "A"
    End Select
 
    MsgBox ABC
 
End Sub
 
 
 

回答
投稿日時: 22/10/16 09:42:52
投稿者: 半平太

>労働時間は5分単位で記録されているので
>敢えて判定の式に1秒を加えて現実の問題を回避する
>方法を思いついたのですが、だめでしようか?
 
OKです。それが実践的な解決策です。
 
> Case Is < CDate("6:00:01") 
’つまり、それによって、6:00前後の小数演算誤差(+方向にあっても、
’勿論マイナス方向でも)を吸収できます。
 
> Case Is > CDate("6:00:02"), Is < CDate("8:00:01")
’違和感あります。
 
 Case Is < CDate("8:00:01")
’だけでいいです。CDate("6:00:01") 以上である事は、
’上の第一関門を通って来ているので自明です。
 
> Case Is > CDate("8:00:02")
’これが普通です。(残り全てのケースで適用なので)
’ ↓
  Case Else
 
書くとしても、Case Is >= CDate("8:00:01") でしょう。
結果に違いは出ないですが、第2関門(8:00:01未満)の次のゾーンですから。

投稿日時: 22/10/16 11:33:32
投稿者: taichi

WinArrow さん、半平太 さん、MMYS さん ありがとうございました。
 
1秒を追加し、Case Else を使って判定式を書き換えることにします。
 
MMYS さん
Sub test()
  If 0.1 + 0.2 = 0.3 Then
    MsgBox "真"
  Else
    MsgBox "偽"
  End If
End Sub
何故「偽」になるのですか? ご教授下さい。

投稿日時: 22/10/16 11:45:28
投稿者: taichi

MMYS さん
 
秒単位は扱わないので、下のように変更しても問題ないですか?
 
Sub text()
    Dim T As Date, ABC As String
    Dim n As Long
     
' n = DateDiff("n", CDate("12:00"), CDate("18:00"))
  n = DateDiff("n", CDate("08:00"), CDate("14:00"))
     
    Select Case TimeSerial(0, n, 0)
        Case Is > CDate("8:00")
            ABC = "A"
        Case Is > CDate("6:00")
            ABC = "B"
        Case Else
            ABC = "C"
    End Select
      
    MsgBox ABC
  
End Sub

回答
投稿日時: 22/10/16 17:00:48
投稿者: WinArrow
投稿者のウェブサイトに移動

taichi さんの引用:
WinArrow さん、半平太 さん、MMYS さん ありがとうございました。
 
1秒を追加し、Case Else を使って判定式を書き換えることにします。
 
MMYS さん
Sub test()
  If 0.1 + 0.2 = 0.3 Then
    MsgBox "真"
  Else
    MsgBox "偽"
  End If
End Sub
何故「偽」になるのですか? ご教授下さい。

 
0.1 や 0.2 のようにデータ型を省略すると、Double型とみなされます。
  
Double型の小数点以下計算には、計算誤差が発生するものと考えましょう。
  
0.1 + 0.2 の計算誤差が発生しているか確認する方法
計算結果を10倍して少数点以下を取り出す。
Sub test()
Dim AA, BB
    AA = CDbl(0.1)
    BB = CDbl(0.2)
    If (AA + BB) * 10 - Int((AA + BB) * 10) <> 0 Then
        Debug.Print (AA + BB) * 10 - Int((AA + BB) * 10)
        MsgBox "小数点以下に値あり"
    Else
        MsgBox "小数点以下に値なし"
    End If
End Sub

 
  
上記の場合、single型で計算すると計算誤差は回避できる
  
Sub test()
Dim AA, BB
    AA = CSng(0.1)
    BB = CSng(0.2)
    If (AA + BB) * 10 - Int((AA + BB) * 10) <> 0 Then
        MsgBox "小数点以下に値あり"
    Else
        MsgBox "小数点以下に値なし"
    End If
End Sub

投稿日時: 22/10/16 17:24:42
投稿者: taichi

WinArrow さん、半平太 さん、MMYS さん ありがとうございました。
 
>Double型の小数点以下計算には、計算誤差が発生するものと考えましょう。
 
式が論理的に合っていれば問題ないとばかり思っていました。
思いもよらない事ばかりのお話で大変勉強になりました。
 
今後ともよろしくお願いいたします。