Tcl Tutorial Lesson 6

Results of a command - Math 101

The Tcl command for doing mathematical calculations is expr. The following discussion of the expr command is extracted and adapted from the expr man page. Many commands use expr behind the scenes in order to evaluate test expressions, such as if, while and for, discussed in later sections. All of the advice given here for expr also holds for these other commands.

expr takes all of its arguments ("2 + 2" for example) and evaluates the result as a Tcl "expression" (rather than a normal command), and returns the value. The operators permitted in Tcl expressions include all the standard math functions, logical operators, bitwise operators, as well as math functions like rand(), sqrt(), cosh() and so on. Expressions almost always yield numeric results (integer or floating-point values).

Performance tip: enclosing the arguments to expr in curly braces will result in faster code. So do expr {$i * 10} instead of simply expr $i * 10. It is also safer, as illustrated at the end of this lesson.

Operands

A Tcl expression consists of a combination of operands, operators, and parentheses. White space may be used between operands, operators and parentheses; it is ignored by the expression processor. Where possible, operands are interpreted as integer values. Integer values may be specified in decimal (the normal case), in binary (if the first two characters of the operand are 0b), in octal (if the first two characters of the operand are 0o), or in hexadecimal (if the first two characters of the operand are 0x). For compatibility with older Tcl releases, an octal integer value is also indicated simply when the first character of the operand is 0, whether or not the second character is o.

Note that the octal and hexadecimal conversion takes place differently in the expr command than in the Tcl substitution phase. In the substitution phase, a \x32 would be converted to an ascii "2", while expr would convert 0x32 to a decimal 50.

If an operand does not have one of the integer formats given above, then it is treated as a floating-point number, if that is possible. Floating-point numbers may be specified in any of the ways accepted by an ANSI-compliant C compiler. For example, all of the following are valid floating-point numbers:

2.1
3.
6E4
7.91e+16
.000001

If no numeric interpretation is possible, then an operand is left as a string (and only a limited set of operators may be applied to it) but literal strings must be enclosed in double quotes.

Consider the following example: the variable number has a value 2,1, which is not interpreted as a valid numerical value. So instead it is regarding as a string. Then any operation is done with strings in mind.

% set number 2,1
% expr {$number > 2}    ;# True, because the string "2,1"
%                        # alphabetically (!) comes after "2"
1
% expr {$number > 2.0}  ;# False, because the string "2,1"
%                        # alphabetically comes before "2.0"
0

It is possible to deal with numbers in that form, but you will have to convert these "strings" to numbers in the standard form first.

Operands may be specified in any of the following ways:

  • As a numeric value, either integer or floating-point.
  • As strings enclosed in double quotes.
  • As a boolean (logical) value in one of the following forms: 1 or 0, true or false, yes or no, on or off.
  • As a Tcl variable, using standard $ notation. The variable's value will be used as the operand.

Operators

Some operators work on numbers in general, some work on integer numbers only and some work on strings only. Each category is listed below

Operations on numbers

The operators that work on numbers of any kind are listed below, grouped in decreasing order of precedence:

- +
Unary minus, unary plus
**
Exponentiation (works on both floating-point numbers and integers)
* /
Multiply, divide. For integers, see below
+ -
Add and subtract.
== != < > <= >= <
Relational operators: is equal, not equal, less, greater, less than or equal, and greater than or equal. Each operator produces 1 if the condition is true, 0 otherwise. These operators may be applied to numeric operands as well as strings, in which case string comparison is used.
    % set x 1
    % expr { $x>0? ($x+1) : ($x-1) }
    2

Operations on integer numbers

~ !
Bit-wise NOT, logical NOT. Same precedence as unary minus and unary plus.
%
Remainder. Same precedence as multiply and divide.
<< >>
Left and right (bit) shift. Precedence lower than add and subtract.
&
Bit-wise AND. Each pair of bits is subjected to the logical operation.
^
Bit-wise exclusive OR.
|
Bit-wise OR.

Note: When applied to integers, the division and remainder operators can be considered to partition the number line into a sequence of equal-sized adjacent non-overlapping pieces where each piece is the size of the divisor; the division result identifies which piece the divisor lay within, and the remainder result identifies where within that piece the divisor lay. A consequence of this is that the result of -57 / 10 is always -6, and the result of -57 % 10 is always 3.

Operations on strings

== != < > <= >= <
Relational operators: is equal, not equal, less, greater, less than or equal, and greater than or equal. Each operator produces 1 if the condition is true, 0 otherwise. With either one or two strings as operands these operations use string comparison (alphabetical-lexicorgraphical comparison, using the ASCII/UNICODE table).
eq ne in ni
Compare two strings for equality (eq) or inequality (ne) and two operators for checking if a string is contained in a list (in) or not (ni). These operators all return 1 (true) or 0 (false). Using these operators ensures that the operands are regarded exclusively as strings (and lists), not as possible numbers.

