I'm experimenting with using the Guid type in databases and applications but I don't like the string format of the Guid. It's not easily read or formatted on a report. I wanted to find a way to represent very large integers such as the Guid (a 128 bit integer under the covers), so I looked around and found a Base 36 type sample on Code Project article by Steve Barker that gave me a great start.
The problem with Base 36 is that several characters are similar to other characters or numbers, so took the code and modified it to use a limited set of 20 characters that are distinctive from numbers and other characters sufficiently to make it easy for humans to read them back or hand enter them in a user interface.
I downloaded the code and went to work making the modifications. Here's the core of the Base 30 struct:
//removed are chars similar to numbers or another char when printed: I, J, O, Q, V, Z
private static List<char> alphaDigits = new List<char>(new char[]{ 'A','B','C','D','E','F','G','H','K','L','M','N','P','R','S','T','U','W','X','Y' });
private static byte Base30DigitToNumber(char Base30Digit)
{
if(char.IsDigit(Base30Digit))
{
//Handles 0 - 9
return byte.Parse(Base30Digit.ToString());
}
else
{
//Converts one base-30 digit to it's base-10 value
if (alphaDigits.IndexOf(Base30Digit) > -1)
{
//Handles ABCDEFGHKLMNPRSTUWXY (these are letters that cannot be confused for numbers)
int index = alphaDigits.IndexOf(Base30Digit) + 10;
return (byte)(index);
}
else
{
throw new InvalidBase30DigitException(Base30Digit);
}
}
}
private static char NumberToBase30Digit(byte NumericValue)
{
//Converts a number to it's base-30 value.
//Only works for numbers <= 29.
if(NumericValue > 29)
{
throw new InvalidBase30DigitValueException(NumericValue);
}
//Numbers:
if(NumericValue < 10)
{
return NumericValue.ToString()[0];
}
else
{
//Note that A is code 65, and in this
//scheme, A = 10, Y = 29 ABCDEFGHKLMNPRSTUWXY use alphaDigits for List<char>
int index = NumericValue - 10;
return alphaDigits[index]; //(char)(NumericValue + 55);
}
}
Now, I have added a couple of static methods to give me a shiny Base 30 guid string and convert that string back to a Guid here:
/// <summary>
/// Convert a Guid to a set of four Base30 string values connected by a dash character.
/// </summary>
/// <param name="g"></param>
/// <returns></returns>
public static string GuidToBase30Set(Guid g)
{
byte[] b = g.ToByteArray();
Base30 b1 = BitConverter.ToUInt32(b, 0);
Base30 b2 = BitConverter.ToUInt32(b, 4);
Base30 b3 = BitConverter.ToUInt32(b, 8);
Base30 b4 = BitConverter.ToUInt32(b, 12);
return string.Format("{0}-{1}-{2}-{3}", b1, b2, b3, b4);
}
/// <summary>
/// Convert the Base30 set string produced by GuidToBase30Set back to a Guid.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static Guid Base30SetToGuid(string s)
{
string[] p = s.Split('-');
if (p.Length != 4) throw new ArgumentException("Invalid Base30Set format.");
try
{
Base30 b1 = p[0];
Base30 b2 = p[1];
Base30 b3 = p[2];
Base30 b4 = p[3];
uint x1 = (uint)b1.NumericValue;
uint x2 = (uint)b2.NumericValue;
uint x3 = (uint)b3.NumericValue;
uint x4 = (uint)b4.NumericValue;
byte[] a1 = BitConverter.GetBytes(x1);
byte[] a2 = BitConverter.GetBytes(x2);
byte[] a3 = BitConverter.GetBytes(x3);
byte[] a4 = BitConverter.GetBytes(x4);
byte[] gb = new byte[16];
a1.CopyTo(gb, 0);
a2.CopyTo(gb, 4);
a3.CopyTo(gb, 8);
a4.CopyTo(gb, 12);
return new Guid(gb);
}
catch
{
throw new ArgumentOutOfRangeException("Invalid Base30Set string.");
}
}
The code above gets you something like this:
Common Guid string: b908d243-c1ac-4ea4-a954-121e4ab5c334
Base30Set from same: 47PGC95-1S8WCHP-MPTTA1-16CUN8P
You can download the code here (Base30.zip 3.6 KB).