简体   繁体   中英

How to generate sequence of numbers in Tcl

I'm looking for a way to generate a list of numbers, according to input from , to and step parameters.

Using incr is no good as I would like to support also float and double numbers.

For example, in case from=-0.3 , to=0.25 and step=0.1 , I would like to generate the list -0.3 -0.2 -0.1 0 0.1 0.2 . I'm having troubles with the formatting and rounding.

This is a classic problem in computing . Seriously.

What you need to do is to use integer iteration anyway and then scale by the step size. That minimises the errors. You also need to use format carefully.

set from -0.3
set to 0.25
set step 0.1
for {set i 0} true {incr i} {
    set x [expr {$i*$step + $from}]
    if {$x > $to} break
    set printable [format "%.1f" $x]
    puts "$i => $printable"
}

If you can remove the string prefixes yourself:

proc genNums {{from 0} {to 1} {step .1} {prec 1}} {
    if {$step < 0} {
        set op ::tcl::mathop::>
    } else {
        set op ::tcl::mathop::<
    }
    for {set n $from} {[$op $n $to]} {set n [expr {$n + $step}]} {
        lappend res [format %.*f $prec $n]
    }
    return $res 
}

% genNums -0.3 0.25 0.1
# => -0.3 -0.2 -0.1 0.0 0.1 0.2
% genNums -0.3 0.25 0.1 2
# => -0.30 -0.20 -0.10 0.00 0.10 0.20

But if you want, you can set it up so that you can pass the string to the command:

proc genNums args {
    array set params {from 0 to 1 step .1 prec 1}
    array set params [split [string map {= { }} $args]]
    if {$params(step) < 0} {
        set op ::tcl::mathop::>
    } else {
        set op ::tcl::mathop::<
    }
    for {set n $params(from)} {[$op $n $params(to)]} {set n [expr {$n + $params(step)}]} {
        lappend res [format %.*f $params(prec) $n]
    }
    return $res 
}

genNums from=-0.3 to=0.25 step=0.1
# => -0.3 -0.2 -0.1 0.0 0.1 0.2
% genNums from=-0.3 to=0.25 step=0.1 prec=2
# => -0.30 -0.20 -0.10 0.00 0.10 0.20

Documentation: + (operator) , < (operator) , array , expr , for , format , if , lappend , proc , return , set , split , string , Mathematical operators as Tcl commands

What do you think about the following solution:

proc ::General::Range {Start Stop {Step 1}} {
    if {$Step eq 0} {return Error-'ZeroStep'}
    if {$Start eq $Stop} {return $Start}
    set Range {}
    if {![string in integer $Step]} {
        # Double
        regexp {^\d+\.(\d+)$} $Step FullMatch ToFormat
        while {$Start <= $Stop} {
            lappend Range [string trimright $Start 0]
            set Start [format "%.${ToFormat}f" [expr {$Start + $Step}]]
        }
    } else {
        # Integer
        while {[expr {$Stop > 0 ? [expr {$Start <= $Stop}] : [expr {$Start >= $Stop}]}]} {lappend Range $Start; incr Start $Step}
    }
    return $Range
}

Here's an example using Tcl's coroutines to generate the next value in the range on-demand

% proc range {from to step} {
    yield
    set value $from
    while {$value <= $to} {
        yield $value
        set value [expr {$value + $step}]
    }
}
% coroutine generator range -0.35 0.25 0.1
% puts [generator]
-0.35
% puts [generator]
-0.24999999999999997
% puts [generator]
-0.14999999999999997
% puts [generator]
-0.04999999999999996
% puts [generator]
0.050000000000000044
% puts [generator]
0.15000000000000005
% puts [generator]

% puts [generator]
invalid command name "generator"

As Donal was saying, here we see the accumulating floating point errors. Applying his method:

proc range {from to step} {
    yield
    set i 0
    while 1 {
        set value [expr {$from + $i * $step}]
        yield $value
        if {$value > $to} break
        incr i
    }
}

we get the sequence

-0.35
-0.24999999999999997
-0.14999999999999997
-0.04999999999999993
0.050000000000000044
0.15000000000000002
0.2500000000000001

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM