openlang

An empty cell equals zero and the empty string

Inherited a macro that flags order rows with a zero quantity, and it flagged every blank row too. The check was If ws.Cells(r, 4).Value = 0 Then. A blank cell hands back a Variant holding Empty, and Empty compares equal to 0 (and to "", if you write the other check, so you can't win that way either). Nobody had declared anything, naturally, so the whole macro ran on Variants and the comparison just did what Variants do.

The quick fix is IsEmpty before the comparison. Fine. But it made me count how many ways a VBA value can mean "nothing here". Empty from a blank cell, Null from an Access field, Nothing for an object variable, "", 0, and an error value if the cell shows #N/A. Six spellings, and each needs a different test.

How do you all model "no value" in VBA? Convert at the edge into typed variables and a flag, or keep Variants and test with VarType everywhere?

The conversation continues 7

Follow the reasoning
  1. Permalink

    Read the cell once and decide what it is in one place. After that the rest of the macro gets a typed value and never sees a Variant again.

    Dim v As Variant
    v = ws.Cells(r, 4).Value2
    
    Select Case VarType(v)
        Case vbEmpty
            hasQty = False
        Case vbDouble
            hasQty = True
            qty = CDbl(v)
        Case vbString
            hasQty = False
            LogOddCell ws, r, v
        Case Else
            hasQty = False
            LogOddCell ws, r, v
    End Select

    Value2 matters here. With Value, a cell formatted as currency or a date arrives as vbCurrency or vbDate and drops into Case Else. An error cell shows up as vbError, which also lands in Case Else, so it gets logged rather than counted. LogOddCell stands for whatever you already do with skipped rows.

  2. Permalink

    The person typing into that sheet has more ways to make "nothing" than the list has. They press Delete, and it's Empty. They tap the space bar to clear it, and it's a one-character string that looks blank on screen. They type '0 because the zero kept vanishing in some format, and now it's the text "0", which a vbDouble branch skips. All three look identical when you scroll past them. Is there a cheap way to show the user which cells the macro skipped, a fill colour or something, so the space-bar ones get fixed at the source?

  3. Permalink

    Got curious whether Empty has a type of its own or just borrows one, and the Immediate window answer is that it borrows whatever the expression around it wants. Empty + 1 is 1, Empty & "x" is "x", and Empty = 0 and Empty = "" are both True. So it isn't really a value that equals zero. It's a value that turns into zero or into an empty string depending on who's asking, which is why the = 0 test looked like it worked for so long. The interesting question is which test notices the difference, and as far as I can tell only IsEmpty and VarType do.

  4. Permalink

    I'd add one stage in front of that. Read the whole column into an array first, classify every cell, and only then compute, so the totals never start from a partly checked range. data = rng.Value2 gives a two-dimensional array with one element per cell, which is also much quicker than a Cells call per row.

    One snag there. If the range is a single cell, Value2 returns the value itself, not an array, and data(1, 1) fails. So check IsArray(data) right after the read, and wrap the single value yourself before the classify step. Then the counts of empty, numeric and odd cells are known before anything is added up.

  5. Permalink

    Count before fixing. On a sheet like that I'd want three numbers. Cells where IsEmpty is true, cells where it isn't but the value is "", and everything else. The middle group is the one people forget. A formula like =IF(B2="","",B2*C2) shows nothing, IsEmpty says False because the cell holds a formula, and VarType says vbString. Excel's COUNTBLANK counts those cells as blank, so a check against the worksheet count and a check in the macro can disagree, and the gap is that group.

  6. Permalink

    Ha, Tcl has only one nothing, the empty string! The catch runs the other way. A missing value and legitimately empty text are the same string, so you check info exists or dict exists instead of the value.

  7. Permalink

    Ran your three counts before touching anything, and that changed the story. Of the rows the macro flagged, only a handful were truly empty. The rest were formula blanks from a helper column somebody added (with a header that just says "x", thank you, whoever you were). So my IsEmpty fix would have caught almost none of them, and I'd have called it done.

    Went with smallprint's Select Case on Value2, plus a Case vbString branch that treats "" as no quantity and anything else as a cell to log. The log sheet now has eleven entries, all of them the space-bar kind kim_dh described.

Other sides of the same question

  1. Units of measure as cheap ids

    For integer ids I've been trying units of measure instead of single-case unions, and I'd like a second opinion before it spre…

    Replies 1fsharp
  2. A helper ported from Java picks a different overload in Groovy

    Moved a Java helper class into Groovy on the build script side, and the same call went to a different method. Laid out in ord…

    Replies 3groovyjava
  3. Validating JSON before the discriminated union

    A type assertion only tells the compiler what to believe. An external response starts as bytes and says nothing about its var…