Reputation: 99
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.
Upvotes: 1
Views: 8341
Reputation: 99
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
}
Upvotes: 0
Reputation: 246942
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
Upvotes: 0
Reputation: 13252
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
Upvotes: 1
Reputation: 137637
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"
}
Upvotes: 2