Numeric Base Conversion

From LinuxServerTech

Jump to: navigation, search


Discussion

Sometimes you need to read a number using a different base, ie base 16 or such. While there are various routines which are much faster than this code built into many libraries, a generic "read a base x number" and/or "write a base x number" routines are difficult to find.

Following is a Perl implementation with a few added quirks. For one thing, it allows you to define the characters used to display a value in a given digit position. In Base 16, we are used to using 0-9 for our "normal" values of 0-9 (taken from our Base 10 we learn growing up), and using A-F to represent the other values that can be in a particular position, ie the base 10 values 10-15.

However, when doing something strange like base 26, it is common practice to use the characters a-z to represent the values 0-25 which can appear at any position.

The Code

#! /usr/bin/perl -w
# used to determine the characters used to display a number
# Create a string with one character per possible value in a digit
# of the base in question. Split on // to make an array of values
my %baseDigits; 

# note, this charset is good for any base UP TO 16
# if you don't know why, think about it a little bit
$baseDigits{16} = [split( //, '0123456789abcdef')];

# for base 26, we use the alpha's, so a 0 will be displayed as an 'a'
# with a value of 25 for any digit being displayed as a 'z'
$baseDigits{26} = [split( //, 'abcdefghijklmnopqrstuvwxyz')];

# Subroutine decodes a string representation of a number (value)
# in a given base, optionally using the digit definition passed as the final
# array in the parameter list.
# returns the numeric value the string

sub decodeBaseNum {
   my ($value, $base, $digitDefinitions ) = @_;
   $digitDefinitions = $baseDigits{16} unless $digitDefinitions;
   # convert digits to hash using digit char as the key and value as the integer value
   my $i = 0;
   $digitDefinitions = { map { $_ => $i++ }   @$digitDefinitions };
   my $result = 0;
   foreach my $digits ( split( //, $value ) ) {
      $result = $result * $base + $$digitDefinitions{$digits};
   }
   return $result;
}

# given a number (value), encodes the number as a base something number, 
# optionally using a set of digits defined by the caller

sub encodeBaseNum {
   my ( $value, $base, $digitDefinitions ) = @_;
   $digitDefinitions = $baseDigits{16} unless $digitDefinitions;
   my $result = ;
   while ( $value ) {
      $result = $$digitDefinitions[$value % $base] . $result;
      $value = int($value / $base );
   }
   return $result;
}

print 'ff base 16 is ' . &decodeBaseNum( 'ff', 16 ) . "\n";
print "1024 in base 16 is " . &encodeBaseNum( 1024, 16 ) . "\n";
print "1024 in base 16 using base 26 digits is " . &encodeBaseNum( 1024, 16, $baseDigits{26} ) . "\n";
$code = 'bba';
$num = &decodeBaseNum( $code, 26, $baseDigits{26} );
print "$code in base 26 is $num\n";
$num++;
print "$code plus one is $num or " . &encodeBaseNum( $num, 26, $baseDigits{26} ) . " in base 26\n";
1;

Caveats

  • There is no error checking in this code, so if you tell it to create a base 26 number using the base 16 charset, you will get very weird results
  • Round off errors can cause incorrect values if you exceed Perl's maximum number of valid digits