Version 10 of Based numbers

Updated 2003-08-11 15:35:56

Richard Suchenwirth 2002-07-06 - Every string standing for a number in a positional value system (as opposed to e.g. Roman numbers or Hebrew numbers) has to be understood relative to its base. We're most familiar with base 10 (the decimal system), where e.g. 123 stands for

 1*10^2 + 2*10^1 + 3*10^0

and in computery the bases 2 (binary), 8 (octal), and 16 (hexadecimal) are also fairly common, but any integer could function as a base as long as we have enough distinct digits. If you want to experiment with other bases, here are two routines that convert a number to and from a different base, up to 62 (base64 uses a very different sequence of digits, so I didn't go that far)

Note also that signs are preserved (unlike computery habit to have octals and hexadecimals as unsigned, resp. sign-extended). You could have that with

 base $myBase [format %u $number]

Also, the C/Tcl markup of prefixing 0 to octals and 0x to hexadecimals is not created or recognized - how would e.g. a number to base 3 be marked? Like in Ada,

 3#12021#

? So you have to keep track of number bases yourself.


Converting numbers from arbitrary bases is an earlier approach at the same topic.


 proc base {base number} {
    set negative [regexp ^-(.+) $number -> number] ;# (1)
    set digits {0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N
        O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p
        q r s t u v w x y z}
    set res {}
    while {$number} {
        set digit [expr {$number % $base}]
        set res [lindex $digits $digit]$res
        set number [expr {$number / $base}]
    }
    if $negative {set res -$res}
    set res
 }
 proc frombase {base number} {
    set digits {0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N
        O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p
        q r s t u v w x y z}
    set negative [regexp ^-(.+) $number -> number]
    set res 0
    foreach digit [split $number ""] {
        set decimalvalue [lsearch $digits $digit]
        if {$decimalvalue<0 || $decimalvalue >= $base} {
            error "bad digit $decimalvalue for base $base"
        }
        set res [expr {$res*$base + $decimalvalue}]
    }
    if $negative {set res -$res}
    set res
 }

Examples:

 % base 7 1234
 3412
 % frombase 7 3412
 1234
 % base 16 255
 FF

One might use this to experiment with number palindromes, where the sequence of digits is reverted:

 % base 19 42
 24
 % frombase 19 24
 42

# (1): In the Tcl chatroom, Michael Schlenker reported:

 set negative [expr {$number != [set number [expr {abs($number)}]]}] 

is about five times faster if negative, and a bit faster if positive.


A number to base 11 is very frequently used - the check digits in ISBN book numbers, but they use X for 10, instead of A as the code above would do.


MSW responds to: Also, the C/Tcl markup of prefixing 0 to octals and 0x to hexadecimals is not created or recognized - how would e.g. a number to base 3 be marked?

 Number['r'|'R'][digits or letters]

 0r16  ;# == 0x0
 0r1   ;# == 0b0
 0R8   ;# == 00
 0r10  ;# == 0
 0rf   ;# == 0x0
 0r27

That's e.g. how icon does it, not 100% sure about the 'r', but lisp doesn it that way, too.


Category Mathematics - Arts and crafts of Tcl-Tk programming