Version 11 of Brace your expr-essions

Updated 2007-01-09 14:09:12 by LV

KBK closed a follow-up to the comp.lang.tcl newsgroup with the advice that, "Also, it's important always, always to brace your expressions. Don't do

     set a [expr $b / $c]

but rather

     set a [expr { $b / $c }]

Otherwise, your variables may undergo unexpected conversions numeric -> string -> numeric, you can lose precision, your expressions will be much slower, and you can even have security problems:

     # don't run
     set x { [format C:\] }
     set y [expr $x + 3]

"

AMG: The security problems of unbraced expr expressions very similar to SQL injection attacks. Notice how sqlite's Tcl binding does its own variable expansion to avoid this very problem. Many, many sh scripts have this problem as well because the default is to apply multiple passes of interpretation.


RS: In the following cases braced expressions don't work:

  • operator in variable (which in most languages is impossible, but Tcl can do):
 set op +
 expr $x $op $y
  • (parts of) expression generated by string manipulation:
 expr [join $intlist +]

KPV: Braced expressions also don't work when you do negation in the lazy way:

  set n1 -3
  set n2 -$n1
  expr {2 + $n2} ;# error
  expr 2 + $n2   ;# ok

aricb Dec. 10 2005 -- As a bonus, braced expressions are generally much faster than their non-braced counterparts. Here's an attempt to illustrate the difference:

 proc time_braces {} {

     set expressions {
         {1 + 1}
         {16 - 7}
         {(16 - 7)}
         {7 * 12}
         {81 / 3}
         {81 / 5}
         {16224308 + 123580329}
         {6.3 + 1.2}
         {6.3 - 1.2}
         {6.3 * 1.2}
         {6.3 / 1.2}
         {log(765) * 3}
         {(log(765) * 3) / 8.23}
     }

     puts [format " %-22s  %-8s  %-8s  %-8s  %-5s" "Expression" "-braces" "+braces" "ratio" "% speedup"]
     puts " [string repeat - 22]  [string repeat - 8]  [string repeat - 8]  [string repeat - 8]  [string repeat - 5]"

     foreach expression $expressions {
         set time1 [lindex [time "expr $expression" 100000] 0]
         set time2 [lindex [time [list expr $expression] 100000] 0]
         set ratio [expr {$time1 / $time2}]
         set percent [expr {100 * (1 - ($time2 / $time1))}]
         puts [format " %-22s  %8.6f  %8.6f  %6.3f:1  %5.2f" $expression $time1 $time2 $ratio $percent]
     }

     puts "\n -braces and +braces are in microseconds per iteration"
 }

Results:

 Expression              -braces   +braces   ratio     % speedup
 ----------------------  --------  --------  --------  -----
 1 + 1                   2.334690  0.366480   6.371:1  84.30
 16 - 7                  2.388280  0.359960   6.635:1  84.93
 (16 - 7)                3.748160  0.356120  10.525:1  90.50
 7 * 12                  2.438530  0.368200   6.623:1  84.90
 81 / 3                  2.456060  0.395680   6.207:1  83.89
 81 / 5                  2.518490  0.397380   6.338:1  84.22
 16224308 + 123580329    2.726690  0.369530   7.379:1  86.45
 6.3 + 1.2               3.237970  0.497100   6.514:1  84.65
 6.3 - 1.2               3.199230  0.486350   6.578:1  84.80
 6.3 * 1.2               3.279100  0.480630   6.823:1  85.34
 6.3 / 1.2               3.257540  0.494340   6.590:1  84.82
 log(765) * 3            6.202120  0.975590   6.357:1  84.27
 (log(765) * 3) / 8.23   7.951270  1.142210   6.961:1  85.63

 -braces and +braces are in microseconds per iteration

RS 2007-01-09: These results puzzle me: the expressions contain no variable references, all constants. I thought the time advantage of braced expressions was because they could retrieve the numeric value of variables directly, without having to parse string reps - but in the above cases, the strings would have to be parsed either way...

AM The numeric value of the strings is cached in the data structure that [expr] builds up during the parsing. So there is no real difference between numeric constants and variables with numeric values

RS Well, but the only difference braces make, in my opinion, is that $, \, or [...] substitutions don't take place. If there are none,

 expr 1+1

should be totally equivalent (in the eyes of expr) to

 expr {1+1}

LV It should be simple enough to enhance the above benchmark to include some variable uses to compare numeric strings vs numeric variables.


Arts and crafts of Tcl-Tk programming