The Tale of trimZeros, or UI: Detail Counts

Continuing in a similar vein to my last post (Dragging Roads), another post on UI code. My CS professor has a few labs that we come back to and build on over the course of the year — one of these is an RPN calculator, which we built last semester (and which has become a coding kata for me in several languages), and which we are now developing GUIs for: first with Swing, and now on Android. (Spoilers: no, nothing in today’s post is actually related to Android UI in particular.)

Although the calculator code itself works on doubles, as you would expect, the UI stores the currently entered number as a String. This seems like a dirty hack (it is), but in order to support floating point entry and a backspace key, it’s the simplest means. The alternative, using exponentiation to place each digit in the right place on a double, has more nasty corner cases than are worth dealing with in the timeframe of a lab exercise. This String-backed UI has some consequences, namely that the user can input values that are either (a) invalid or (b) look wrong. Today’s post examines (b) — more specifically, removing unnecessary leading zeros.

Trimming zeros is easy, right?

private void trimZeros() {
    if (currentEntry.length() < 1) return;
    
    int i;

    for (i = 0; currentEntry.length() > i 
             && currentEntry.charAt(i) == '0'; ++i);

    currentEntry = currentEntry.substring(i);
}

(currentEntry is the String used to store the current entered number.)

This is somewhat more “clever” than it probably should be, but it’s still straightforward code — find the index of the first non-‘0’ character in the currentEntry string, then lop off the zeros all at once with a substring call.

However, it doesn’t quite preform how we want:

|  Input  |  Output  |
+---------+----------+
|  "0123" |   "123"  |
| "0.123" |  ".123"  |
|   "000" |      ""  |
|     "0" |      ""  |

Leading zeros on integers are trimmed properly, but zero values are reduced to empty strings, and decimal values lose the leading zero. With the exception of the empty strings, these are perfectly valid — feed them into #parseDouble and we’ll get the right double out, but we can do better.

private void trimZeros() {
    if (currentEntry.length() < 1) return;
    
    int i;
    int decimalPoint = currentEntry.indexOf('.');

    for (i = 0; currentEntry.length() - 1 > i 
             && currentEntry.charAt(i) == '0'; ++i) {

        if (i + 1 == decimalPoint) break;
    }

    currentEntry = currentEntry.substring(i);
}

Let’s check our table again:

|  Input  |  Output  |
+---------+----------+
|  "0123" |   "123"  |
| "0.123" | "0.123"  |
|   "000" |     "0"  |
|     "0" |     "0"  |

We’ve made two changes here: first, we never try to consume the last character in the string. Therefore, if we have an input of “00”, we only chop off the first ‘0’, leaving “0” — exactly what we want. Second, we look up the location of the decimal point (if there is one), and ensure we stop one character short of it, preserving a leading zero before the decimal point.

(An aside for those not well versed in Java: #indexOf returns -1 for a character not in the string. Because i starts at 0 and is only incremented, we can guarantee that i + 1 != -1 (overflow excepted — it’s not ever a problem here).)

Minor UI polish like this is not particularly difficult — the total diff between these two methods is 3 lines of code. However, issues like this exist in every system and piece of data that comes close to a user. It’s worth investing the time to hunt them down and fix them, though — users may not notice a well-behaved UI, but they will notice an awkward one.

Advertisements