Click here to Skip to main content
Email Password   helpLost your password?

Introduction

I found myself wanting to express a whole host of numbers in as few characters as possible. I came up with the idea of using a Base36 type to represent Base 36 numbers. This scheme is basically an extension to hexadecimal, but whereas hexadecimal stops at 15 (F), Base 36 carries on, with G being 16, all the way up to Z, which is 35. 10 in Base 36 is in fact 36 in Base 10. Base 36 has the benefit that the "numbers" are expressed by characters that are readable to humans, so this can be a good way of passing numerical data over the telephone for instance. Using Base 36, numbers up to 46,655 can be expressed using only 3 characters (ZZZ).

The Code

The code for my Base36 struct is extremely simple, so I won't go into details explaining it; download it and take a look. I've overloaded as many operators as I could, so Base 36 numbers can be added, subtracted, multiplied etc... I've tried to keep the methods of my struct consistent with the way that Microsoft labels type methods. You can instantiate a Base 36 "number" in string format, or from a standard Base 10 number:

Base36 b1 = 104;
//This has the value 104 in base 10.


Base36 b2 = "DSGFDFDZ434";
//This has the value 50,420,080,957,151,344 in Base 10.

The source files should be made into a class library. The demo project should be made into a console application, with a reference added to the class library; this demonstrates my struct in action!

And that's it! The struct seems to work very well, although I'm sure some of the code can be optimised. Good luck!

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralBase 30 Derivative Class
Tyler Jensen
14:48 24 Nov '08  
Great article. I've posted on my blog a derivitive of the code for a Base 30 struct that removes the alpha characters that can easily be confused for another alpha or numeric character. This helps when relying on a human to read back or hand enter a value.

You can read about it and get the code on my blog post.

Tyler Jensen
Right tools, right methods!

GeneralI can't find the Exception Classes in the source code
james.wren
6:54 30 Jun '08  
The Source code won't compile because it doesn't find the exception classes. Am I missing something?
GeneralRe: I can't find the Exception Classes in the source code
Steve Barker 333
13:17 30 Jun '08  
Sorry James. That was an omission on my part! This code is so old, I'd forgotten I'd even written it. Looking back, it's not great code (I've learnt a lot since), but it does seem to work OK.

I've fixed the problem and will post an update shortly...

Cheers,

Steve.

Steve Barker

GeneralRe: I can't find the Exception Classes in the source code
Steve Barker 333
13:26 30 Jun '08  
...in fact, I can't see an obvious way to add new source code, so I'll post the code here.

Here's an update version of Base-36:
<code>
            /// <summary>
     /// Class representing a Base36 number
     /// </summary>
     public struct Base36
     {
          #region Constants (and pseudo-constants)

          /// <summary>
          /// Base36 containing the maximum supported value for this type
          /// </summary>
          public static readonly Base36 MaxValue = new Base36(long.MaxValue);
          /// <summary>
          /// Base36 containing the minimum supported value for this type
          /// </summary>
          public static readonly Base36 MinValue = new Base36(long.MinValue + 1);

          #endregion
         
          #region Fields

          private long numericValue;

          #endregion

          #region Constructor

          /// <summary>
          /// Instantiate a Base36 number from a long value
          /// </summary>
          /// <param name="NumericValue">The long value to give to the Base36 number</param>
          public Base36(long NumericValue)
          {
               numericValue = 0; //required by the struct.
               this.NumericValue = NumericValue;
          }


          /// <summary>
          /// Instantiate a Base36 number from a Base36 string
          /// </summary>
          /// <param name="Value">The value to give to the Base36 number</param>
          public Base36(string Value)
          {
               numericValue = 0; //required by the struct.
               this.Value = Value;
          }


          #endregion

          #region Properties

          /// <summary>
          /// Get or set the value of the type using a base-10 long integer
          /// </summary>
          public long NumericValue
          {
               get
               {
                    return numericValue;
               }
               set
               {
                    numericValue = value;
               }
          }    


          /// <summary>
          /// Get or set the value of the type using a Base36 string
          /// </summary>
          public string Value
          {
               get
               {
                    return Base36.NumberToBase36(numericValue);
               }
               set
               {
                    try
                    {
                         numericValue = Base36.Base36ToNumber(value);
                    }
                    catch
                    {
                         //Catch potential errors
                         throw new InvalidBase36StringException(value);
                    }
               }
          }
         
         
          #endregion

          #region Public Static Methods

          /// <summary>
          /// Static method to convert a Base36 string to a long integer (base-10)
          /// </summary>
          /// <param name="Base36Value">The number to convert from</param>
          /// <returns>The long integer</returns>
          public static long Base36ToNumber(string Base36Value)
          {
               //Make sure we have passed something
               if(Base36Value == "")
               {
                    throw new InvalidBase36StringException(Base36Value);
               }

               //Make sure the number is in upper case:
               Base36Value = Base36Value.ToUpper();

               //Account for negative values:
               bool isNegative = false;

               if(Base36Value[0] == '-')
               {
                    Base36Value = Base36Value.Substring(1);
                    isNegative = true;
               }
              
               //Loop through our string and calculate its value
               try
               {
                    //Keep a running total of the value
                    long returnValue = Base36DigitToNumber(Base36Value[Base36Value.Length - 1]);

                    //Loop through the character in the string (right to left) and add
                    //up increasing powers as we go.
                    for(int i = 1; i < Base36Value.Length; i++)
                    {
                         returnValue += ((long)Math.Pow(36, i) * Base36DigitToNumber(Base36Value[Base36Value.Length - (i + 1)]));
                    }

                    //Do negative correction if required:
                    if(isNegative)
                    {
                         return returnValue * -1;
                    }
                    else
                    {
                         return returnValue;
                    }
               }
               catch
               {
                    //If something goes wrong, this is not a valid number
                    throw new InvalidBase36StringException(Base36Value);
               }         
          }


          /// <summary>
          /// Public static method to convert a long integer (base-10) to a Base36 number
          /// </summary>
          /// <param name="NumericValue">The base-10 long integer</param>
          /// <returns>A Base36 representation</returns>
          public static string NumberToBase36(long NumericValue)
          {
               try
               {
                    //Handle negative values:
                    if(NumericValue < 0)
                    {
                         return string.Concat("-", PositiveNumberToBase36(Math.Abs(NumericValue)));
                    }
                    else
                    {
                         return PositiveNumberToBase36(NumericValue);
                    }
               }
               catch
               {
                    throw new InvalidBase36NumberException(NumericValue);
               }
          }


          #endregion

          #region Private Static Methods

          private static string PositiveNumberToBase36(long NumericValue)
          {
               //This is a clever recursively called function that builds
               //the base-36 string representation of the long base-10 value
               if(NumericValue < 36)
               {
                    //The get out clause; fires when we reach a number less than
                    //36 - this means we can add the last digit.
                    return NumberToBase36Digit((byte)NumericValue).ToString();         
               }
               else
               {
                    //Add digits from left to right in powers of 36
                    //(recursive)
                    return string.Concat(PositiveNumberToBase36(NumericValue / 36), NumberToBase36Digit((byte)(NumericValue % 36)).ToString());
               }
          }


          private static byte Base36DigitToNumber(char Base36Digit)
          {
               //Converts one base-36 digit to it's base-10 value
               if(!char.IsLetterOrDigit(Base36Digit))
               {
                    throw new InvalidBase36CharacterValueException(Base36Digit);
               }

               if(char.IsDigit(Base36Digit))
               {
                    //Handles 0 - 9
                    return byte.Parse(Base36Digit.ToString());
               }
               else
               {
                    //Handles A - Z
                    return (byte)((int)Base36Digit - 55);
               }
          }


          private static char NumberToBase36Digit(byte NumericValue)
          {
               //Converts a number to it's base-36 value.
               //Only works for numbers <= 35.
               if(NumericValue > 35)
               {
                    throw new InvalidBase36DigitValueException(NumericValue);
               }

               //Numbers:
               if(NumericValue <= 9)
               {
                    return NumericValue.ToString()[0];
               }
               else
               {
                    //Note that A is code 65, and in this
                    //scheme, A = 10.
                    return (char)(NumericValue + 55);
               }
          }
              

          #endregion

          #region Operator Overloads

          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static bool operator > (Base36 LHS, Base36 RHS)
          {
               return LHS.numericValue > RHS.numericValue;
          }

    
          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static bool operator < (Base36 LHS, Base36 RHS)
          {
               return LHS.numericValue < RHS.numericValue;
          }


          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static bool operator >= (Base36 LHS, Base36 RHS)
          {
               return LHS.numericValue >= RHS.numericValue;
          }

         
          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static bool operator <= (Base36 LHS, Base36 RHS)
          {
               return LHS.numericValue <= RHS.numericValue;
          }
         
         
          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static bool operator == (Base36 LHS, Base36 RHS)
          {
               return LHS.numericValue == RHS.numericValue;
          }


          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static bool operator != (Base36 LHS, Base36 RHS)
          {
               return !(LHS == RHS);
          }


          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static Base36 operator + (Base36 LHS, Base36 RHS)
          {
               return new Base36(LHS.numericValue + RHS.numericValue);
          }


          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static Base36 operator - (Base36 LHS, Base36 RHS)
          {
               return new Base36(LHS.numericValue - RHS.numericValue);
          }

         
          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="Value"></param>
          /// <returns></returns>
          public static Base36 operator ++ (Base36 Value)
          {
               return new Base36(Value.numericValue++);
          }


          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="Value"></param>
          /// <returns></returns>
          public static Base36 operator -- (Base36 Value)
          {
               return new Base36(Value.numericValue--);
          }
                

          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static Base36 operator * (Base36 LHS, Base36 RHS)
          {
               return new Base36(LHS.numericValue * RHS.numericValue);
          }


          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static Base36 operator / (Base36 LHS, Base36 RHS)
          {
               return new Base36(LHS.numericValue / RHS.numericValue);
          }


          /// <summary>
          /// Operator overload
          /// </summary>
          /// <param name="LHS"></param>
          /// <param name="RHS"></param>
          /// <returns></returns>
          public static Base36 operator % (Base36 LHS, Base36 RHS)
          {
               return new Base36(LHS.numericValue % RHS.numericValue);
          }


          /// <summary>
          /// Converts type Base36 to a base-10 long
          /// </summary>
          /// <param name="Value">The Base36 object</param>
          /// <returns>The base-10 long integer</returns>
          public static implicit operator long (Base36 Value)
          {
               return Value.numericValue;
          }


          /// <summary>
          /// Converts type Base36 to a base-10 integer
          /// </summary>
          /// <param name="Value">The Base36 object</param>
          /// <returns>The base-10 integer</returns>
          public static implicit operator int (Base36 Value)
          {
               try
               {
                    return (int)Value.numericValue;
               }
               catch
               {
                    throw new OverflowException("Overflow: Value too large to return as an integer");
               }
          }


          /// <summary>
          /// Converts type Base36 to a base-10 short
          /// </summary>
          /// <param name="Value">The Base36 object</param>
          /// <returns>The base-10 short</returns>
          public static implicit operator short (Base36 Value)
          {
               try
               {
                    return (short)Value.numericValue;
               }
               catch
               {
                    throw new OverflowException("Overflow: Value too large to return as a short");
               }
          }


          /// <summary>
          /// Converts a long (base-10) to a Base36 type
          /// </summary>
          /// <param name="Value">The long to convert</param>
          /// <returns>The Base36 object</returns>
          public static implicit operator Base36 (long Value)
          {
               return new Base36(Value);
          }


          /// <summary>
          /// Converts type Base36 to a string; must be explicit, since
          /// Base36 > string is dangerous!
          /// </summary>
          /// <param name="Value">The Base36 type</param>
          /// <returns>The string representation</returns>
          public static explicit operator string (Base36 Value)
          {
               return Value.Value;
          }


          /// <summary>
          /// Converts a string to a Base36
          /// </summary>
          /// <param name="Value">The string (must be a Base36 string)</param>
          /// <returns>A Base36 type</returns>
          public static implicit operator Base36 (string Value)
          {
               return new Base36(Value);
          }

                     
          #endregion

          #region Public Override Methods

          /// <summary>
          /// Returns a string representation of the Base36 number
          /// </summary>
          /// <returns>A string representation</returns>
          public override string ToString()
          {
               return Base36.NumberToBase36(numericValue);
          }


          /// <summary>
          /// A unique value representing the value of the number
          /// </summary>
          /// <returns>The unique number</returns>
          public override int GetHashCode()
          {
               return numericValue.GetHashCode();
          }


          /// <summary>
          /// Determines if an object has the same value as the instance
          /// </summary>
          /// <param name="obj">The object to compare</param>
          /// <returns>True if the values are the same</returns>
          public override bool Equals(object obj)
          {
               if(!(obj is Base36))
               {
                    return false;
               }
               else
               {
                    return this == (Base36)obj;
               }
          }


          #endregion

          #region Public Methods

          /// <summary>
          /// Returns a string representation padding the leading edge with
          /// zeros if necessary to make up the number of characters
          /// </summary>
          /// <param name="MinimumDigits">The minimum number of digits that the string must contain</param>
          /// <returns>The padded string representation</returns>
          public string ToString(int MinimumDigits)
          {
               string base36Value = Base36.NumberToBase36(numericValue);

               if(base36Value.Length >= MinimumDigits)
               {
                    return base36Value;
               }
               else
               {
                    string padding = new string('0', (MinimumDigits - base36Value.Length));    
                    return string.Format("{0}{1}", padding, base36Value);
               }
          }


          #endregion

     }
</code>

...and all the exceptions you'll need:

<code>
      public class InvalidBase36CharacterValueException : Exception
      {
            #region Private Fields

            private char value;

            #endregion

            #region Internal Constructor

            internal InvalidBase36CharacterValueException(char value)
            {
                  this.value = value;
            }

            #endregion

            #region Public Override Methods

            public override string Message
            {
                  get
                  {
                        return string.Format("The character {0} is not a valid base-36 character", value);
                  }
            }

            #endregion
      }
</code>

<code>
      public class InvalidBase36DigitValueException : Exception
      {
            #region Private Fields
           
            private byte value;

            #endregion

            #region Internal Constructor

            internal InvalidBase36DigitValueException(byte value)
            {
                  this.value = value;
            }

            #endregion

            #region Public Override Methods

            public override string Message
            {
                  get
                  {
                        return string.Format("The value {0} could not be converted to a single base-36 digit", value);
                  }
            }

            #endregion
      }
</code>

<code>
      public class InvalidBase36NumberException : Exception
      {
            #region Private Fields
           
            private long value;

            #endregion

            #region Internal Constructor

            internal InvalidBase36NumberException(long value)
            {
                  this.value = value;
            }

            #endregion

            #region Public Override Methods

            public override string Message
            {
                  get
                  {
                        return string.Format("The number {0} could not be converted to a valid base-36 string", value);
                  }
            }

            #endregion
      }
