Tcl Part I · Module 4 · Operators and Expressions
Numeric Comparison, and the == Trap
Comparison looks like the simplest thing in this module and contains the module's worst trap. The six operators behave exactly as you expect on numbers. The problem is that == does not only compare numbers. When its operands do not look numeric it quietly falls back to comparing text, which means the same operator in the same line of code can ask two different questions depending on what the data happens to contain that day. This chapter shows exactly when each happens; the next chapter gives you the operator that does not do this.
Foundation12 min readTclPart IOperatorsComparisonEquality
Part I · Module 4 · Chapter 4.3 · Numeric Comparison, and the == Trap
1. The Six Operators
puts "10 == 10 = [expr {10 == 10}]"
puts "10 != 9 = [expr {10 != 9}]"
puts "10 < 9 = [expr {10 < 9}]"
puts "10 <= 10 = [expr {10 <= 10}]"
puts "10 > 9 = [expr {10 > 9}]"
puts "10 >= 11 = [expr {10 >= 11}]"10 == 10 = 1
10 != 9 = 1
10 < 9 = 0
10 <= 10 = 1
10 > 9 = 0
10 >= 11 = 0Each returns 1 or 0 — which, from Chapter 3.6, are values Tcl accepts as true and false. That is where almost every condition you write gets its value.
2. Numbers Compare as Numbers
The useful half first. == compares the numeric values, not the text:
puts "10 == 10.0 = [expr {10 == 10.0}]"
puts "1e2 == 100 = [expr {1e2 == 100}]"
puts "0x10 == 16 = [expr {0x10 == 16}]"
puts "\" 5\" == \"5\" = [expr {" 5" == "5"}]"10 == 10.0 = 1
1e2 == 100 = 1
0x10 == 16 = 1
" 5" == "5" = 1All four are true, and in all four the text on the two sides is different.
10 and 10.0 are different characters and the same number. 1e2 and 100 likewise. 0x10 is hexadecimal for 16. And " 5" has a leading space that the numeric reading ignores — Chapter 3.5 §5.
This is normally exactly what you want. A measurement that arrives as 10.0 should equal a threshold written as 10.
3. The Trap
Now what happens when the operands are not numeric:
puts "\"abc\" == \"abd\" = [expr {"abc" == "abd"}]"
puts "\"abc\" == \"abc\" = [expr {"abc" == "abc"}]"
puts "\"abc\" < \"abd\" = [expr {"abc" < "abd"}]""abc" == "abd" = 0
"abc" == "abc" = 1
"abc" < "abd" = 1No error. == compared them as text instead.
So the rule is:
==compares numerically when both operands look like numbers, and compares as text when they do not.
One operator, two questions, and the data decides which one you asked.
4. Why That Is Dangerous
A comparison that changes meaning based on its input is a comparison you cannot reason about by reading it.
proc same {a b} {
return [expr {$a == $b}]
}
puts "same 10 10.0 -> [same 10 10.0]"
puts "same abc abc -> [same abc abc]"
puts "same 007 7 -> [same 007 7]"
puts "same v1.0 v1.00 -> [same v1.0 v1.00]"same 10 10.0 -> 1
same abc abc -> 1
same 007 7 -> 1
same v1.0 v1.00 -> 0Four calls to one procedure, and it asked a numeric question twice and a text question twice. Nothing in the source tells you which.
Look at the last two in particular. 007 and 7 are reported as the same because both read as numbers. v1.0 and v1.00 are reported as different because neither does. If these were version strings, the first result is wrong; if they were counts, the first is right. The operator cannot know, and neither can a reader.
5. Ordering Has the Same Split
< and > behave the same way:
puts "9 < 10 = [expr {9 < 10}]"
puts "\"9\" < \"10\" = [expr {"9" < "10"}]"
puts "\"apple\" < \"banana\" = [expr {"apple" < "banana"}]"
puts "\"Apple\" < \"apple\" = [expr {"Apple" < "apple"}]"9 < 10 = 1
"9" < "10" = 1
"apple" < "banana" = 1
"Apple" < "apple" = 1The first two give the same answer for different reasons — one numerically, one because both read as numbers anyway.
Note the last line: uppercase sorts before lowercase, because text comparison uses character codes. That is worth knowing before you use < on anything human-readable.
6. When == Is the Right Choice
None of this means avoid ==. It means use it when you mean a numeric question:
set retries 3
set limit 3
if {$retries >= $limit} {
puts "limit reached"
}
set temperature 20.0
puts "at threshold: [expr {$temperature == 20}]"limit reached
at threshold: 1Both are genuinely numeric comparisons, and the 20.0 against 20 case is exactly the flexibility you want.
Two cautions even then:
- Do not use
==on measured real numbers.0.1 + 0.2 == 0.3is false, and Chapter 4.10 is entirely about why. - Validate first if the input is untrusted. Chapter 3.11 covers the guard.
7. The Fix Is the Next Chapter
If you want a comparison that always asks the same question, Tcl has one: eq and ne compare as text and never switch.
That is Chapter 4.4, and it is short — the hard part was understanding why you need it, which is this chapter.
8. Common Mistakes
Using == on identifiers, names or codes. They may be numeric today. §4.
Assuming == is a string comparison because the operands are quoted. Quotes are grouping — Chapter 2.7 — and do not affect the comparison.
Using == on real numbers. Chapter 4.10.
Using < on human-readable text without thinking about case. §5.
Expecting an error when the operands are not numbers. There is none; it falls back silently. §3.
9. Exercises
Exercise 1 — the six. Predict and check all six operators with the values 7 and 7, then 7 and 8.
Exercise 2 — which question was asked? For each, say whether == compared numerically or as text, then check:
expr {100 == 1e2}
expr {"100" == "100.0"}
expr {"abc" == "ABC"}
expr {"0x0A" == 10}
expr {"v2" == "v2"}Exercise 3 — make it change meaning. Write a single expr {$a == $b} line. Choose values for a and b that make it true as a numeric comparison, then choose different values that make the same line do a text comparison. You have just demonstrated the trap.
Exercise 4 — the version-string bug. You are comparing release identifiers. Show a pair where == gives the answer you want and a pair where it does not. What property of the data decides?
Exercise 5 — ordering and case. Sort out, by prediction then by test, which of these are true: "B" < "a", "a" < "b", "10" < "9", 10 < 9.
Exercise 6 — state the rule. In one sentence, and without using the word "sometimes", say what == does.
10. Remember
- Six operators:
== != < <= > >=. Each returns1or0. ==compares numerically when both operands look like numbers, so10 == 10.0,1e2 == 100and0x10 == 16are all true.- When they do not look numeric, it silently compares text instead.
- So the data decides which question you asked, and a reader of the line cannot tell.
- This breaks when data changes shape — identifiers that become all-digits start comparing numerically.
<and>split the same way, and text ordering puts uppercase before lowercase.- Use
==when you mean a numeric question, and not on measured real numbers. eqandneare the operators that never switch. Next chapter.
Where this fits
Part of the Tcl curriculum.
