Tcl Part I · Module 3 · Values, Variables, and Expressions
Integer and Floating-Point Division
Every other surprise in this module announces itself with an error. This one does not. When both sides of a division are whole numbers, Tcl gives you a whole number back, discarding the remainder — so a calculation that should produce 0.7 produces 0, and a percentage that should read 70% reads 0%. Nothing fails. The script runs, the report is written, and the number is wrong. This chapter shows the two correct fixes, the one that looks right and is not, and the rounding rule for negative numbers that differs from the language most engineers learned first.
Foundation13 min readTclPart IValuesArithmeticDivision
Part I · Module 3 · Chapter 3.10 · Integer and Floating-Point Division
1. The Result
puts [expr {10 / 4}]2Not 2.5. Not an error. Two.
2. The Rule
If both operands are whole numbers, Tcl divides as whole numbers and discards the remainder.
10 / 4 is 2 remainder 2, and the remainder is thrown away. You can see the discarded part with %:
puts "quotient: [expr {10 / 4}]"
puts "remainder: [expr {10 % 4}]"quotient: 2
remainder: 2This is the same rule C and many other languages use. What makes it dangerous in Tcl is Chapter 3.1: values carry no types, so you cannot look at a variable and tell whether it holds a whole number. You find out from the answer.
3. The Two Correct Fixes
Make one operand a real number. One decimal point is enough:
puts [expr {10.0 / 4}]
puts [expr {10 / 4.0}]2.5
2.5Convert explicitly with double(). This is what you need when the value is in a variable, where you cannot add a decimal point:
set a 10
set b 4
puts [expr {double($a) / $b}]2.5double() is a function in expr's language — Chapter 3.7 §8 — and it says exactly what you mean: treat this as a real number.
4. The Fix That Does Not Work
This is the first thing most people try, and it is worth seeing fail:
puts [expr {10 / 4 * 1.0}]2.02.0, not 2.5.
Multiplying by 1.0 converted the result after the division had already discarded the remainder. The information was gone before the conversion happened. You get a real number holding the wrong value, which is worse than an integer holding the wrong value because it looks like it was computed properly.
Convert before dividing, not after.
5. The Bug This Causes
Here it is in the shape you will actually meet:
set passed 7
set total 10
puts "wrong: [expr {100 * ($passed / $total)}]"
puts "right: [expr {100.0 * $passed / $total}]"wrong: 0
right: 70.0The first line is written the way the formula reads: a pass fraction, multiplied by 100. And 7 / 10 is 0 in whole numbers, so the whole thing is 0.
A pass rate of 0% from code that looks completely reasonable. No error, no warning.
The fix in the second line also shows the better habit: put the 100.0 at the front so the whole calculation is in real numbers from the start, rather than converting in the middle.
6. Negative Numbers Round Differently Than You Expect
Now the part that even experienced engineers get wrong, because it differs from C.
puts "-10 / 4 = [expr {-10 / 4}]"
puts "10 / -4 = [expr {10 / -4}]"
puts "-10 % 4 = [expr {-10 % 4}]"
puts "10 % -4 = [expr {10 % -4}]"-10 / 4 = -3
10 / -4 = -3
-10 % 4 = 2
10 % -4 = -2-10 / 4 is -3, not -2.
Tcl rounds toward negative infinity — it takes the next whole number down, always. So -2.5 becomes -3, not -2. C truncates toward zero and would give you -2.
And the remainder follows: in Tcl, % takes the sign of the divisor, so -10 % 4 is 2 and 10 % -4 is -2.
| Tcl | C | |
|---|---|---|
-10 / 4 | -3 | -2 |
-10 % 4 | 2 | -2 |
7. Rounding on Purpose
When you do want a whole number from a real one, choose the behaviour rather than letting division decide:
set x 2.5
set y -2.5
puts "round(2.5) = [expr {round($x)}]"
puts "round(-2.5) = [expr {round($y)}]"
puts "int(2.9) = [expr {int(2.9)}]"
puts "int(-2.9) = [expr {int(-2.9)}]"
puts "floor(-2.1) = [expr {floor(-2.1)}]"
puts "ceil(2.1) = [expr {ceil(2.1)}]"round(2.5) = 3
round(-2.5) = -3
int(2.9) = 2
int(-2.9) = -2
floor(-2.1) = -3.0
ceil(2.1) = 3.0round()goes to the nearest, away from zero on a tie.int()truncates toward zero — this is the C-style behaviour if you need it.floor()andceil()go down and up, and return real numbers.
Naming the one you want is clearer than relying on what division happens to do.
8. Common Mistakes
Dividing two variables and expecting a fraction. §5. You cannot tell from the variables whether they hold whole numbers.
Converting after the division. §4. The remainder is already gone.
Assuming C's negative rounding. §6. Tcl floors; C truncates.
Using / when you wanted %. If you want "how many left over", that is %.
Writing 100 * ($a / $b). Put the real number first: 100.0 * $a / $b.
9. Exercises
Exercise 1 — the basic surprise. Predict and then check expr {7 / 2}, expr {7.0 / 2}, expr {7 / 2.0} and expr {double(7) / 2}.
Exercise 2 — fix a percentage. This reports 0. Fix it two different ways:
set passed 3
set total 8
puts "[expr {100 * ($passed / $total)}]%"Exercise 3 — watch the late fix fail. Run expr {7 / 2 * 1.0} and explain the result in one sentence.
Exercise 4 — negatives. Predict all four before running:
puts [expr {-7 / 2}]
puts [expr {7 / -2}]
puts [expr {-7 % 2}]
puts [expr {-8 / 2}]The last one is not a trap — say why.
Exercise 5 — an average. Given set values {3 4 4} and set sum 11 and set n 3, compute the average correctly. Then compute it incorrectly, and say what a reader of the output would conclude.
Exercise 6 — choose your rounding. You need to split 17 items into groups of 5 and report how many groups are needed, including a partial one. Write it two ways: with ceil(), and using / and %. Which reads better?
10. Remember
- Two whole numbers divide to a whole number, remainder discarded.
10 / 4is 2. - No error is produced. This is the one arithmetic surprise that gives you a plausible wrong number.
- Fix it before dividing: write
10.0, or usedouble($a)when the value is in a variable. - Converting afterwards does not work.
10 / 4 * 1.0is 2.0. - Put the real number at the front of a formula:
100.0 * $a / $b. - Tcl rounds toward negative infinity.
-10 / 4is -3, where C gives -2. %takes the sign of the divisor, so-10 % 4is 2 — convenient for wrapping an index.- Choose rounding explicitly with
round(),int(),floor()orceil().
One chapter left in this module, and it brings the last four together: taking text from outside your script and turning it into a number you can rely on.
Where this fits
Part of the Tcl curriculum.