</code>

<code>
      public class InvalidBase36StringException : Exception
      {
            #region Private Fields
           
            private string value;

            #endregion

            #region Internal Constructor

            internal InvalidBase36StringException(string value)
            {
                  this.value = value;
            }

            #endregion

            #region Public Override Methods

            public override string Message
            {
                  get
                  {
                        return string.Format("The string {0} is not a valid base-36 number", value);
                  }
            }

            #endregion
      }
</code>

I hope that helps?

Cheers,

Steve.

Steve Barker
GeneralTesting Code for Base36ToUInt64()
Russell Mangel
23:00 23 Mar '06  
private void TestBase36ToUInt64() {
if (Base36ToUInt64("0") != 0)
throw new Exception("Failed: 0 != 0");
if(Base36ToUInt64("1") != 1)
throw new Exception("Failed: 1 != 1");
if (Base36ToUInt64("A") != 10)
throw new Exception("Failed: A != 10");
if (Base36ToUInt64("2S") != 100)
throw new Exception("Failed: 2S != 100");
if (Base36ToUInt64("RS") != 1000)
throw new Exception("Failed: RS != 1000");
if (Base36ToUInt64("LFLS") != 1000000)
throw new Exception("Failed: LFLS != 1000000");
if (Base36ToUInt64("GJDGXS") != 1000000000)
throw new Exception("Failed: GJDGXS != 1000000000");
if (Base36ToUInt64("CRE66I9S") != 1000000000000)
throw new Exception("Failed: CRE66I9S != 1000000000000");
// Int64 Max Value
if (Base36ToUInt64("1Y2P0IJ32E8E7") != 9223372036854775807)
throw new Exception("Failed: 1Y2P0IJ32E8E7 != 9223372036854775807");
// UInt64 Max Value
if (Base36ToUInt64("3W5E11264SGSF") != 18446744073709551615)
throw new Exception("Failed: 3W5E11264SGSF != 18446744073709551615");}

