Skip to content
VLSI Mentor

Tcl Part I · Module 4 · Operators and Expressions

Math Functions Inside expr

Alongside its operators, expr understands a set of functions — rounding, absolute value, minimum and maximum, square roots and the trigonometric family. They are written like function calls and they are not Tcl commands, which is the first thing to get straight. The second, and the one that causes actual bugs, is that some of them hand back whole numbers and some hand back real numbers, and the difference shows up when you print or compare the result.

Foundation11 min readTclPart IOperatorsMathRounding

Part I · Module 4 · Chapter 4.8 · Math Functions Inside expr

1. They Are Not Commands

Azvya Education Pvt. Ltd.VLSI Mentor
not-commands.tcl
puts [catch {abs -7} m]
puts $m
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1
invalid command name "abs"

There is no abs command. abs exists only inside an expression:

Azvya Education Pvt. Ltd.VLSI Mentor
inside.tcl
puts [expr {abs(-7)}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
7

This is Chapter 3.7 again: expr has its own small language, and these functions belong to it. The parentheses are part of that language, not Tcl syntax.

2. The Set You Will Use

Azvya Education Pvt. Ltd.VLSI Mentor
common.tcl
puts "abs(-7)      = [expr {abs(-7)}]"
puts "abs(-7.5)    = [expr {abs(-7.5)}]"
puts "min(3, 9, 4) = [expr {min(3, 9, 4)}]"
puts "max(3, 9, 4) = [expr {max(3, 9, 4)}]"
puts "round(2.6)   = [expr {round(2.6)}]"
puts "int(2.9)     = [expr {int(2.9)}]"
puts "entier(2.9)  = [expr {entier(2.9)}]"
puts "floor(2.9)   = [expr {floor(2.9)}]"
puts "ceil(2.1)    = [expr {ceil(2.1)}]"
puts "sqrt(16)     = [expr {sqrt(16)}]"
puts "double(7)    = [expr {double(7)}]"
puts "fmod(7.5, 2) = [expr {fmod(7.5, 2)}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
abs(-7)      = 7
abs(-7.5)    = 7.5
min(3, 9, 4) = 3
max(3, 9, 4) = 9
round(2.6)   = 3
int(2.9)     = 2
entier(2.9)  = 2
floor(2.9)   = 2.0
ceil(2.1)    = 3.0
sqrt(16)     = 4.0
double(7)    = 7.0
fmod(7.5, 2) = 1.5

min and max take any number of arguments. fmod is the remainder for real numbers, which Chapter 4.1 §4 said % refuses to do.

3. Which Ones Return Whole Numbers

Read the output above again, looking only at the decimal points. This is the part worth memorising:

Returns a whole numberReturns a real number
round()floor()
int()ceil()
entier()sqrt()
abs() of a whole numberdouble()
abs() of a real number

floor(2.9) is 2.0, not 2. sqrt(16) is 4.0, not 4.

4. The Four Ways to Reach a Whole Number

They differ on negatives, so choose deliberately:

Azvya Education Pvt. Ltd.VLSI Mentor
rounding.tcl
foreach v {2.9 2.5 -2.5 -2.9} {
    puts [format "%5s   round=%-4s int=%-4s floor=%-6s ceil=%-6s" $v \
        [expr {round($v)}] [expr {int($v)}] [expr {floor($v)}] [expr {ceil($v)}]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  2.9   round=3    int=2    floor=2.0    ceil=3.0
  2.5   round=3    int=2    floor=2.0    ceil=3.0
 -2.5   round=-3   int=-2   floor=-3.0   ceil=-2.0
 -2.9   round=-3   int=-2   floor=-3.0   ceil=-2.0
  • round() — nearest, and away from zero on an exact half.
  • int() — towards zero. This is C-style truncation.
  • floor() — always down.
  • ceil() — always up.

Note round(-2.5) is -3 while int(-2.5) is -2. If you have been using them interchangeably on positive numbers, negatives will separate them.

5. A Rounding Result Worth Understanding

This one looks like a bug and is not:

Azvya Education Pvt. Ltd.VLSI Mentor
surprise.tcl
puts "2.675 * 100         = [expr {2.675 * 100}]"
puts "round(2.675 * 100)  = [expr {round(2.675 * 100)}]"
puts "format %.2f 2.675   = [format %.2f 2.675]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2.675 * 100         = 267.5
round(2.675 * 100)  = 268
format %.2f 2.675   = 2.67

Two reasonable ways to round 2.675 to two decimal places, and they disagree — 2.68 against 2.67.

Here is why, and it is worth following:

Azvya Education Pvt. Ltd.VLSI Mentor
why.tcl
puts "2.675 stored as: [format %.20f 2.675]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2.675 stored as: 2.67499999999999982236

2.675 cannot be stored exactly, so what you really have is a hair under 2.675. format %.2f works from that stored value and correctly rounds down to 2.67.

But multiplying by 100 produces a result that has to be rounded to the nearest storable value, and that lands on exactly 267.5. round() then takes the half upwards and gives 268.

Neither function is wrong. They were given slightly different numbers. Chapter 4.10 is entirely about this.

6. Trigonometry and Logarithms

Present, and rarely needed in scripting, so one block for completeness:

Azvya Education Pvt. Ltd.VLSI Mentor
trig.tcl
puts "acos(-1)   = [expr {acos(-1)}]"
puts "sin(0)     = [expr {sin(0)}]"
puts "log(2.718281828459045) = [expr {log(2.718281828459045)}]"
puts "log10(1000) = [expr {log10(1000)}]"
puts "pow(2, 10)  = [expr {pow(2, 10)}]"
puts "hypot(3, 4) = [expr {hypot(3, 4)}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
acos(-1)   = 3.141592653589793
sin(0)     = 0.0
log(2.718281828459045) = 1.0
log10(1000) = 3.0
pow(2, 10)  = 1024.0
hypot(3, 4) = 5.0

Angles are in radians. acos(-1) is the usual way to obtain pi, since Tcl has no built-in constant for it.

pow(2, 10) is 1024.0 where 2 ** 10 is 1024 — the function always works in real numbers.

7. Random Numbers

Azvya Education Pvt. Ltd.VLSI Mentor
random.tcl
expr {srand(42)}
puts "first run:  [expr {rand()}]"

expr {srand(42)}
puts "same seed:  [expr {rand()}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
first run:  0.5245871020129822
same seed:  0.5245871020129822

rand() returns a real number from 0 up to but not including 1. srand(seed) sets the starting point, and the same seed gives the same sequence — which is the property worth knowing, because it is what makes a run reproducible.

For a whole number in a range:

Azvya Education Pvt. Ltd.VLSI Mentor
random-int.tcl
expr {srand(7)}
puts "1 to 6: [expr {int(rand() * 6) + 1}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 to 6: 6

int() rather than round(), because round() would make the top and bottom values half as likely.

8. Common Mistakes

Calling a function as a command. sqrt 16 is not a command. §1.

Expecting floor() and sqrt() to return whole numbers. They return reals. §3.

Using round() for display. Use format %.2f. §5.

Treating round() and int() as the same. They differ on negatives. §4.

Using round() to pick a random integer. It skews the ends. §7.

Expecting rand() to be unpredictable without seeding. If you need reproducibility, seed deliberately.

9. Exercises

Exercise 1 — whole or real? Without running, say which of these print a decimal point: abs(-3), abs(-3.0), round(3.7), floor(3.7), sqrt(25), int(3.7), double(3). Then check.

Exercise 2 — the four roundings. Build the §4 table yourself for the values 1.5, -1.5, 0.5 and -0.5. Which two functions never disagree on positives but always disagree on negatives?

Exercise 3 — format versus round. Produce 3.14 from pi two ways: once with format, once with arithmetic and round(). Which would you use in a report?

Exercise 4 — the 2.675 case. Reproduce §5. Then try 2.665 and 2.685 the same way. Do they all disagree, or only some?

Exercise 5 — a dice roll. Write a reproducible roll of two six-sided dice that prints the same result every run. Then make it different each run, and say what you changed.

Exercise 6 — remainder of a real number. expr {7.5 % 2} fails. Get the answer with a function, and say why the operator refuses.

10. Remember

  • These are functions inside expr, not Tcl commands. abs -7 fails.
  • round(), int() and entier() return whole numbers. floor(), ceil(), sqrt() and double() return reals.
  • floor(2.9) is 2.0 and sqrt(16) is 4.0 — the decimal point matters for printing and for text comparison.
  • The four roundings differ on negatives: round to nearest away from zero, int towards zero, floor down, ceil up.
  • For display, use format %.2f, not round().
  • min and max take any number of arguments. fmod is the real-number remainder.
  • Angles are in radians, and acos(-1) gives pi.
  • The same srand() seed gives the same sequence, which is how you make a run reproducible.

Next: testing membership of a list, inside an expression.

Where this fits

Part of the Tcl curriculum.