Tcl Part I · Module 3 · Values, Variables, and Expressions
Numbers: How Tcl Decides Something Is Numeric
Values carry text, so before you do arithmetic on something that came from outside your script you often want to know whether it will work as a number. Tcl gives you a direct way to ask. This chapter covers that, and two traps that are worth more than the rest of the page put together: the obvious way to write the check quietly accepts an empty value, and a number written with a leading zero does not mean what it looks like.
Foundation13 min readTclPart IValuesNumbersValidation
Part I · Module 3 · Chapter 3.5 · Numbers: How Tcl Decides Something Is Numeric
1. Why Ask At All
A value is text until a command reads it as something else — Chapter 3.1. Arithmetic is one such reading, and it fails if the text is not numeric:
set field "n/a"
puts [expr {$field * 2}]can't use non-numeric string as operand of "*"That error is good when it happens during development and bad when it happens overnight on line 40,000 of a data file. Often you want to look first and decide what to do.
2. string is
The command that asks:
puts [string is integer -strict "42"]
puts [string is double -strict "1e-3"]
puts [string is integer -strict "n/a"]1
1
0string is integer asks "would this work as a whole number?". string is double asks "would this work as a real number?".
3. What Each One Accepts
Worth running once so the categories are concrete:
foreach v {42 -7 1e-3 3.14 0x1F n/a 007} {
puts [format "%-6s integer=%s double=%s" $v \
[string is integer -strict $v] \
[string is double -strict $v]]
}42 integer=1 double=1
-7 integer=1 double=1
1e-3 integer=0 double=1
3.14 integer=0 double=1
0x1F integer=1 double=1
n/a integer=0 double=0
007 integer=1 double=1Three things to take from that table:
- Every integer is also a valid double.
string is doubleis the broader question. - Scientific notation counts as a double, not an integer.
- Hexadecimal counts as an integer.
0x1Fpasses, andexprreads it as 31.
4. The Trap: -strict Is Not Optional
Here is the most important thing on this page. Leave -strict off and the empty string passes:
puts "empty, no -strict: integer=[string is integer {}] double=[string is double {}]"
puts "empty, -strict: integer=[string is integer -strict {}]"empty, no -strict: integer=1 double=1
empty, -strict: integer=0Without -strict, string is integer "" is 1.
Think about what that means for a guard. You write the check specifically to catch bad input. An empty field — a blank column, a missing value, a line that did not parse — is the most common bad input there is. And the check passes it.
And the empty value does not fail later either, necessarily — it may produce a wrong number instead of an error, which is worse:
set field ""
puts "is integer (no -strict): [string is integer $field]"
puts [expr {$field + 0}]is integer (no -strict): 1
can't use empty string as operand of "+"Here expr did catch it. You cannot rely on that for every command, and the check that said "this is an integer" was wrong regardless.
5. Whitespace Is Accepted
A smaller surprise, and useful to know when parsing columns:
set field " 42 "
puts "is double -strict: [string is double -strict $field]"
puts "arithmetic: [expr {$field + 0}]"is double -strict: 1
arithmetic: 42Surrounding spaces are tolerated by both the check and by expr. You do not have to trim before testing — though trimming anyway makes the value easier to print and compare.
6. The Leading Zero
Now the second trap, and this one silently changes answers.
puts "expr 010 = [expr {010}]"
puts "expr 0x1F = [expr {0x1F}]"
puts "expr 0b101 = [expr {0b101}]"
puts "expr 0o17 = [expr {0o17}]"expr 010 = 8
expr 0x1F = 31
expr 0b101 = 5
expr 0o17 = 15010 is 8, not 10. A leading zero means octal — base 8.
This matters because zero-padded numbers are everywhere in real data: 007, 010, 0900. If such a field reaches expr, you get a wrong answer with no error at all, unless the digits happen to include an 8 or a 9:
puts [catch {expr {08 + 1}} m]
puts $m1
missing operator at _@_
in expression "0_@_8 + 1";
looks like invalid octal numberSo 010 gives a wrong answer silently, while 08 fails loudly — and Tcl's message even guesses the cause for you. That inconsistency is what makes it dangerous.
7. The Fix: scan
scan reads a number out of text in a base you choose, and %d means decimal:
puts [scan "010" %d]
puts [scan "007" %d]
puts [expr {[scan "010" %d] + 1}]10
7
11010 read as decimal is 10, as you wanted.
Two practical approaches when handling zero-padded fields:
scan $field %dwhen you know the field is meant to be decimal. Explicit and version-proof.- Strip leading zeros as text before arithmetic, if you prefer to keep the value as text until the end.
The point is to decide deliberately rather than let a leading zero decide for you.
8. Tcl Integers Do Not Overflow
A pleasant surprise after the two traps:
puts [expr {2**62}]
puts [expr {9223372036854775807 + 1}]4611686018427387904
9223372036854775808That second number is one past the largest signed 64-bit integer, and Tcl computed it exactly. Tcl integers have arbitrary precision — they grow as needed rather than wrapping around.
If you have come from C or SystemVerilog, this removes a whole category of bug. It also means you cannot use overflow to detect a value being too large; if you need a range check, write one.
9. Common Mistakes
Omitting -strict. The check then passes empty input. §4. This is the one to remember.
Assuming a zero-padded field is decimal. §6. Use scan.
Testing with string is integer when you meant to allow decimals. 3.14 fails that check. Use double unless you specifically require a whole number.
Expecting integer overflow. §8. Tcl integers grow.
Testing the wrong thing. string is integer tells you the text would work as a number. It does not tell you the value is sensible — -999999 passes.
10. Exercises
Exercise 1 — build the table. Run the §3 loop and add these values: " 5 ", "", 1_000, +7, .5, 5.. Which surprised you?
Exercise 2 — break a guard on purpose. Write a check using string is integer without -strict, feed it an empty value, and confirm it reports the value as a valid integer. Then add -strict and confirm it now reports 0.
Exercise 3 — the octal trap. Predict the output of expr {010 + 010} before running it. Then produce the answer you actually wanted, using scan.
Exercise 4 — loud or silent? Explain in one sentence why 010 is more dangerous than 08.
Exercise 5 — a reusable guard. Write three lines that take a variable field and print either the value doubled, or the message not numeric: <value>. Test it with 42, 3.5, "" and n/a.
Exercise 6 — big numbers. Compute 2 to the power 100 and print it. Then say what a C program using a 64-bit integer would have produced.
11. Remember
string is integerandstring is doubleask whether text would work as a number.- Always write
-strict. Without it the empty string passes every class, which breaks exactly the guard you were writing. - Every integer is also a valid double. Hexadecimal counts as an integer; scientific notation counts only as a double.
- Surrounding whitespace is accepted by both the check and
expr. - A leading zero means octal on this interpreter —
010is 8 — and the behaviour has varied across releases. - Use
scan $field %dto read a zero-padded field as decimal, everywhere and in every version. 08errors while010is silently wrong. The silent one is the problem.- Tcl integers have arbitrary precision and do not overflow.
Next: the other thing a value gets read as — true or false.
Where this fits
Part of the Tcl curriculum.