Note the difference between == and eq (analoguously != and ne):

% expr { "9" == "9.0"}   ;# Operands can be interpreted as numbers
1
% expr { "9" eq "9.0"}   ;# Now: no attempt is made to convert the
%                         # operands to numbers!
0

Logical operations

Expressions can be combined to form more complicated expressions:

&&
Logical AND. Produces a 1 result if both operands are non-zero, 0 otherwise. Valid for numeric operands only (integers or floating-point).
||
Logical OR. Produces a 0 result if both operands are zero, 1 otherwise. Valid for numeric operands only (integers or floating-point).
x?y:z
If-then-else. If x evaluates to non-zero, then the result is the value of y. Otherwise the result is the value of z. The x operand must have a numeric value:

For example:

% set x 1
1
% expr { $x % 2 ? "Odd" : "Even" }
Odd
% set y 3
% expr {$x > 1 || ($x < 2 && $y == 3)}
1

You can influence the order of evaluation using parentheses: expr {3+4*5} gives 23 and expr {(3+4)*5} gives 35.

Math functions

Tcl supports the following mathematical functions in expressions:

abs         acos        asin        atan
atan2       bool        ceil        cos
cosh        double      entier      exp
floor       fmod        hypot       int
isqrt       log         log10       max
min         pow         rand        round
sin         sinh        sqrt        srand
tan         tanh        wide

Besides these functions, you can also apply commands within an expression. For instance:

% set x 1
1
% set w "Abcdef"
Abcdef
% expr { [string length $w]-2*$x }
4

It is even possible to define your own math functions, though this is a somewhat advanced subject and requires some understanding of namespaces.

The above mathematical functions and operators also exist as independent commands:

% expr {sqrt(4)}
2.0
% ::tcl::mathfunc::sqrt 4    ;# sqrt lives in a separate "namespace",
%                             # "::tcl::mathfunc"
2.0

Type conversions

Tcl supports the following functions to convert from one representation of a number to another:

double int wide entier
  • double() converts a number to a double-precision floating-point number.
  • int() converts a number to an ordinary integer number (by truncating the decimal part).
  • wide() converts a number to a so-called wide integer number (these numbers have a larger range).
  • entier() coerces a number to an integer of appropriate size to hold it without truncation. This might return the same as int() or wide() or an integer of arbitrary size (in Tcl 8.5 and above).

The next lesson explains the various types of numbers in more detail.


Examples

Some mathematical expressions:

set X 100
set Y 256
set Z [expr {$Y + $X}]
set Z_LABEL "$Y plus $X is "

puts "$Z_LABEL $Z"
puts "The square root of $Y is [expr { sqrt($Y) }]\n"

puts "Because of the precedence rules \"5 + -3 * 4\"   is:"
puts "   [expr {-3 * 4 + 5}]"
puts "Because of the parentheses      \"(5 + -3) * 4\" is:"
puts "   [expr {(5 + -3) * 4}]"

  Resulting output
256 plus 100 is  356
The square root of 256 is 16.0

Because of the precedence rules "5 + -3 * 4"   is:
    -7
Because of the parentheses      "(5 + -3) * 4" is:
    8
set A 3
set B 4
puts "The hypotenuse of a triangle: [expr {hypot($A,$B)}]"

#
# The trigonometric functions work with radians ...
#
set pi6 [expr {3.1415926/6.0}]
puts "Sine and cosine of pi/6: [expr {sin($pi6)}] [expr {cos($pi6)}]"

  Resulting output
The hypotenuse of a triangle: 5.0
Sine and cosine of pi/6: 0.49999999226497965 0.8660254082502546
#
# Working with arrays
#
set a(1) 10
set a(2) 7
set a(3) 17
set b    2
puts "Sum: [expr {$a(1)+$a($b)}]"

  Resulting output

Sum: 17

Bracing your expressions

Consider the following commands:

% set userinput {[puts DANGER!]}
[puts DANGER!]
% expr $userinput == 1
DANGER!
0
% expr {$userinput == 1}
0

In the first example, the code contained in the user-supplied input is evaluated, whereas in the second the braces prevent this potential danger. As a general rule, always surround expressions with braces, whether using expr directly or some other command that takes an expression (such as if or while).

Numbers with a leading zero

Beware of leading zeros: 0700 is not interpreted as the decimal number 700 (seven hundred), but as the octal number 700 = 7*8*8 = 448 (decimal).

Worse, if the number contains a digit 8 or 9 an error results:

% expr {0900+1}
expected integer but got "0900" (looks like invalid octal number)

Octal numbers are in fact a relic of the past, when such number formats were much more common.

If you need to read in decimal data that might have leading zeros, then use the scan command to properly convert them into numbers without the above problems.