Based numbers

See Also

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

Description

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.