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

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.

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 does it that way, too.

escargo 2005-08-22: That's not *quite* the way it works in Icon. It's really *digit-literal radix-specification digit-specification*, where *radix-specification* is **r** or **R**. The number specifying the radix comes first:

16rff == 0xff 8r17 == 017 2r11 == 0b11

escargo 2003-08-11: It is worth noting that base does not have to be a **positive** integer. I did some work (for a high school extra credit assignment) for base **-2**. These numbers did not have a sign bit in their representations, yet could still represent both positive and negative numbers. The logic for the math operations look funny, but it does work.