Russell Mangel
Las Vegas, NV
GeneralTesting Code for UInt64ToBase36()
Russell Mangel
22:55 23 Mar '06  
private void TestUInt64ToBase36(){
if (UInt64ToBase36(0) != "0")
throw new Exception("Failed: 0 != 0");
if (UInt64ToBase36(1) != "1")
throw new Exception("Failed: 1 != 1");
if (UInt64ToBase36(10) != "A")
throw new Exception("Failed: 10 != A");
if (UInt64ToBase36(100) != "2S")
throw new Exception("Failed: 100 != 2S");
if (UInt64ToBase36(1000) != "RS")
throw new Exception("Failed: 1000 != RS");
if (UInt64ToBase36(1000000) != "LFLS")
throw new Exception("Failed: 1000000 != LFLS");
if (UInt64ToBase36(1000000000) != "GJDGXS")
throw new Exception("Failed: 1000000000 != GJDGXS");
if (UInt64ToBase36(1000000000000) != "CRE66I9S")
throw new Exception("Failed: 1000000000000 != CRE66I9S");
// UInt64 Max Value
if (UInt64ToBase36(9223372036854775807) != "1Y2P0IJ32E8E7")
throw new Exception("Failed: 9223372036854775807 != 1Y2P0IJ32E8E7");
// UInt64 Max Value
if (UInt64ToBase36(18446744073709551615) != "3W5E11264SGSF")
throw new Exception("Failed: 18446744073709551615 != 3W5E11264SGSF");}

Russell Mangel
Las Vegas, NV

-- modified at 4:01 Friday 24th March, 2006
GeneralHere is my version of Base36
Russell Mangel
22:53 23 Mar '06  
Hi,

I wrote this routine awhile ago, instead of using Int64 data types I used UInt64. If you need Int64 values, just cast the values to Int64.

I will post some test code in my next message which helps prove the methods are working correctly.

What do you think?



// Begin Source Code

public static UInt64 Base36ToUInt64(String base36String){
String s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
base36String = base36String.ToUpper();
Int32 i = 0, pos = 0;
UInt64 rv = 0;
do
{
pos = s.IndexOf(base36String[i]);
rv = (rv * 36) + (UInt32)pos;
i++;
}
while (i < base36String.Length);
return rv;}

// Here is the opposite conversion

public static String UInt64ToBase36(UInt64 uInt64){
String s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
UInt32 i = 12;
UInt64 r = 0;
Char[] c = new char[i+1];
do
{
r = (uInt64 % 36);
c[i] = s[(Int32)r];
uInt64 = (uInt64 - r) / 36;
i--;
}
while (uInt64 > 0);
Char[] trimChars = { '\0' };
return new String(c).Trim(trimChars);}

// End Source Code



Russell Mangel
Las Vegas, NV

-- modified at 4:22 Friday 24th March, 2006
GeneralRe: Here is my version of Base36
samyu1*
6:57 23 Jun '09  
Hi
Your code looks like you are a expert in programming. I need some urgent help actually you wrote the code for Converting Uint64ToBase36 and Base36ToUInt64.
can you help me out in converting a 20-digit length numeric value from Base10ToBase36

