Created
July 12, 2013 07:36
-
-
Save cameronism/5982609 to your computer and use it in GitHub Desktop.
Fast arbitrary base conversion for .NET
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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