TCL incr gives wrong value for zero padded integer - tcl

I was trying to increment a number which is padded by zeroes to become a six digit number. But strangely any value other than single digit gives a wrong value. like
set x 000660
incr x 1
gives result 433. Also tried with smaller number like 010 but the result is 9. Why is this happening ?
What is the proper way to solve this issue ?

You can try this way too.
proc getIntVal { x } {
# Using 'scan' command to get the literal integer value
set count [ scan $x %d n ]
if { $count!= 1 } {
return -1
}
return $n
}
proc padZero { x } {
# Using 'format' to pad with leading zeroes.
return [ format "%05d" $x ]
}
set val 00060
puts "Initial value : $val"
set tmp [ getIntVal $val ]; # 'tmp' will have the value as '60'
incr tmp;
set val [ padZero $tmp ]; # Padding with zero now
puts "Final value : $val"

Numbers beginning with 0 like
000660
are octet integers. It's equivalent to decimal 432.
The same for 010 (the same as 8 in decimal)
To strip off zeros, try this:
proc stripzeros {value} {
regsub ^0+(.+) $value \\1 retval
return $retval
}
For more information, see Tcl FAQ: How can I use numbers with leading zeroes?.

Yu Hao already explained the problem of octets, and Dinesh added some procs to circumvent the issue. I am suggesting creating one proc that will take on a zero padded integer and return another zero padded integer of the same format and which should work just like incr:
proc incr_pad {val args} {
# Check if increment is given properly
if {[llength $args] == 0} {
set args 1
} elseif {[llength $args] > 1} {
return -code error {wrong # args: should be "incr_pad varName ?increment?"}
}
# Check for integers
if {![regexp {^[0-9]+$} $val]} {
return -code error "expected integer but got \"$val\""
} elseif {![regexp {^[0-9]+$} $args]} {
return -code error "expected integer but got \"$args\""
}
# Get number of digits
set d [regexp -all {[0-9]} $val]
# Trim 0s to the left
set newval [string trimleft $val 0]
# Now use incr
incr newval $args
# Return back the number formatted with the same zero padding as initially given
return [format "%0${d}d" $newval]
}
With this...
% incr_pad 000660 1
000661
% incr_pad 2.5 1
expected integer but got "2.5"
% incr_pad 02 1.5
expected integer but got "1.5"
% incr_pad 010 2
012
% incr_pad 1 2 3
wrong # args: should be "incr_pad varName ?increment?"
% incr_pad 00024
00025
% incr_pad 999
1000
Of course, you can change the name of the function to a shorter one or one which you find more appropriate.

Related

Finding Signed Decimal equivalent of a 2's complement binary

I have a TCL procedure that intends to find the signed decimal equivalent of a Binary(which in fact was originally from a HEX).
Ex - Original HEX -- cc
Equivalent BIN -- 1100 1100
Now, to find the signed decimal equivalent, I need to do the following:
a. Find 1's complement of the "Equivalent BIN"
b. Add 1 to [Step a]'s result -- then, convert that BIN to HEX, convert to DEC after that.
c. Convert [Step b]'s output(BIN) to HEX.
d. Convert [Step c]'s output(HEX) to DEC.
I'm unable to proceed from Step b -- My code:
proc revTwosComplmnt { bin } {
for {set i 0} {$i < [string length $bin]} {incr i} {
if {[string index $bin $i]} {
append ret 0
} else {
append ret 1
}
}
puts "ret after 1's complement is -------------------> $ret"
set ret [expr $ret + 1]
puts "ret after adding 1 is -------------------> $ret"
set ret [binary scan B* $ret]
puts "ret is -------------------> $ret" }
Output:
rssibin[0] is 11001100
ret after 1's complement is -------------------> 00110011
ret after adding 1 is -------------------> 36874
bad field specifier "3"
while executing
"binary scan B* $ret"
(procedure "TwosComplmnt" line 12)
invoked from within
"TwosComplmnt [lindex $rssibin $i]"
(procedure "CSIRSSI" line 22)
invoked from within
"CSIRSSI $line0 4"
(file "CSIRecord.test" line 238)
Please help me in how to proceed.
Your add-one step is not doing what you expect. It happens to not error on the value you tried, but the value it is computing is definitely not what you were expecting! The issue is that the expr command is interpreting 00110011 as an octal number, not as binary.
The simplest method for converting binary digits into a number you can do arithmetic on is to use scan with a %b format:
scan $ret "%b" ret
It might be better to the bit-negation in expr as well:
proc revTwosComplmnt { bin } {
# Convert to numeric
scan $bin "%b" value
# Do the operation; look up what the ~ operator does...
set value [expr { ~$value + 1 }]
# Filter this result to the low 8 bits; Tcl math is infinite-precision
set ret [expr {$value & 0xFF}]
# Convert to binary bit string
return [format "%08b" $ret]
}
# Testing
puts [revTwosComplmnt 11001100]; # ==> 00110100
binary scan takes the input data before the format not after, see https://www.tcl-lang.org/man/tcl8.6/TclCmd/binary.htm#M3 . So you should write
set ret [binary scan $ret B*]
By the way, a shorter way to write your loop would be
foreach bit [split $bin {}] {
append ret [expr {$bit ? 0 : 1}]
}
This is the final solution I worked up to:
proc revTwosComplmnt { bin } {
set tret ""
set binarr [split $bin {}]
for {set index [expr [llength $binarr] - 1]} {$index >= 0} {incr index -1} {
if {[regexp {^0*$} $tret -]} {
#Append will not work here, hence need to prepend the bits
set tret "[lindex $binarr $index]$tret"
continue
}
set tret "[expr {[lindex $binarr $index] ? 0: 1}]$tret"
}
#puts "\n\nTwo's Complement value for $bin is $tret\n\n"
return $tret
}
Here, I was trying to return a Binary string as the 2's complement. I would later use these 8 binary characters and divide them in 2 groups of 4 binchars. Convert them to Hexadecimal and thereafter to Decimal if need be.

Finding Median and average of list in tcl

I am having trouble finding a way to calculate the median and average of a list of numbers and the resources online seem to be really limited with Tcl. So far I managed to only print the numbers of the list.
Your help would be greatly appreciated.
proc ladd {l} {
set total 0
set counter 0
foreach nxt $l {
incr total $nxt
incr counter 1
}
puts "$total"
puts "$counter"
set average ($total/$counter)
puts "$average"
}
set a [list 4 3 2 1 15 6 29]
ladd $a
To get the average (i.e., the arithmetic mean) of a list, you can just do:
proc average {list} {
expr {[tcl::mathop::+ {*}$list 0.0] / max(1, [llength $list])}
}
That sums the values in the list (the trailiing 0.0 forces the result to be a floating point value, even if all the added numbers are integers) and divides by the number of elements (or 1 if the list is empty so an empty list gets a mean of 0.0 instead of an error).
To get the median of a list, you have to sort it and pick the middle element.
proc median {list {mode -real}} {
set list [lsort $mode $list]
set len [llength $list]
if {$len & 1} {
# Odd number of elements, unique middle element
return [lindex $list [expr {$len >> 1}]]
} else {
# Even number of elements, average the middle two
return [average [lrange $list [expr {($len >> 1) - 1] [expr {$len >> 1}]]]
}
}
To complete the set, here's how to get the mode of the list if there is a unique one (relevant for some applications where values are selected from a fairly small set):
proc mode {list} {
# Compute a histogram
foreach val $list {dict incr h $val}
# Sort the histogram in descending order of frequency; type-puns the dict as a list
set h [lsort -stride 2 -index 1 -descending -integer $h]
# The mode is now the first element
return [lindex $h 0]
}
I'll leave handling the empty and non-unique cases as an exercise.

tcl to add numbers in columns till pattern mismatches

Hi I need to add numbers in a column till pattern matches and then to start adding numbers after pattern matches, for example:
start 1
start 2
start 3
pattern
start 4
start 5
start 6
I need to have sum as 6 till pattern and 15 after pattern separately, i tried regexp start but it adds all the numbers in 2nd column irrespective of 'pattern', i know sed works, but i need in tcl-regexp only
With minimal change to your current code and your current attempt/method to reach the desired outcome, this is what I suggest:
set sum1 0
set sum2 0
set ind 0
set skip true
while {![eof $file]} {
# Notice the change of $x to x here
gets $file x
if {[regexp start $x]} {
set ind [lindex $x 1]
# Depending on $skip, add the number to either sum1 or sum2
if {$skip == "true"} {
set sum1 [expr $sum1 + $ind]
} else {
set sum2 [expr $sum2 + $ind]
}
}
if {[regexp pattern $x]} {
set skip "false"
}
}
puts $sum1
puts $sum2
Though, I would use the following to make things a bit simpler:
set sum 0
while {[gets $file x] != -1} {
# if there line has "pattern, then simply print the current sum, then resets it to zero
if {[regexp pattern $x]} {
puts $sum
set sum 0
} elseif {[regexp {start ([0-9]+)} $x - number]} {
# if the line matches 'start' followed by <space> and a number, save that number and add it to the sum
# also, I prefer using incr here than expr. If you do want to use expr, brace your expression [expr {$sum+$ind}]
incr sum $number
}
}
# puts the sum
puts $sum

TCL conditional commands using ternary operator

is it possible to run conditional commands using TCL's ternary operator?
using if statement
if {[string index $cVals $index]} {
incr As
} {
incr Bs
}
I would like to use ternary operator as follows but I get an error
invalid command name "1" while executing "[string index $cVals $index]
? incr As : incr Bs"
[string index $cVals $index] ? incr As : incr Bs
For ternary conditions, we should be using boolean values only, either 0 or 1.
So, you can't use string index directly, since it will return either a char or empty string. You have to compare whether the string is empty or not.
Also, the for the pass/fail criteria of conditions, we have to give literal values. You should use expr to evaluate the expressions.
A basic example can be ,
% expr { 0 < 1 ? "PASS" : "FAIL" }
PASS
% expr { 0 > 1 ? "PASS" : "FAIL" }
FAIL
%
Note that I have used double quotes for the string since it has the alphabets. In case of numerals, it does not have to be double quotes. Tcl will interpret numbers appropriately.
% expr { 0 > 1 ? 100 : -200 }
-200
% expr { 0 < 1 ? 100 : -200 }
100
%
Now, what can be done to your problem ?
If you want to use any commands (such as incr in your case), it should be used within square brackets, to mark that as a command.
% set cVals "Stackoverflow"
Stackoverflow
% set index 5
5
% # Index char found. So, the string is not empty.
% # Thus, the variable 'As' is created and updated with value 1
% # That is why we are getting '1' as a result.
% # Try running multiple times, you will get the updated values of 'As'
% expr {[string index $cVals $index] ne {} ? [incr As] : [incr Bs] }
1
% info exists As
1
% set As
1
% # Note that 'Bs' is not created yet...
% info exists Bs
0
%
% # Changing the index now...
% set index 100
100
% # Since the index is not available, we will get empty string.
% # So, our condition fails, thus, it will be increment 'Bs'
% expr {[string index $cVals $index] ne {} ? [incr As] : [incr Bs] }
1
% info exists Bs
1
%

Comparison of two alphanumeric values in tcl

Can anyone help me in comparing two alphanumeric values in tcl.
If say I have two values of versions
set val1 "2.1.a"
set val2 "1.2.a"
And if I want to get the max of two values i.e. $val1 ( as per above example), how can I do that?
Is there any way besides doing character by character comparison?
Possible set of values:
1.0
1.1a
1.2.3f
2.1
Thanks in advance.
try doing as follows:
set val1 "2.1.a"
puts $val1
set val2 "1.2.a"
puts $val2
puts [string compare $val2 $val1]
It performs a character-by-character comparison of strings string1 and string2. Returns -1, 0, or 1, depending on whether string1 is lexicographically less than, equal to, or greater than string2
Go through this link for further details;
hope it will solve your problem.
I would break up the version string into a list, compare them one by one:
# Breaks the version string into a list of tokens
proc tokenize {v} {
set v [string map { " " "" } $v]
set result {}
foreach token [split $v ".-"] {
set tokens_scanned [scan $token "%d%s" number alpha]
if {$tokens_scanned == 0} {lappend result $token} ;# is alpha, e.g. beta
if {$tokens_scanned == 1} {lappend result $number} ;# is number, e.g. 5
if {$tokens_scanned == 2} {lappend result $number $alpha} ;# is both, e.g. 5beta
}
return $result
}
# Examples of versions this function handles:
# 1, 1a, 1.5, 3.2b, 3.2.1, 3.2.1-beta1
proc compare_version {v1 v2} {
# Sanitize the data
set v1 [tokenize $v1]
set v2 [tokenize $v2]
foreach token1 $v1 token2 $v2 {
if {$token1 < $token2} { return -1 }
if {$token1 > $token2} { return 1 }
}
return 0
}
By Tcl documentation, to compare the two version numbers in tcl you should use package command:
package vcompare version1 version2
Compares the two version numbers given by version1 and version2. Returns -1 if version1 is an earlier version than version2, 0 if they are equal, and 1 if version1 is later than version2.