Monday, December 7, 2015

All Your Base Are Belong To Us

I wanted to encode a number in Base 36. A simple enough task, but as these things sometimes do, this took me down the rabbit hole of wanting to create a generic routine. I needed a set of methods, one that would take an integer and convert it to an arbitrary base and another to convert it back to a Base 10 number. There are a bunch of examples floating around the web but I wanted to create my own (combining some of the best elements from the code I found). It's a relatively easy problem to solve but along the way I found some interesting tricks to speed up the process.

So here is my C# take on this old problem:

// Chars to use in Base conversion
private const string BASE_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
/// <summary>
/// Convert a base 10 number to a different base.
/// </summary>
/// <param name="number">Number to convert</param>
/// <param name="radix">Base to convert the number into (2-62)</param>
/// <returns>String</returns>
public static string NumberToBase(long number, int radix) {
 
  if (number == 0) return "0";
  if (radix < 2 || radix > BASE_DIGITS.Length)
    throw new ArgumentOutOfRangeException("radix", radix, "The Radix/Base must be between 2 and " + BASE_DIGITS.Length);
 
  var baseChars = BASE_DIGITS.ToCharArray();
  var charStack = new Stack<char>();
  var num = Math.Abs(number);
 
  while (num != 0) {
    charStack.Push(baseChars[num % radix]);
    num /= radix;
  }
  var result = new string(charStack.ToArray());
  return number < 0 ? "-" + result : result;
}
 
/// <summary>
/// Convert a string in a different base to a base 10 number
/// </summary>
/// <param name="number">Number to convert</param>
/// <param name="radix">Base to convert from</param>
/// <returns>Int64</returns>
public static long NumberFromBase(string number, int radix) {
  if (radix < 2 || radix > BASE_DIGITS.Length)
    throw new ArgumentOutOfRangeException("radix", radix, "The Radix/Base must be between 2 and " + BASE_DIGITS.Length);
 
  long result = 0;
  long multiplier = 1;
 
  for(var i = number.Length - 1; i >= 0; i--) {
    var c = number[i];
    // This is the negative sign symbol
    if (i == 0 && c == '-') {
      result = -result;
      break;
    }
 
    result += BASE_DIGITS.IndexOf(c) * multiplier;
    multiplier *= radix;
  }
  return result;
}

I used LINQPad to create a quick program to test it out.

void Main() {
  Random r = new Random();
  for (int i = -1000; i < 1000000; i++) {
    var basex = r.Next(2, 62);
    var encoded = NumberToBase(i, basex);
    var decoded = NumberFromBase(encoded, basex);
     
    if (i != decoded) {
      Console.WriteLine("i: {0} enc: {1} dec: {2}", i, encoded, decoded);
    }
  }
}

No comments: