Copyright © 2015 Arjen Markus. All rights reserved.
1. Introduction
In almost all programs you need to do some calculations with numbers or
check for a particular condition. Tcl’s approach to both is via the
expr
command, though you do not need to use it explicitly in the
context of for
, while
and if
. The syntax is quite what you are
used to from high-school mathematics, except that you use a "$" in
front of variables. Here is a very simple example:
for { set n 0 } { $n < 10 } { incr n } { puts "The square root of $n is: [expr {sqrt($n)}]" }
which produces the square roots of the integers 0 to 9. To access the
value of the variable n
we use $n
, just as in any ordinary Tcl code.
This is also true for accessing the value of array elements:
set i 0 expr {$a(b) + $number($i)}
adds the value of array element $i (=0) in the array "number" to the value of array element "b" in the array "a".
While Tcl’s basic syntax uses prefix notation, that is, the first word
identifies the command, the syntax of expr
’s expressions uses
'infix' notation. You might say that expr
defines its own sublanguage.
In this it is not unique: regular expressions for example form their own
sublanguage too, both dedicated to the problem these commands are
designed for.
If you prefer the prefix notation, you can use all operations and functions as if they were ordinary Tcl commands too. See the section Prefix notation.
This chapter assumes that you use Tcl 8.5 or later. Some of the features discussed here do not exist in Tcl 8.4 or earlier. |
2. Conditions - implicit use of the expr
command
The commands if
, for
and while
use the expr
command implicitly:
if { $x > 0 } { puts "x is positive" }
It is therefore not necessary to do something like:
if { [expr {$x*$x + $y*$y}] > 4.0 } { puts "Point outside the circle" }
It is much better (more concise, clearer) to use:
if { $x*$x + $y*$y > 4.0 } { puts "Point outside the circle" }
or even (taking advantage of the built-in function hypot()
to
calculate the length of a vector):
if { hypot($x,$y) > 2.0 } { puts "Point outside the circle" }
3. Two simple examples
Here we illustrate the use of expr
and several commands that use it
implicitly by calculating the solutions to a number-theoretical problem
and a fairly basic physical system.
3.1. Pell’s equation
The code below can be used to find solutions to the so-called Pell’s equation, an equation of interest in number theory. The equation itself is deceptively simple (at least that is one particular form):
x2 - D y2 = 1
where D is an integer and the problem is to find integer solutions (x,y).
Let us set D to 5 and search for solutions with x and y between 0 and 10000. A straightforward implementation is:
set D 5 for {set y 0} {$y <= 10000} {incr y} { for {set x 0} {$x <= 10000} {incr x} { if { $x**2 - $D *$y**2 == 1 } { puts "$x - $y" } } }
This works fine and produces the following list:
1 - 0 9 - 4 161 - 72 2889 - 1292
But if you look more closely at the two loops, you will note that we are
spending a lot of iterations in areas where no solution can exist: if x
and y get bigger than, say, 10, their ratio will converge to sqrt(5)
, or
2.236…
. Hence, why not look for solutions where x is about 2.236*y
?
That is what the fragment below does:
set D 5 for {set y 0} {$y <= 10000} {incr y} { set xmin [expr {int( sqrt($D)*$y ) - 3}] ;# Convert to integers! set xmax [expr {int( sqrt($D)*$y ) + 3}] # set x $xmin while { $x <= $xmax } { if { $x**2 - $D *$y**2 == 1 } { puts "$x - $y" } incr x } }
The result is slightly different - it picks up one of the solutions with negative x, but the program is much faster:
-1 - 0 1 - 0 9 - 4 161 - 72 2889 - 1292
Try this with different values of D. You may be surprised to see that for D = 61 no solution is printed. That is because the smallest solution is (x,y) = (1766319049,226153980). But you can check that it is indeed a solution. |
3.2. A damped harmonic oscillator
A completely different problem is that of the damped oscillator, such as a pendulum (well, in first approximation). The differential equation is:
m y” + r y’ + k y = 0
with m, r and k given parameters and y the excitation at some moment in time. You need two initial conditions: the excitation at time T0 and the velocity at that moment, so solve the equation.
The equation shown here is simple enough to solve analytically, but in general you need to rely on numerical methods. The simplest such method is that by Euler. First the equation is written as a system of two first-order equations:
y’ = v
m v’ + r v + k y = 0
where v is the velocity of the oscillator.
Then the solution at time T+dt is approximated as (dt is the time step):
ynew = yold + dt * y’old
vnew = vold + dt * v’old
where the subscripts 'old' and 'new' refer to the previous and the current times.
Looping over the steps we get the approximate solution to the original equation:
set excitation 0.0 set velocity 1.0
set damping 0.1 set fcoeff 1.0
set dt 0.05
for {set t 0} {$t < 100} {incr t} { # # Calculate the derivatives # set d_excitation $velocity set d_velocity \ [expr {-$damping * $velocity - $fcoeff * $excitation}] # # Calculate the new values # set excitation [expr {$excitation + $dt * $d_excitation}] set velocity [expr {$velocity + $dt * $d_velocity}] # puts "[expr {$t*$dt}] $excitation $velocity" }
The math::calculus package in the Tcllib library implements
several methods to solve systems of first-order differential equations.
|
4. Numbers and strings
The expr
command deals with both numbers and strings. The code:
set string "Book" puts [expr {$string == "Book"}]
prints the value 1
, indicating that the condition is satisfied, that
is the value of the variable string
is equal to the literal string
Book
.
This example reveals a difference between literal values in Tcl and in
the expr
expression sublanguage. In Tcl the following two commands are
exactly the same:
puts Hello
and
puts "Hello"
In expr
the literal string "Hello" without quotes leads to a
complaint:
expr Hello
produces:
invalid bareword "Hello" in expression "Hello"; should be "$Hello" or "{Hello}" or "Hello(...)" or ...
(Just try it in an interactive session)
Another aspect that you need to be aware of is that expr
prefers
numbers over strings: it will first try to convert a string value to a
number and if that does not succeed, it will use the string as such.
Therefore:
set a "1.0" set b "+1" expr {$a == $b}
return 1
, indicating that the 'numbers' $a
and $b
are the same.
If you want to compare strings, use the eq
("the strings are
equal") and ne
("the strings are not equal") operations. This has the
further advantage that expr
will not try to convert the strings to
numbers.
There are no equivalents for the "greater than" and similar
operations that work exclusively on strings. If you want to ensure
strings are treated as strings in a logical expression, you need to use
the string compare command.
|
The expr
command also provides shorthand operations for searching
elements in a list:
set list {A B C D} set element1 "D" set element2 "Z" expr {$element1 in $list} ;# Is the element _in_ the list? expr {$element2 ni $list} ;# Is the element _not in_ the list?
will print:
1 1
as the element "D" is found in the list (the first 1
, so the
expression results in "true") but "Z" is not (the second 1
, so this
expression is also true). The two operations use the exact matching
method as found in the following command:
lsearch -exact $list $element1
though this command returns the position of the element or -1, if the
element was not found. Therefore the in
operation is equivalent to:
expr {[lsearch -exact $list $element1] >= 0}
5. Integers and floating-point numbers
Like most computer languages, Tcl distinguishes 'integer' numbers and 'floating-point' numbers. While they can be mixed in calculations and are (usually) converted from one type to the other without you having to worry about it, you do need to be aware of a few caveats.
'Dividing two integers'
If you calculate something like "100 divided by 2", you get 50
, just as
expected. If you calculate "2 divided by 100", however, the answer is 0
,
not 0.02
! The reason is that Tcl, like most computer languages, uses
'integer division' if the two operands are integer.
To force the outcome to be a fraction, use a function like double()
:
expr {2 / double(100)}
results in:
0.02
You can of course also turn one of the constants into a floating-point value:
expr {2.0 / 100}
'Different types of integers'
Tcl has three types of integers, 32-bits integers, 64-bits integers and
arbitrary-range integers. Normally they are converted automatically into
the appropriate type, but several functions are available to force a
value into one of these three: int()
, wide()
and entier()
. See
the section on standard functions.
Each type has its own storage requirements and its own limitations as to the numbers that can be stored.
'Floating-point numbers'
A frequently asked question concerns the very nature of floating-point numbers. Many people are awed by the following:
set factor 2.01 puts [expr {int(100*$factor)}]
producing
200
not the answer they expect: 201
.
Actually, puts [expr {100*$factor}]
gives:
200.99999999999997
The reason for this is that the number 2.01
can not be represented
exactly by the underlying 'binary' floating-point system, just as 1/3
can not be exactly represented as a decimal number.
This can be remedied in three ways:
-
Use the
round()
function, so that the nearest integer is used, instead of the one obtained by truncating the floating-point number -
Use a 'decimal arithmetic' package, such as the one included in the
Tcllib
library. -
Accept this fact of life.
The latter is the easiest approach - contemporary computers simply use binary floating-point arithmetic throughout!
6. Using functions
The expr
command has a full complement of the common
mathematical functions, such cos
, sin
, log
and log10
:
expr {acos(-1.0)}
produces:
3.141592653589793
the nearest approximation of the mathematical constant pi
that can be
obtained with double-precision floating-point numbers.
If you need numbers like 'ln(2)' or the conversion factor of
degrees to radians often, you may find the math::constants package in Tcllib
useful. It defines a small set of such constants that can then be
selectively imported as namespace variables.
|
To tabularise a function like 'sin(x) * exp(-x)' for x from 0 to 5 you can use the following code:
puts "x\tf(x)" for { set i 0 } { $i < 100 } { incr i } { set x [expr {$i * 0.05}] puts "$x\t[expr {sin($x) * exp(-$x)}]" }
If you need to use the result of some command, say you want to know the
position of an element in the list, rather than if it is in the list,
this is done via […]
just as in Tcl itself:
set list {A B C D} set element1 "B" expr {1 + [lsearch $list $element1]}
which results in the index of the 'next' element:
2
The default matching method for lsearch is -glob , which means
that an asterisk (* ) or a question mark (? ) in the string to be
found in the list may lead to a different element than you expect.
|
6.1. User-defined functions
Sometimes it is useful to define your own functions for use in expr
.
For instance, if you use angles expressed in degrees and need to use
their sines and cosines, converting the angles to radians and then
using the result as arguments to cos
or sin
can become tedious.
By defining procedures in the namespace tcl::mathfunc
these
procedures become available as functions in expr
:
namespace eval tcl::mathfunc { variable degtorad set degtorad [expr {acos(-1.0)/180.0}] } proc tcl::mathfunc::sind {x} { variable degtorad expr {sin($degtorad*$x)} } proc tcl::mathfunc::cosd {x} { variable degtorad expr {cos($degtorad*$x)} }
foreach degree {0 30 45 60 90} { puts "$degree [expr {sind($degree)}] [expr {cosd($degree)}]" }
which results in:
0 0.0 1.0 30 0.49999999999999994 0.8660254037844387 45 0.7071067811865475 0.7071067811865476 60 0.8660254037844386 0.5000000000000001 90 1.0 6.123233995736766e-17
|
7. Overview of the operations
The operations defined by the expr
command follow the usual
precedences: multiplication (*) takes precedence over addition (+) for
instance. You can use parentheses to force evaluation to take place in a
different order:
expr {1 + 2 * 3}
is evaluated as: multiply 2
by 3
and add the result to 1
. Result: 7
.
expr {(1 + 2) * 3}
is evaluated as: add 1
and 2
and multiply the result by 3
. Result: 9
.
Most operations are left-associative:
expr {2 - 2 - 3}
is evaluated as:
expr {(2-2) - 3}
So the result is -3
.
The only exception is the exponentiation operation, just as ordinary convention would have it:
expr {2**3**5}
is evaluated as:
expr {2**(3**5)}
not as
expr {(2**3)**5}
So the result of expr {2**3**5}
is 14134776518227074636666380005943348126619871175004951664972849610340958208
,
yes a pretty large number. (The result of expr {(2**3)**5}
is:
32768
or 2**15
).
x + y |
Add x and y |
x - y |
Subtract y from x |
+ x |
Unary plus (no effect) |
- x |
Unary minus (multiply x by -1) |
x * y |
Multiply x and y |
x / y |
Divide x by y (integer division if both x and y are integers!) |
x ** y |
Take x to the power y |
x % y |
Take the modulo x wrt y (integers only) |
x == y |
True (1) if x is equal (numerically or by string comparison) to y; false (0) otherwise |
x != y |
True (1) if x is not equal (numerically or by string comparison) to y; false (0) otherwise |
x > y |
True (1) if x is greater (numerically or by string comparison) than y; false (0) otherwise |
x >= y |
True (1) if x is greater (numerically or by string comparison) than y or equal to y; false (0) otherwise |
x < y |
True (1) if x is lower (numerically or by string comparison) than y; false (0) otherwise |
x ⇐ y |
True (1) if x is lower (numerically or by string comparison) than y or equal to y; false (0) otherwise |
x eq y |
True (1) if x is equal to y by string comparison. If either is a number, it is converted to a string first |
x ne y |
True (1) if x is not equal to y by string comparison. If either is a number, it is converted to a string first |
The comparison operations will first try to compare the operands numerically and if that fails, the operands are treated as strings. |
!x |
Negation of x (if x is true, then !x is false; if x is false, then !x is true) |
x || y |
True (1) if x is true or y is true; false (0) otherwise |
x && y |
True (1) if x is true and y is true; false (0) otherwise |
x ? y : z |
If the expression x is true, evaluates to y, otherwise to z |
x in y |
True (1) if the string x is contained as an element in the list y |
x ni y |
True (1) if the string x is 'not' contained as an element in the list y |
|
The bitwise operations can be used on integer values only.
~x |
Negation of each bit in x |
x & y |
Bitwise AND on each pair of bits in x and y |
x | y |
Bitwise OR on each pair of bits in x and y |
x ^ y |
Bitwise exclusive-OR on each pair of bits in x and y |
x << y |
Shift all bits in x to the left by y positions |
x >> y |
Shift all bits in x to the right by y positions |
Right bit shifts operations are tricky when it comes to negative values. In the Tcl implementation, they are equivalent to a division by 2+**+y. |
7.1. Standard functions
Here is an overview of the various standard functions, organised in categories:
int(x) |
Convert any number to a 32-bits integer (the value is truncated). For example: int(1.5) = 1, int(-1.5) = -1 |
wide(x) |
Convert any number to a 64-bits integer (similar to int() |
entier(x) |
Convert any number to an arbitrary-range integer. Unlike int() and wide(), there is no limit to the range of the value |
round(x) |
Rounds the argument to the nearest integer number: round(1.5) = 2, round(-1.4) = -1 |
double(x) |
Converts any number to a double-precision real number |
abs(x) |
Absolute value of x |
ceil(x) |
Returns the lowest integer value greater than x: ceil(1.5) = 2, ceil(-1.5) = -1 |
floor(x) |
Returns the greatest integer value lower than x: floor(1.5) = 1, floor(-1.5) = -2 |
fmod(x,y) |
Returns the fraction of a real value wrt a modulo: fmod(1.5,1) = 0.5, fmod(-1.5,1) = -0.5 |
(The integer modulo function is implemented via the operator %: 10%3 = 1) |
|
hypot(x,y) |
Length of the vector (x,y) (high precision, no overflow) |
sqrt(x) |
Square root of x |
acos(x) |
Arccosine of x (result in radians) |
asin(x) |
Arcsine of x (result in radians) |
atan(x) |
Arctangent of x (result in radians) |
atan2(x) |
Mathematical angle of the vector (x,y) (radians) |
cos(x) |
Cosine of x (x in radians) |
cosh(x) |
Hyperbolic cosine of x |
exp(x) |
Exponential function |
hypot(x,y) |
Hypothenuse of a rectangular triangle with sides x and y |
log(x) |
Natural logarithm of x |
log10(x) |
Logarithm with base 10 of x |
pow(x,y) |
x to the power y (alternatively: $x**$y) |
sin(x) |
Sine of x (x in radians) |
sinh(x) |
Hyperbolic sine of x |
tan(x) |
Tangent of x (x in radians) |
tanh(x) |
Hyperbolic tangent of x |
rand() |
Return a random number |
srand(x) |
Seed the random number generator with seed x |
If you need special functions like the gamma-function or Bessel
functions of the first kind, check the math::special package in
Tcllib .
|
8. Prefix notation
Sometimes, especially for ubiquitous simple expressions, it would be easier to use the prefix form. For instance: to double the value of x, you need to use:
set x [expr {2.0 * $x}]
Using a prefix notation would allow you to write:
set x [* 2.0 $x]
This is in fact possible by using the procedure form of the operations
and functions defined by expr
:
namespace import tcl::mathfunc::* namespace import tcl::mathop::*
puts "x\tf(x)" for { set i 0 } { $i < 100 } { incr i } { set x [* $i 0.05] puts "$x\t[* [sin $x] [exp [- $x]]]" }
Whether you use this notation or expr
and the infix notation is mostly
a matter of taste. But they can be handy as a shorthand construction.
Compare:
set sum [+ {*}$list_of_numbers]
to
set sum 0.0 foreach v $list_of_numbers { set sum [expr {$sum +$v}] }
9. Technical aspects
This section describes some more technical aspects of working with numbers in Tcl. Partly they are specific to Tcl, partly they are inherent to the floating-point arithmetic implemented in the hardware of your computer.
9.1. Exceptions and the special "numbers" Nan
and Inf
Division by zero or calculating the root of a negative number are operations that cause problems: the outcome is undefined (at least within the realm of ordinary numbers). Two things can happen:
-
The
expr
command throws an exception:expr {sqrt(-1.0)} ==> domain error: argument not in valid range
-
The result is one of two special "numbers":
Inf
orNaN
.
The first of these is short for "infinity" and it behaves as you would expect:
Inf * 2 = Inf
, 2 / Inf = 0
, etc.
The second is an odd one, even if it is useful from time to time. The abbreviation "NaN" means "not a number" and it has some strange properties:
-
Any arithmetic operation involving a
NaN
yields aNaN
or throw an exception. So once you have `NaN’s in your calculations, they persist and may even spread. -
The value
NaN
is unequal to any number, it is even unequal to itself:expr {NaN == NaN} ==> 0
One possible explicit use of `Nan’s is as a missing value in statistical calculations:
set sum 0.0 set count 0 foreach value $value { # # Avoid missing values # if { $value == $value } { set sum [expr {$sum + $value}] incr count } } puts "Mean value: [expr {$sum/$count}]"
Another popular choice for missing values is the empty string
{} . Older versions of Tcl (before 8.5) do not support NaN and Inf .
|
9.2. Bracing your expressions
In all the code fragments shown here, we have used braces ({…}) around the expressions. There is a simple reason for this: optimisation. If you do not brace your expressions, then the expression is interpreted (parsed) twice:
-
Once by Tcl itself to substitute all variable references and insert the results of any embedded commands
-
Once by
expr
to set up a structure for evaluating the various parts of the expression correctly.
If you brace your expressions, the first step is skipped and the second
step is executed only once: the expr
command then stores the structure
for later reuse.
In very special cases, this may be a problem, for instance if the
expression itself is dynamic, something along the lines of expr "$x $op
$y"
and op
containing, say, a +
if you want addition or -
if you
want subtraction. If you would surround this expression with braces,
the value of $op
would never be used in the parsing of the
expression.
Here is a rather creative example:
# Sum the elements in a list set list {1 2 3 4} expr [join $list +]
As these cases are quite rare, the maxim is: 'Brace your expressions'.
In one particular case unbraced expressions could lead to the wrong result:
set countdown 10 while $countdown { puts "$countdown ..." incr countdown -1 }
Because the condition has no braces, it is evaluated by Tcl itself and
the while
command is passed a constant value: an infinite loop is
born.
9.3. Octal numbers
As a left-over from the grey past, 'octal numbers' can sometimes be of
use, but often present unexpected problems. A string consisting
of digits that starts with a zero, such 012
, is interpreted as an
octal number. This causes trouble if it contains a digit 8 or 9 - expr
will complain that it is not a valid octal number - but perhaps worse, a
string that should be interpreted as a decimal number gets interpreted
as an octal number and the resulting value is wrong: 012
is silently
turned into the decimal value 10, instead of 12.
If you have to deal with such numbers, use the scan
command to
properly turn it into a decimal number:
scan "012" %d value puts $value ==> 12
9.4. Security
Bracing expressions is generally recommended for a better performance, but there is another reason too. If you do not brace an expression, you might get into trouble with strings that contain commands:
set string {[exit]} puts "The value of $string is: [expr $string]"
What happens? The expr
command gets an expression "[exit]", parses it,
notices that it contains square brackets, so it knows that a Tcl command
must be run and runs that command - and therefore your program is
terminated. This, of course, is one of the more innocent
possibilities. If the expression was entered by a malicious user, it
could contain very harmful code!
Bracing your expressions avoids this problem:
set string {[exit]} puts "The value of $string is: [expr {$string}]"
Now, expr
gets an expression that consists of the value of a variable.
It does not need to evaluate a Tcl command, even though the value looks
like a command, since that is not part of the expression!
Concerns for the security of your applications are best
addressed by using a variety of techniques, such as the use of a
'safe interpreter' and the catch or try commands.
|