Created
September 2, 2012 21:23
-
-
Save jrusbatch/3604666 to your computer and use it in GitHub Desktop.
A C# type representing a base-36 encoded 64-bit integer.
This file contains 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
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(); | |
} | |
} |
This file contains 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
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