Version 2 of How to make a Tcl application - part three

Updated 2003-12-17 07:54:51

Arjen Markus Part three of the tutorial How to make a Tcl application


8. Platform issues

Tcl applications are highly portable in general:

  • Tcl itself deals gracefully with such trivial but annoying matters as line-endings (the two characters carriage, return line feed versus line feed versus ... for instance)
  • The Tcl commands that deal with the operating system hide almost all platform-specific issues - file names and directory names for instance via the [file] command
  • the Tk package works on all supported platforms in much the same way - you need not to be concerned about platform-specific issues like window decorations most of the time

But despite of this, there are a few aspects that you do need to consider, when your application or library is to run on several platforms - and believe me: they will. This chapter is meant to introduce them and the means to solve the problems they cause.

8.1 The tcl_platform array

Tcl collects all information about the platform in a global array, called tcl_platform. Here is a typical listing of its contents:

 % parray tcl_platform
 tcl_platform(byteOrder) = littleEndian
 tcl_platform(machine)   = intel
 tcl_platform(os)        = Windows 95
 tcl_platform(osVersion) = 4.0
 tcl_platform(platform)  = windows
 tcl_platform(user)      = arjen
 tcl_platform(wordSize)  = 4

(Note the use of the array printing command parray - very useful in an interactive shell)

The most useful of these for distinguishing between platforms is tcl_platform(platform): it gives you a standard characterisation of the OS. Typical values: windows, unix and macos.

Tip:

Put everything that is platform-specific in a central place - do not scatter checks on the type of platform all over your code, as that makes it difficult to maintain it. Probably the best approach is to use one or two source files with platform-specific procedures whose interface is essentially platform-independent.

Remember that in Tcl it is quite possible to use constructions like:

   if { $tcl_platform(platform) == "windows" } {
      proc getColours {} {
         ...
      }
   }

   if { $tcl_platform(platform) == "unix" } {
      proc getColours {} {
         ...
      }
   }

   if { $tcl_platform(platform) == "macos" } {
      proc getColours {} {
         ...
      }
   }

This way there is no need to do any run-time checking.

8.2 File and directory names

8.3 Fonts and colours

8.4 Interacting with the desktop

8.5 Dealing with numbers

Fortunately, there are very few platform-dependent issues with numbers:

  • Most computers nowadays use the same internal representation for floating-point data, so that the main difference is big-endianness and little-endianness. Use tcl_platform(byteOrder) if you are confronted with that problem, for instance, when dealing with binary data files.
  • Tcl is very good at catching numerical errors:
   catch {
      set x 0.0
      set y [expr {1.0/$x}]
   } msg
   puts $msg

   ==> divide by zero

   The global variables errorInfo and errorCode hold more detailed information:

   % puts $errorInfo
   divide by zero
       while executing
   "expr {1.0/$x}"
   % puts $errorCode
   ARITH DIVZERO {divide by zero}
  • One of the few things that are platform-dependent is the error handling by such functions as atan2(). If atan2() is called with two arguments 0, then this results in a domain error on some platforms and not on others. This is because Tcl depends on the underlying

mathematical processing to do the "right thing". Formally, atan2(0,0) is not defined, so a domain error is reasonable, but it can be annoying nonetheless.

  • Most computers will not complain if an integer overflow occurs, so here is a problem that might go unnoticed for some time. Usually, the problem is obvious because positive values all of a sudden become negative. If you need large integers, consider using the wide() function to force ordinary integers into 64-bits integers. This type is available even on platforms that do not natively support 64-bits integers!

The most important aspects of handling with numbers hold for all platforms:

Use of braces

Consider the script:

   set x 1.1
   set y [expr $x*2.3]

and the variation:

   set x 1.1
   set y [expr {$x*2.3}]

You are almost always better off using the second version! The braces have a number of consequences that are not obvious but that are very welcome:

  • expr needs to parse the expression only once and can store the result for later reuse. This can not be done in the first case.
  • Tcl will not convert the value of x to a string and substitute its result in the expression. This means that time is saved there as well and that the exact value is used.

If you time these commands:

   puts [time {set x 1.1; set y [expr $x*2.3]}   10000
   ===> 135 microseconds per iteration

   puts [time {set x 1.1; set y [expr {$x*2.3}]} 10000
   ===> 14 microseconds per iteration

you will see the difference (the computer I did this on was not very fast by today's standards).

Effects of floating-point arithmetic

If you are not familiar with floating-point numbers and their behaviour, have a look at this script:

   for { set i 0 } { $i < 100 } { incr i } {
      set x [expr {$i*0.01}]
      set y [expr {$x*100.0-$i}]
      puts "$x $y"
   }

you might expect to see a value of zero for all y's. But on my computer, this is the result:

   0.0 0.0
   0.01 0.0
   0.02 0.0
   0.03 0.0
   0.04 0.0
   0.05 0.0
   0.06 0.0
   0.07 8.881784197e-016
   0.08 0.0
   0.09 0.0
   0.1 0.0
   0.11 0.0
   0.12 0.0
   0.13 0.0
   0.14 1.7763568394e-015
   0.15 0.0
   ...
   0.27 0.0
   0.28 3.5527136788e-015
   0.29 -3.5527136788e-015
   0.3 0.0
   ...
   0.54 0.0
   0.55 7.1054273576e-015
   0.56 7.1054273576e-015
   0.57 7.1054273576e-015
   0.58 -7.1054273576e-015
   0.59 0.0
   ...
   0.97 0.0
   0.98 0.0
   0.99 0.0

(the dots represent left-out entries with y = 0.0)

So, in many cases we do get y = 0.0, but not in all. The reason? Floating-point arithmetic is not the same as the decimal arithmetic we know from school. Just as 1/3 can not be represented exactly as a decimal fraction (it is a repetitive fraction: 0.33333...), 1/5 can not be represented exactly in floating-point arithmetic, which usually has a base 2 instead of 10.

Moreover, computers have a limited number of digits to work with. This is exemplified by:

   set x 1.0e30
   set y 1.0e-30
   set z 1.0e30

   puts [expr {$x+$y-$z}]
   ===> 0.0

   puts [expr {$x-$z+$y}]
   ===> 1e-030

The first result is zero, because the sum of x and y can not be represented accurately enough - y is too small in comparison with x. In the second result, x and z cancel each other, leaving room for y.

More on floating-point arithmetic in general can be found in the document by Daniel Goldberg, ...


[ Category Tutorial ]