Numeric Value Comparison in Tcl - tcl

I want to know how to get a numeric value in TCL. What I mean is that, if the value is not numeric, result should fail else pass.
The below is what I have tried;
set trueKIND false
set trueKINDlist [list 1 2 3 4 5 6 7 8 9 10]
if {[lsearch -exact $trueKINDlist $Registrant(KIND)] >= 0} {
set trueKIND true
}
But what happens if the value of trueKINDlist > 10, this code certainly will fail...
Can somebody please tell me how I can write this in TCL? Or assist me with the operator to use in achieving this...
Thanks
Mattie

You can validate the string by [string is ...] procedure. For example:
set trueKIND [string is integer -strict $Registrant(KIND)]
Reference: https://www.tcl.tk/man/tcl8.6/TclCmd/string.htm#M10

You've got to think what kind of validation you want. For example, if you want to just validate that the value is an integer, any integer, do this:
if {![string is entier -strict $value]} {
error "$value is not an integer"
}
(There is also string is integer, but that uses a restricted 32-bit range for historical reasons, and string is wide uses a 64-bit range. For floating point numbers, use string is double. The -strict is required here; without it the empty string is also accepted; again, this is for historical reasons.)
When you have a particular range you want the value to be in, you use a compound condition:
if {![string is entier -strict $value] || !($value >= 0 && $value <= 10)} {
error "$value is not an integer in the range (0..10)"
}
If you are doing this a lot, use a procedure to make it clearer:
proc IntegerInRange {value lowerBound upperBound} {
expr {[string is entier -strict $value] && $value >= $lowerBound && $value <= $upperBound}
}
if {![IntegerInRange $value 0 10]} {
error "$value is not an integer in the range (0..10)"
}
if {![IntegerInRange $value2 3 25]} {
error "$value2 is not an integer in the range (3..25)"
}

Related

How to check value of variable is between MAX_INT and MIN_INT of 32 bit OS

I understand that all variables are of type strings.
How can we check a variable value between MAX_INT(0x7FFFFFFF) and MIN_INT (-0x80000000) of 32 bit OS
set var "12334"
...How to check var variable datatype and value range ..
The string is int command does most of the work. You can use tcl::mathop::<= for the rest:
set MIN_INT -0x80000000
set MAX_INT 0x7FFFFFFF
if {[string is int -strict $value] && [tcl::mathop::<= $MIN_INT $value $MAX_INT]} {
puts "$value is a proper 32-bit signed integer"
}
You can use this if you prefer (the parentheses are just for clarity):
if {[string is int -strict $value] && ($MIN_INT <= $value) && ($value <= $MAX_INT)} {

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
%

Tcl error in doing sum

I want to compute average on a list of numbers stored in a tcl list P. Here's my script :
set sum 0.0
foreach e $P { set sum [expr {$sum + $e}] }
set avg [expr {1.0*$sum / [llength $P]}]
But I have the error : can't use non-numeric string as operand of "+"
How can I do the sum ?
Your problem is probably due to some element in P not being a number. In any case, this is how you calculate average:
package require math::statistics
::math::statistics::mean $P
assuming P is a list of numbers.
If you have a list of data items and want to know if any of them are unsuitable for expr arithmetic you can do something like this:
foreach n $data {
if {![string is double -strict $n]} {
error "$n is not a number"
}
}
This will report the first non-number. The string is double command recognizes both integers and floating point numbers1. If you leave out the -strict flag, the empty string will be considered a number (expr will still choke on it, though2).
This will give you the sublist of all non-number items in $data:
lmap n $data {
if {![string is double -strict $n]} {set n} continue
}
And this will give you the sublist of all proper-number items in $data:
lmap n $data {
if {[string is double -strict $n]} {set n} continue
}
1 the name "double" indicates that it returns true for any string that can be translated to the C data type double, which refers specifically to storage of a double precision floating point number, (an industry standard for encoding floating-point numbers). If you don't know what that is, you can pretend that it means double as in "both numbers that look like integers and numbers that look like reals".
2 expr will also choke on the value NaN which is a perfectly valid floating point value, it just stands for "not a number".
Documentation: continue, error, expr, foreach, if, lmap, math::statistics package, package, set, string

TCL incr gives wrong value for zero padded integer

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.

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.