Skip to content

Instantly share code, notes, and snippets.

@jrusbatch
Created September 2, 2012 21:23
Show Gist options
  • Save jrusbatch/3604666 to your computer and use it in GitHub Desktop.
Save jrusbatch/3604666 to your computer and use it in GitHub Desktop.
A C# type representing a base-36 encoded 64-bit integer.
using System;
using System.Collections.Generic;
[Serializable]
public struct Base36 : IEquatable<Base36>, IComparable<Base36>
{
private const string Characters = "0123456789abcdefghijklmnopqrstuvwxyz";
public static readonly Base36 MaxValue = new Base36(long.MaxValue);
public static readonly Base36 MinValue = new Base36(long.MinValue + 1);
private readonly long numericValue;
private Base36(long value)
{
numericValue = value;
}
public static bool TryParse(string value, out Base36 base36)
{
if (value == null)
{
base36 = default(Base36);
return false;
}
value = value.ToLowerInvariant();
var isNegative = value[0] == '-';
if (isNegative)
{
value = value.Substring(1);
}
var result = 0L;
var position = 0;
for (var i = value.Length - 1; i >= 0; i--)
{
var character = value[i];
var index = Characters.IndexOf(character);
if (index < 0)
{
base36 = default(Base36);
return false;
}
result += index * (long)Math.Pow(36, position);
position++;
}
if (isNegative)
{
result *= -1;
}
base36 = new Base36(result);
return true;
}
public static bool operator ==(Base36 left, Base36 right)
{
return EqualityComparer<Base36>.Default.Equals(left, right);
}
public static bool operator !=(Base36 left, Base36 right)
{
return !(left == right);
}
public static explicit operator int(Base36 value)
{
if (value.numericValue > int.MaxValue || value.numericValue < int.MinValue)
{
throw new OverflowException();
}
return (int)value.numericValue;
}
public static implicit operator Base36(int value)
{
return new Base36(value);
}
public static implicit operator long(Base36 value)
{
return value.numericValue;
}
public static implicit operator Base36(long value)
{
if (value == long.MinValue)
{
throw new OverflowException();
}
return new Base36(value);
}
public int CompareTo(Base36 other)
{
return numericValue.CompareTo(other.numericValue);
}
public override string ToString()
{
var isNegative = numericValue < 0;
var absoluteValue = Math.Abs(numericValue);
var base36 = string.Empty;
while (absoluteValue != 0)
{
base36 = Characters[(int)(absoluteValue % Characters.Length)] + base36;
absoluteValue = absoluteValue / Characters.Length;
}
return isNegative ? '-' + base36 : base36;
}
public override bool Equals(object obj)
{
if (!(obj is Base36))
{
return false;
}
return Equals((Base36)obj);
}
public bool Equals(Base36 other)
{
return numericValue.Equals(other.numericValue);
}
public override int GetHashCode()
{
return numericValue.GetHashCode();
}
}
using System;
using Xunit;
public class Base36Tests
{
[Fact]
public void TryParse_WhenValueIsNull_ReturnsFalse()
{
Base36 base36;
Assert.False(Base36.TryParse(null, out base36));
}
[Fact]
public void TryParse_WhenValueIsNull_SetsOutParameterToTheDefaultValue()
{
Base36 base36;
Base36.TryParse(null, out base36);
Assert.Equal(default(Base36), base36);
}
[Fact]
public void TryParse_WhenValueContainsAnInvalidCharacter_ReturnsFalse()
{
Base36 base36;
Assert.False(Base36.TryParse("foo-", out base36));
}
[Fact]
public void TryParse_WhenValueContainsAnInvalidCharacter_SetsOutParameterToTheDefaultValue()
{
Base36 base36;
Base36.TryParse(null, out base36);
Assert.Equal(default(Base36), base36);
}
[Fact]
public void TryParse_HandlesNegativeValues()
{
Base36 expected = -5L;
Base36 actual;
Assert.True(Base36.TryParse("-5", out actual));
Assert.Equal(expected, actual);
}
[Fact]
public void EqualityOperator_WhenParametersAreEqual_ReturnsTrue()
{
const long Value = 32;
Base36 first = Value;
Base36 second = Value;
Assert.True(first == second);
}
[Fact]
public void EqualityOperator_WhenParametersAreNotEqual_ReturnsFalse()
{
const long FirstValue = 32;
const long SecondValue = 24;
Base36 first = FirstValue;
Base36 second = SecondValue;
Assert.False(first == second);
}
[Fact]
public void InequalityOperator_WhenParametersAreEqual_ReturnsFalse()
{
const long Value = 32;
Base36 first = Value;
Base36 second = Value;
Assert.False(first != second);
}
[Fact]
public void InequalityOperator_WhenParametersAreNotEqual_ReturnsTrue()
{
const long FirstValue = 32;
const long SecondValue = 24;
Base36 first = FirstValue;
Base36 second = SecondValue;
Assert.True(first != second);
}
[Fact]
public void ExplicitCastToInt32_WhenValueIsLargerThanInt32MaxValue_ThrowsOverflowException()
{
Assert.Throws<OverflowException>(() => (int)Base36.MaxValue);
}
[Fact]
public void ExplicitCastToInt32_WhenValueIsLessThanInt32MinValue_ThrowsOverflowException()
{
Assert.Throws<OverflowException>(() => (int)Base36.MinValue);
}
[Fact]
public void ExplicitCastToInt32_DoesNotThrow()
{
int result;
Assert.DoesNotThrow(() => result = (int)default(Base36));
}
[Fact]
public void ImplicitCastToInt32_DoesNotThrow()
{
Base36 result;
Assert.DoesNotThrow(() => result = (Base36)default(int));
}
[Fact]
public void ImplicitCastFromInt64_WhenValueIsLongMaxValue_ReturnsMaxValue()
{
var first = (Base36)long.MaxValue;
Assert.Equal(Base36.MaxValue, first);
}
[Fact]
public void ImplicitCastFromInt64_WhenValueIsLongMinValue_ThrowsOverflowException()
{
Base36 value;
Assert.Throws<OverflowException>(() => value = (Base36)long.MinValue);
}
[Fact]
public void ImplicitCastToInt64_DoesNotThrow()
{
long value;
Assert.DoesNotThrow(() => value = (long)default(Base36));
}
[Fact]
public void ToString_WhenValueIsDefault_ReturnsEmptyString()
{
var value = default(Base36);
Assert.Equal(string.Empty, value.ToString());
}
[Fact]
public void ToString_WhenParsed_EqualsOriginalValue()
{
Base36 expected = 36;
Base36 actual;
Assert.True(Base36.TryParse(expected.ToString(), out actual));
Assert.Equal(expected, actual);
}
[Fact]
public void CompareTo_DelegatesToUnderlyingValue()
{
const long LargerValue = 36;
const long SmallerValue = 24;
var expected = LargerValue.CompareTo(SmallerValue);
var actual = ((Base36)LargerValue).CompareTo(SmallerValue);
Assert.Equal(expected, actual);
}
[Fact]
public void Equals_WhenOtherIsNotSameType_ReturnsFalse()
{
Base36 value = 36;
Assert.False(value.Equals(new object()));
}
[Fact]
public void Equals_WhenValuesAreEqual_ReturnsTrue()
{
Base36 first = 36;
Base36 second = 36;
object boxed = second;
Assert.True(first.Equals(boxed));
}
[Fact]
public void GetHashCode_ReturnsHashCodeOfUnderlyingValue()
{
const long Value = long.MaxValue;
var expected = Value.GetHashCode();
var actual = ((Base36)Value).GetHashCode();
Assert.Equal(expected, actual);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment