【问题标题】:Why is 31 >= 20 returning False here when comparing day?为什么比较天时 31 >= 20 在此处返回 False?
【发布时间】:2017-11-04 10:18:17
【问题描述】:

我正在调试这段代码,但我不确定为什么它返回 false 而不是 true。

?Day(i)>salday(0)
False
?Day(i)
 31 
?salday(0)
20
?isnumeric(day(i))
True
?isnumeric(salday(0))
True

Option Explicit
Option Compare Text

Sub genOP()

Dim wO As Worksheet
Dim i As Long, j As Long
Dim stDate, enDate, intVal, entR As Long, salDay, salAmt, stTime, enTime, dbMin, dbMax
Dim stRow As Long
Dim cet, curMn


'On Error Resume Next
Application.ScreenUpdating = False

stDate = STG.Range("B2"): enDate = STG.Range("B4")
intVal = Split(STG.Range("B3"), ","): entR = STG.Range("B5")
salDay = Split(STG.Range("B6"), "-")
salAmt = STG.Range("B7"): stTime = STG.Range("B8"): enTime = STG.Range("B9"): dbMin = STG.Range("B10"): dbMax = STG.Range("B11")

Set wO = ThisWorkbook.Sheets.Add
TEMP.Cells.Copy wO.Range("A1")

stRow = 19
curMn = Month(stDate)

For i = CLng(stDate) To CLng(enDate)

    If stRow > 19 Then
        wO.Rows(stRow & ":" & stRow).Copy
        wO.Rows(stRow + 1 & ":" & stRow + 1).Insert Shift:=xlDown
        Application.CutCopyMode = False
    End If

    cet = Trim(DESC.Range("A" & WorksheetFunction.RandBetween(2, DESC.UsedRange.Rows.Count)))

    If STG.Range("B14") = "ON" Then
       cet = cet & "Transaction amount " & Chr(34) & "&TEXT(H" & stRow & "," & Chr(34) & "#,##0.00" & Chr(34) & ")&" & Chr(34) & " GEL,"
    End If
    If STG.Range("B13") = "ON" Then
       cet = cet & Chr(34) & "&TEXT(B" & stRow & "-1," & Chr(34) & "dd mmm yyyy" & Chr(34) & ")&" & Chr(34)
    End If
    If STG.Range("B12") = "ON" Then
       cet = cet & " " & Format(stTime + Rnd * (enTime - stTime), "HH:MM AM/PM")
    End If

    If curMn = Month(i) And (Day(i) >= salDay(0) And Day(i) <= salDay(1)) Then  'Salary Day
        cet = Trim(DESC.Range("A" & WorksheetFunction.RandBetween(2, DESC.UsedRange.Rows.Count)))
        wO.Range("B" & stRow) = Format(i, "DD-MM-YYYY")
        wO.Range("I" & stRow) = salAmt
        wO.Range("L" & stRow) = MonthName(Month(i)) & "- Salome Baazov - " & "Geo" & " Ltd "
        curMn = WorksheetFunction.EDate(i, 1)
    Else
        wO.Range("B" & stRow) = Format(i, "DD-MM-YYYY")
        wO.Range("H" & stRow) = WorksheetFunction.RandBetween(dbMin, dbMax) + (WorksheetFunction.RandBetween(0, 1) * 0.5)
        wO.Range("L" & stRow) = "=" & Chr(34) & cet & Chr(34)
    End If

    stRow = stRow + 1
    i = i + intVal(WorksheetFunction.RandBetween(LBound(intVal), UBound(intVal))) - 1
Next i

wO.Rows(stRow).EntireRow.Delete
wO.Range("I" & stRow).Formula = "=SUM(I19:I" & stRow - 1 & ")"
wO.Range("H" & stRow).Formula = "=SUM(H19:H" & stRow - 1 & ")"

wO.Activate
Application.ScreenUpdating = True
STG.Range("B5") = stRow - 1
MsgBox "Process Completed"

End Sub

【问题讨论】:

  • Protip:处理日期时,请使用Date 类型和VBA.DateTime 模块中的函数。
  • 这里存在许多隐式输入问题。 Dim a, b, c, d As Fooabc 声明为隐式 Variant,并将 d 声明为 Foo。这会导致在你背后发生各种隐式类型转换;-)
  • @Mat'sMug 是的,我知道它们是 Variant 类型,但是为什么它还要在 Day(i) 结果中添加前导空格到 i 呢?仅仅是因为类型声明吗?
  • 没有多余的空间。输出到直接窗格的数字只是为负号留下一个位置。嗯,等一下。
  • @Mat'sMug 好吧,那么我们再次来到这个问题标题的同一个问题

标签: vba excel


【解决方案1】:

因为您正在比较两个不同类型的Variants(我们的讨论结果证明......谢谢@MatsMug)。比较不同类型的Variants时,比较结果是未定义行为,一个数字和一个字符串。

又是Variant anomalies.. 考虑一下这个 MCVE:

Sub Test1()
  Dim i, salday
  i = CDate("5/30/2017")
  salday = Split("20-20-20", "-")

  Debug.Print Day(i), salday(0)                  '  30    20
  Debug.Print Day(i) > salday(0)                 '  False
  Debug.Print Day(i) > CStr(salday(0))           '  True
  '                    ^^^^
  Debug.Print Val(Day(i)) > salday(0)            '  True
  '           ^^^^
End Sub

虽然 salday(0) 是 String Variant,但使用 CStr 将其显式转换为 String 解决了这个问题。但是,如果没有这种转换,比较就会失败。 VBA 没有将数字隐式转换为字符串,反之亦然。它比较了两个不同类型的变体并返回了一个垃圾结果。

有关变体诅咒的更多信息,请阅读For v=1 to v and For each v in v -- different behavior with different types

事实证明,使用CLngVal 强制进行数字比较是安全的方法,或者使用CStr 强制进行文本比较。


进一步考虑这三个简单的例子:

Sub Test1()
  Dim x, y: x = 30: y = "20"
  Debug.Print x > y               ' False !!
End Sub

Sub Test2()
  Dim x As Long, y: x = 30: y = "20"
  '       ^^^^^^
  Debug.Print x > y             ' True
End Sub

Sub Test3()
  Dim x, y As String:  x = 30: y = "20"
  '           ^^^^^^
  Debug.Print x > y             ' True
End Sub

如您所见,当变量(数字和字符串)都声明为变体时,比较是垃圾。当其中至少一个是显式的时,比较成功!

【讨论】:

  • 哦,是的,谢谢我错过了,我认为如果 isumeric 返回 true,那么它一定是一个数字
  • 但是为什么 Day(i) 会返回一个带空格的数字呢?
  • @newguy 它发生了 :) 但是那个领先的空间来自哪里?一个错误?确实很奇怪。
  • 这个 VBA 错误现在有一个 code inspection feature request in Rubberduck
  • @Mat'sMug 这很棒。我几乎可以肯定,RD 正在成为 VBA 专业编程的必备条件。
【解决方案2】:
Dim stDate, enDate

这条指令声明了两个Variant 变量。他们被分配到这里:

stDate = STG.Range("B2"): enDate = STG.Range("B4")

假设[B2][B4] 包含实际日期值,此时变量包含Variant/Date。那是因为这里的隐式代码如下:

stDate = STG.Range("B2").Value: enDate = STG.Range("B4").Value

但你可能已经知道了。继续前进。

salDay = Split(STG.Range("B6"), "-")

salDay 也是一个隐含的Variant。不过,该指令已加载完毕。这是隐式代码:

salDay = Split(CStr(STG.Range("B6").Value), "-")

这使得salDay 成为一个字符串数组。所以我们在这里:

?Day(i)
 31 
?salday(0)
20

31 前面的前导空格是因为直接窗格总是为负号留下一个位置。 salDay(0)String,没有前导空格。那就是你的线索。

?Day(i)>salday(0)
False

salday(0)String,我们在这里进行字符串比较 as was already pointed out。除了 31 前面没有领先空间;隐式代码是这样的,因为Day(i)的类型是Integer

?CStr(Day(i)) > salDay(0)
False

解决方案是完全摆脱salDay:你不需要它。假设[B6] 还包含一个实际日期,您可以立即将日期转换为Integer

?Day(STG.Range("B6").Value)

作为奖励,您可以将代码与工作表中基础日期值的字符串表示形式分离,因此更改 NumberFormat 不会破坏您的代码。总是这样对待日期!

【讨论】:

  • 隐式字符串转换,是的。
  • 很好的解释。因此,事实证明将 int 与字符串进行比较不会将任何一种类型转换为另一种类型以进行比较?!!奇怪的 VBA。
  • @A.S.H 确实如此......这就是我们最终进行字符串比较的原因,因为 一个 操作数是一个字符串 :-)
  • 但是?"31" &gt; "20" 返回True?
  • 为什么不呢?通过任何 alpha 排序,“3”大于“2”!
猜你喜欢
  • 1970-01-01
  • 2020-02-11
  • 1970-01-01
  • 1970-01-01
  • 2021-10-04
  • 1970-01-01
  • 2017-02-18
  • 2011-04-27
  • 1970-01-01
相关资源
最近更新 更多