rvb A general base-conversion procedure for arbitrary length string representations of numbers with integer and/or fraction.
#=============================================================================
# PROC : baseconvert
# PURPOSE : convert number in one base to another base
# AUTHOR : Richard Booth
# DATE : Fri Jul 14 10:40:50 EDT 2006
# ---------------------------------------------------------------------------
# ARGUMENTS :
# % base_from
# original base (expressed in base 10)
# % base_to
# base to convert number to (expressed in base 10)
# % number
# number expressed in base_from (must have form int.fra, int, or .fra)
# RESULTS :
# * returns number expressed in base_to
# EXAMPLE-CALL :
#{
# set num16 [baseconvert 10 16 3.1415926535]
#}
#=============================================================================
proc baseconvert {base_from base_to number} {
set number [string tolower $number]
if {![regexp {([0-9a-z]*)\.?([0-9a-z]*)} $number match sint sfra]} {
puts "baseconvert error: number \"$number\" is not in correct format"
return ""
}
set map 0123456789abcdefghijklmnopqrstuvwxyz
set i -1
foreach c [split $map ""] {
incr i
set D2I($c) $i
set I2D($i) $c
}
set lint [string length $sint]
set lfra [string length $sfra]
set converted_number 0
if {$lint > 0} {
set B {}
foreach c [split $sint ""] {
lappend B $D2I($c)
}
set aint ""
while {1} {
set s 0
set r 0
set C {}
foreach b $B {
set v [expr {$b + $r*$base_from}]
set b [expr {$v/$base_to}]
set r [expr {$v%$base_to}]
incr s $b
lappend C $b
}
set B $C
set aint "$I2D($r)$aint"
if {$s == 0} {break}
}
set converted_number $aint
}
if {$lfra > 0} {
set s [expr {int(1.0*$lfra*log($base_from)/log($base_to))}]
set B {}
foreach c [split $sfra ""] {
set B [linsert $B 0 $D2I($c)]
}
set afra ""
for {set j 0} {$j < $s} {incr j} {
set r 0
set C {}
foreach b $B {
set v [expr {$base_to*$b + $r}]
set r [expr {$v/$base_from}]
set b [expr {$v%$base_from}]
lappend C $b
}
set B $C
set afra "$I2D($r)$afra"
}
append converted_number .$afra
}
return $converted_number
}a small test script for baseconvert: set fmt "%-10s (base %-2d) => %-10s (base %-2d)"
foreach number {12ff.abb 122. 125 .222 0.222 0.22 0.2 0.1} {
puts [format $fmt $number 16 [baseconvert 16 10 $number] 10]
puts [format $fmt $number 16 [baseconvert 16 8 $number] 8]
puts [format $fmt $number 16 [baseconvert 16 2 $number] 2]
}glennj Here's a simpler implementation, but integer values only
namespace eval baseconvert {
variable chars "0123456789abcdefghijklmnopqrstuvwxyz"
namespace export baseconvert
}
proc baseconvert::dec2base {n b} {
# algorithm found at http://www.rosettacode.org/wiki/Number_base_conversion#Python
variable chars
expr {$n == 0 ? 0
: "[string trimleft [dec2base [expr {$n/$b}] $b] 0][string index $chars [expr {$n%$b}]]"
}
}
proc baseconvert::base2dec {n b} {
variable chars
set sum 0
foreach char [split $n ""] {
set sum [expr {($sum * $b) + [string first $char $chars]}]
}
return $sum
}
proc baseconvert::baseconvert {num basefrom baseto} {
dec2base [base2dec $num $basefrom] $baseto
}rvb A related problem is sampling a multi-dimensional grid.Lars H: Hmm... That's a rather antiquated way to do it, though. If you really want all points in the grid, then it is easier to construct it as the Cartesian product of a list of lists.rvb Friendly jabs aside, my point was that counting in any particular base is like climbing a multidimensional grid (or even the geometric analogue, to a lightheaded antiquarian.) However, the Cartesian product of a list of lists approach, which I tried with the recursive procedure, is very elegant.
#=============================================================================
# PROC : gridsample
# PURPOSE : uniformly sample a normalized hypercube
# AUTHOR : Richard Booth
# DATE : Tue Jul 18 11:19:10 EDT 2006
# ---------------------------------------------------------------------------
# ARGUMENTS :
# % ndiv
# number of divisions of the normalized hypercube
# (each independent variable range is [-1, 1])
# % nind
# number of independent variables
# RESULTS :
# * returns list of (normalized) samples
# EXAMPLE-CALL :
#{
# set samples [gridsample 5 2]
#}
#=============================================================================
proc gridsample {ndiv nind} {
if {$nind < 1 || $ndiv < 1} {
return {}
}
set samples {}
set base [expr $ndiv+1]
set npts [expr pow($base, $nind)]
for {set i 0} {$i < $npts} {incr i} {
set sample {}
set v $i
for {set j 0} {$j < $nind} {incr j} {
set w [expr int($v/$base)]
set r [expr $v - $base*$w]
set v $w
lappend sample [expr {2.0*$r/$ndiv-1}]
}
lappend samples [join $sample]
}
return $samples
}a test script for gridsample: set nind 3
set ndiv 6
set samples [gridsample $ndiv $nind]
puts "sample A B C"
set iexpt -1
foreach sample $samples {
puts "[incr iexpt] [join $sample]"
}MJ - for conversion of large integers to hex the above proc is fairly slow (probably because it doesn't take advantage of Tcl 8.5 large integer support).The proc below is much faster.
# needs Tcl 8.5 for large integer support
proc hex {num} {
set res {}
set hex_list {0 1 2 3 4 5 6 7 8 9 A B C D E F}
while {$num / 16 != 0} {
set rest [expr {$num%16}]
set res [lindex $hex_list $rest]$res
set num [expr {$num/16}]
}
set res [lindex $hex_list $num]$res
}
% time {baseconvert 10 16 25543398472347234723447294729472384329374982742984729472347247729472984264726487264284628462846274628462846284628462846284623874623874623784623486248726487642846} 100
879291.28 microseconds per iteration
% time {hex 25543398472347234723447294729472384329374982742984729472347247729472984264726487264284628462846274628462846284628462846284623874623874623784623486248726487642846} 100
4507.07 microseconds per iterationSEH 20111201 --For hex conversion: format %llx $num
% time {hex 25543398472347234723447294729472384329374982742984729472347247729472984264726487264284628462846274628462846284628462846284623874623874623784623486248726487642846} 100
1867.9400000000001 microseconds per iteration
% time {format %llx 25543398472347234723447294729472384329374982742984729472347247729472984264726487264284628462846274628462846284628462846284623874623874623784623486248726487642846} 100
19.329999999999998 microseconds per iterationrvb Replaced with faster (30x to 40x) list-based code. Previous baseconvert was very slow mainly because exprs were not {}'d (see Tcl Performance).For what it's worth, here's a formula calculator for baseconvert formula calculators:
ForCalc basecalc {
basea "base A" {} 10 b
baseb "base B" {} 16 a
numa "Number in base A" {} 10.0 b
numb "Number in base B" {} a.0 a
} {
a {
set basea [expr int($basea)]
set baseb [expr int($baseb)]
set numa [baseconvert $baseb $basea $numb]
}
b {
set basea [expr int($basea)]
set baseb [expr int($baseb)]
set numb [baseconvert $basea $baseb $numa]
}
} -title "base conversion"
