Skip to content

Instantly share code, notes, and snippets.

@cameronism
Created July 12, 2013 07:36
Show Gist options
  • Select an option

  • Save cameronism/5982609 to your computer and use it in GitHub Desktop.

Select an option

Save cameronism/5982609 to your computer and use it in GitHub Desktop.
Fast arbitrary base conversion for .NET
public sealed class BaseConverter
{
public readonly int Base;
private readonly char[] _Lookup;
private readonly int[] _Reverse;
private readonly static BaseConverter[] _Converters = new BaseConverter[61];
public static BaseConverter Get(byte @base)
{
@base -= 2;
if (@base >= _Converters.Length)
{
throw new ArgumentOutOfRangeException("base", "Must be between 2 and 62");
}
var converter = _Converters[@base];
if (converter == null)
{
var fresh = Create(@base + 2);
converter = Interlocked.CompareExchange<BaseConverter>(ref _Converters[@base], fresh, null);
converter = converter ?? fresh;
converter.Dump();
}
return converter;
}
static BaseConverter Create(int @base)
{
int next;
IEnumerable<int> range;
next = Math.Min(10, @base);
range = Enumerable.Range('0', next);
if (@base > 36)
{
range = range.Concat(Enumerable.Range('A', 26));
next = Math.Min(26, @base - 36);
range = range.Concat(Enumerable.Range('a', next));
}
else if (@base > 10)
{
next = Math.Min(26, @base - 10);
range = range.Concat(Enumerable.Range('a', next));
}
return new BaseConverter(range.Select(i => (char)i));
}
static int[] BuildReverse(char[] lookup, bool toUpper, bool toLower)
{
int max = lookup.Max(ch => (int)ch);
int[] reverse = Enumerable.Repeat(-1, max + 1).ToArray();
for (int i = 0; i < lookup.Length; i++)
{
reverse[lookup[i]] = i;
if (toUpper)
{
reverse[lookup[i].ToString().ToUpperInvariant()[0]] = i;
}
if (toLower)
{
reverse[lookup[i].ToString().ToLowerInvariant()[0]] = i;
}
}
return reverse;
}
public BaseConverter(IEnumerable<char> digits, bool toUpper = false, bool toLower = false)
{
_Lookup = digits.ToArray();
_Reverse = BuildReverse(_Lookup, toUpper, toLower);
Base = _Lookup.Length;
// reverse.Select((i, j) => new { j, i }).Dump();
// lookup.Dump();
}
// ThreadStaticAttribute (vs direct static field access without locking)
// slows ToString execution by about 5% for random [0, int.MaxValue]
[ThreadStatic]
static char[] _CachedBuffer;
// long.MaxValue in base2 is 63 '1's
const int BUFFER_LENGTH = 64;
public string ToString(long num)
{
// the int32 version runs up to twice as fast as the int64 version
if (num < int.MaxValue) return ToString((int)num);
var buffer = _CachedBuffer;
if (buffer == null)
{
_CachedBuffer = buffer = new char[BUFFER_LENGTH];
}
int index = BUFFER_LENGTH;
while (num > 0)
{
long rem;
num = Math.DivRem(num, Base, out rem);
buffer[--index] = _Lookup[rem];
}
return new String(buffer, index, BUFFER_LENGTH - index);
}
public string ToString(int num)
{
if (num == 0) return "0";
var buffer = _CachedBuffer;
if (buffer == null)
{
_CachedBuffer = buffer = new char[BUFFER_LENGTH];
}
int index = BUFFER_LENGTH;
while (num > 0)
{
int rem;
num = Math.DivRem(num, Base, out rem);
buffer[--index] = _Lookup[rem];
}
return new String(buffer, index, BUFFER_LENGTH - index);
}
public long ToInt64(string str)
{
long value;
if (TryParseInt64(str, 0, str.Length, out value))
{
return value;
}
throw new ArgumentException("Could not parse number", "str");
}
public long ToInt64(string str, int start, int count)
{
long value;
if (TryParseInt64(str, start, count, out value))
{
return value;
}
throw new ArgumentException("Could not parse number", "str");
}
public bool TryParseInt64(string str, out long value)
{
return TryParseInt64(str, 0, str.Length, out value);
}
public bool TryParseInt64(string str, int start, int count, out long value)
{
long num = 0;
for (int i = 0; i < count; i++)
{
int ch = str[start + i];
ch = ch < _Reverse.Length ? _Reverse[ch] : -1;
if (ch == -1)
{
value = 0;
return false;
}
num = ch + (Base * num);
}
value = num;
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment