Version 27 of The infinity trick

Updated 2018-10-02 18:46:29 by dbohdan

Summary

Lars H It is common in programming that one sometimes wants to say "infinity", or at least "a number larger than any given number". The ordinary way to do that is to pick a fixed but very large number (e.g. the largest representable number) or to use an unreasonable number (e.g. -1 items) and add extra logic for handling it, but in Tcl there is a better way, thanks to that everything is a string: just say "infinity"!


KD 2008-09-22: Ugh... what an ugly trick. I'd say Inf (available from 8.5 on) is more predictable and much less trickier.

jk 2008-09-30: of course, but this trick allows Inf to be used in 8.4 and earlier. sadly -Inf will only work in 8.5 and on so if you want to do something similar you need something tricksy like !Inf

Description

Take for example conditions such as this

if {$someQuantity >= $limit} then {
    # Do some kind of flush
}

that aren't too uncommon in programs. Suppose you don't want any such flushes to happen at all. How to do that? It turns out you can do

set limit infinity

This will not error out, as one might perhaps expect, since comparisons are string comparisons if not both operands are numbers;

% expr {1 <= "infinity"}
1
% expr {2 < "infinity"}
1
% expr {2 > "infinity"}
0
% expr {"infinity" > "infinity"}
0
% expr {-3000 < "infinity"}
1

A more complete example:

proc record_work {items} {
    variable items_total
    incr items_total $items
    # Perhaps write a log message about this.
    set now [clock seconds]
    variable last_message_seconds
    variable period
    if {$now - $last_message_seconds >= $period} then {
        puts [format "At %s there is a total of %d items."\
            [clock format $now] $items_total]
        set last_message_seconds $now
    }
}

proc init_work_record {report_period} {
    variable items_total 0\
      last_message_seconds [clock seconds]\
      period $report_period
}

With these procs,

init_work_record 60

will cause reports to be at least a minute apart and

init_work_record 3600

will cause reports to be at least an hour apart, but

init_work_record infinity

will effectively turn reporting off completely.

Another common application is when one needs to compute the minimum of a list of numbers

proc min {L} {
    set res infinity
    foreach n $L {
        if {$n < $res} then {set res $n}
    }
    return $res
}

In this case, where the numbers are all known from the start, it is usually possible to initialise res to the first number in the list and get a correct value that way, but if the numbers are generated as one goes along then the infinity trick usually simplifies the code considerably.

It is normally not possible to do arithmetic with infinity (although on some platforms it can get parsed as some kind of double), so one has to execute some care in how it is used, but very often the code can be structured so that arithmetic only need to happen to finite numbers.


AM What a cute little trick! It even works for numbers like .1


I think most programmers if they needed some function to run forever they would script something like this instead:

while {true} {
    #Do Something
}

The endless while loop is very common in C programs and is easier to read then the forever for loop.

Lars H: Yeah, it was a poor example (although not entirely unlike what one might encounter). I've replaced it with something better.

TV As long as 1/inf and 2/inf remain both zero AND unequal, I guess...

Zaros They are both zero and equal to each other. In fact, 1.0/inf and 2.0/inf both equal 1/2 (all zero)


RS notes that constant [while] conditions need not be braced, and Tcl's canonical true value is 1 (as shown by [expr), so he codes pseudo-endless loops as

while 1 {
    #do something or break
}

Also, if you want to avoid the infinity trick causing a bug, there is a simple test for integerness. Consider that

proc foo x {
    for {set i 0} {$i<$x} {incr i} {
        #do something
    }
}

will run infinitely (or longer than wanted) if it is called with non-numeric x. Just increment x by 0, which doesn't hurt integers, but throws a clear error if someone attempts otherwise:

proc foo x {
    incr x 0 ;#-- make sure x has an integer value
    for {set i 0} {$i<$x} {incr i} {
        #do something
    }
}

dougcosine: incr x 0 is a clever trick, but it's probably more legible to use string is :

# a class other than integer might be better in some cases
# check the documentation to see what's best for you!
if {![string is integer -strict $x]} {
  error "x has to be an integer, I guess..."
}

when plotting histograms of ratios jk used the infinity trick twice, first to have a bin boundary at infinity that all numbers are less than. but also by relying on

catch {expr 1/0} res

being < infinity but greater than any real number

RS: Well, of course, "d" sorts before "i"...

% expr 1/0
divide by zero
% expr {"divide by zero" < "infinity"}
1

Of course, you can't take things like this too seriously:

% expr {"really small number" < "infinity"}
0

at the opposite end of the scale hows about "!infinity"

% expr {"!infinity" < "infinity"}
1
% expr {"!infinity" < 999}
1
% expr {"!infinity" < 0}
1
% expr {"!infinity" < -999}
1

which allows things such as:

proc max {L} {
    set res !infinity
    foreach n $L {
        if {$n > $res} then {set res $n}
    }
    return $res
}