I used the following code but that has a stack overflow i.e Integral constant is too large since Long accepts only 18-digit length and mine is 20-digit length I also tried with ULong but that hasn't woked for me

        public static string Base36Encode(long value)
{
char[] base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
string returnValue = "";
while (value != 0)
{
returnValue = base36Chars[value % base36Chars.Length] + returnValue;
value /= 36;
}
return returnValue;
}

For ex: if I give this '20090326003056659359' input value the output must be like this '00048MXE74WU6X7J'

Thank you in advance.
GeneralRe: Here is my version of Base36
Steve Barker 333
12:46 24 Jun '09  
Hi Samyu1,

The only way I can think of is to use the decimal type instead of a long (unless you want to create your own customer "longer" type, but let's leave that as a last resort!).

Here's an implementation:

public static string Base36Encode(decimal value)
{
if (value == 0)
{
return "0";
}

char[] base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
string returnValue = "";

while (value != 0)
{
returnValue = base36Chars[(int)(value % (decimal)base36Chars.Length)] + returnValue;
value /= 36;
}

return returnValue;
}

It needs a bit of tidying up to remove leading zeros, but other than that, should give you a bit more breathing space!

I hope this helps,

Steve.

Steve Barker

GeneralRe: Here is my version of Base36
samyu1*
4:48 25 Jun '09  
Thank you Steve. But still I am getting the same problem Integral constant is too large the number I am using is this 20090521003032782256 and it need to be converted to this 00048MZBBINX84HC. I think if it is taken as string it would be possible can you help me out by using the string.

Thank you
GeneralRe: Here is my version of Base36
Steve Barker 333
7:21 25 Jun '09  
Did you remember to change the type of the variable you're passing in to the new version of the function to a decimal? Wink

Steve Barker

Generalwhat about this...
Jeremy Falcon
10:46 8 Jun '05  
Why not just leave the number alone (say in binary) and show it as text by bytes and then convert it back when needed?

For instance (using your example)...

50,420,080,957,151,344
=
10110011 00100000 11001011 11100110
11111111 01011000 01110000 (bin by bytes)
=
179 32 203 230 255 88 112 (dec by bytes)

This can be represented as "³ ËæÿXp" in ANSI using 7 chars/bytes whereas yours (DSGFDFDZ434) uses 11.

Jeremy Falcon
GeneralRe: what about this...
Steve Barker 333
5:30 12 Jun '05  
Absolutely! Your method takes much less space, and I use this way sometimes, but if you need to read the code out to a user (say, over the 'phone), how do you talk them through typing in your text string?! If space is really an issue and readability is not, I used a compression DLL written in .NET, and that gives even smaller strings!

Steve Barker
GeneralRe: what about this...
Jeremy Falcon
16:35 12 Jun '05  
Steve Barker 333 wrote: but if you need to read the code out to a user (say, over the 'phone)
Good point. Well, the original thought behind it was data transport and not user interaction. I could see the looks on their face when someone tries to explain "☺♥♦cN" to the user. Smile

Jeremy Falcon
GeneralFantastic!
GeminiMan
18:41 7 Jun '05  
I was just thinking about this problem with GUIDs. They're long and boring and disconcerting for users. This solves that completely!

Great work!
GeneralRe: Fantastic!
tupacs01
5:50 8 Jun '05  
I agree! I was thinking of this very problem and started working on a similar solution for system-generated ID #s - now you've done all the hard work for me Thanks!

BTW, I'm thinking of modifying your system to remove characters that might be easily mistaken for other characters or numbers. The ones that come immediately to mind are "O" and zero, "I" and one, and probably to a lesser degree "B" and eight, "S" and five, etc. Do you think this will be difficult to modify?

Thanks for the cool tool.
GeneralRe: Fantastic!
umuhk
9:47 8 Jun '05  
I did some digging a while back for a compact base-32 encoding that did exactly what you suggest, and I found this link:

http://www.crockford.com/wrmg/base32.html[^]

base-32 is slightly less efficient space-wise than base-36, but it has the advantage of aligning nicely with binary.

GeneralRe: Fantastic!
Keith Farmer
9:55 8 Jun '05  
It would also work for taking A-Z, and removing look-alikes: B, D, I, O, Q, S, Z leaves 29 characters. If you remove L as well (to get rid of the l/1/I problem if using lower or mixed case), then you have 28 characters, which aligns with nibbles.
GeneralRe: Fantastic!
Steve Barker 333
5:34 12 Jun '05  
I think you'll only need to change two methods to allow different character sets etc... I should really have coded this class with override-able methods, so people can inherit and alter the character sets use to something different. Oh well!

Steve Barker
GeneralRe: Fantastic!
tupacs01
9:03 12 Jun '05  
I was testing the app, and it appears the custom Exception definitions are missing. I went ahead and added them in manually, and it seems to work fine. Looking through the code, it looks like you could define a string as the group of digits and use the IndexOf() function. So you could replace this code:

if(!char.IsLetterOrDigit(Base36Digit))
{
throw new InvalidBase36DigitException(Base36Digit);
}
if(char.IsDigit(Base36Digit))
{
//Handles 0 - 9
return byte.Parse(Base36Digit.ToString());
}
else
{
//Handles A - Z
return (byte)((int)Base36Digit - 55);
}

With this:

string _goodDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int _digpos = _goodDigits.IndexOf(Base36Digit);
if (_digpos == -1)
{
throw new InvalidBase36DigitException(Base36Digit);
}
else
{
return ((byte)_digpos);
}

You could expose _goodDigits via a public property and let the user decide which digits they want to use. You could use the _goodDigits.Length property to determine the base of the number. Of course with a dynamic base, you'll need to store base information with each number and implement a conversion function (probably easiest to convert back to base 10 and then back out to the new base), or throw an exception if two different bases are used in the same operation (probably much easier).
GeneralRe: Fantastic!
Jeremy Falcon
10:51 8 Jun '05  
Since when should an end user deal with a GUID/UUID? They're not passwords ya know. Smile

Jeremy Falcon
GeneralRe: Fantastic!
GeminiMan
10:56 8 Jun '05  
Since we have multiple users on disconnected systems creating invoices etc. so using an incremental number is not feesible and since we already have a Guid value that makes the item unique, showing them the GUID in a very small form as the Invoice # makes great sense if you can get it under 20 characters.

This is what UPS now does as a matter of fact. (I'm not sure if it's a hash or what)
GeneralRe: Fantastic!
tupacs01
11:04 8 Jun '05  
I don't like the MS SQL generated GUID's. There are definitely better solutions out there. My main 2 problems with GUID's is that they are too darn long (32 characters without dashes) and there's no check digit. Anything that long needs to have a mechanism to prevent mis-keying an entry.

I can just imagine a customer on the phone trying to read that thing to a customer service clerk, and the clerk mis-typing it over and over again. I agree with you that unique identifiers should definitely be 20 characters or less (16 is what I usually shoot for - 4 groups of 4 characters lines up nicely all the way around).
GeneralRe: Fantastic!
Jeremy Falcon
4:27 9 Jun '05  
The problem with relying on this is that GUIDs are case-sensitive. In using this conversion you loose a bit of the guaranteed uniqueness with that.

Also, a GUID/UUID is overkill for an invoice number. They are based on MAC address, time it was created, and a random number. You can use those three things to produce a slightly short string manually.

Also, if you're really concerned about length in this manner, you always convert to a pseudo (as in not they way it really works) Base 62, where instead of 0-Z for a place holder you'd have 0-Z-z where a would represent one greater than Z. Of course, that would also mean your invoice numbers would be case sensitive -- which can lead to usability issues.

Jeremy Falcon
GeneralRe: Fantastic!
GeminiMan
5:22 9 Jun '05  
MS GUIDs are all non-case sensitive. You get the same GUID upper case as lower case, so that makes me happy.

Base 36 is fine because I don't want case sensitivity, people have to be able to read it back.

What I do is strip out the -s in the GUID then encode it with Base 36. When I decode I added a function that returns the GUID back to me if possible and puts back to the -s in the right spots before using the new Guid function on a string.

Gets it down < 20 characters.

I'm looking at just taking a hash of the GUID and see if that can get it smaller while still unique.... (SHA hashes are supposed to be Guarenteed unique I think...)


Last Updated 7 Jun 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010