Most programming languages offer a function to remove the fractional part of a floating point value which is typically called trunc().
There's no function named trunc() in Tcl but math functions entier, int and wide serve similar purpose. The function entier() truncates the fractional part and returns the result as an arbitrary precision integer value:
% expr {entier(3.14)} 3 % expr {entier(8.9)} 8
The functions int() and wide() additionally limit the value range discarding superfluous bits. The value range of int() is determined by $tcl_platform(wordSize) and the value range of wide() is 64 bits:
% puts $tcl_platform(wordSize) 4 % expr {int(0x7FFFFFFF)} 2147483647 % expr {int(0xFFFFFFFF)} -1 % expr {int(0x80000000)} -2147483648 % expr {int(0x1FFFFFFFF)} -1 % expr {int(+inf)} integer value too large to represent % expr {int(nan)} floating point value is Not a Number
The behavior of entier() is not the same as using ceil() and floor() because the latter return floating point values.
proc ::tcl::mathfunc::trunc {x} { return [expr {($x >= 0) ? floor($x) : ceil($x)}] }
This can make a difference when dealing with arbitrary precision values:
% entier(99999999999999999)*2 199999999999999998 % trunc(99999999999999999)*2 1.9999999999999997e+17
% expr {ceil(4000)} 4000.0 % expr {ceil(4000)/3} 1333.3333333333333 % expr {entier(4000)/3} 1333
It's a nice thing that Tcl can compute in arbitrary precision but some situations require limited value ranges. Typically this is the case when implementing communication protocols or low-level drivers. In such cases computation results must fit particular data types like an unsigned byte or a 32 bit signed integer.
Doing this saturation arithmetic is often the preferred choice as discarding bits can change the value dramatically. The following trunc() function might be useful:
# trunc(x) -> truncate to integer (round towards zero) # if x is non-finite (+inf, -inf, nan) the function raises an error # trunc(x, n) -> saturate and truncate to integer having given number of bits # if n > 0: unsigned integer range (0 .. 2**n-1) # if n < 0: signed integer range (two's complement) # else: if n is zero an error is raised # trunc(x, a, z) -> saturate within range [a..z] and truncate to integer # if x is -inf: saturate result to a # if x is +inf: saturate result to z # can set a or z to -inf or +inf to get an open range proc ::tcl::mathfunc::trunc {args} { set count [llength $args] if {($count <= 0) || ($count > 3)} { error "wrong # args: should be trunc(x) | trunc(x, n) | trunc(x, a, z)" } switch -- $count { 1 { lassign $args x set result [expr {entier($x)}] } 2 { lassign $args x n if {![string is entier -strict $n]} { error [format {bad bit size "%s": must be integer} $n] } if {$n != 0} { if {$n > 0} { set p [expr {1 << $n}] set a 0 } else { set p [expr {1 << abs($n + 1)}] set a [expr {-$p}] } set result [expr {entier(min($p - 1, max($a, $x)))}] } else { # raises domain error: argument not in valid range set result nan } } 3 { lassign $args x a z if {![string is double -strict $a]} { error [format {bad range boundary "%s": must be numeric value} $a] } if {![string is double -strict $z]} { error [format {bad range boundary "%s": must be numeric value} $z] } set result [expr {entier(min($z, max($a, $x)))}] } default { error "internal error" } } return $result }
Usage:
% expr {trunc(123.4)} 123 ; # fit to 8 bit unsigned integer type % expr {trunc(257.9, 8)} 255 % expr {trunc(-1, 8)} 0 ; # fit to 16 bit signed integer type % expr {trunc(-123456.7, -16)} -32768 % expr {trunc(12345.6789, -16)} 12345 ; # fit into range 0 to 9 % expr {trunc(rand()*10, 0, 9)} ...