//****************************************************************************************************** // RadixCodec.cs - Gbtc // // Copyright © 2017, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this // file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 08/11/2017 - Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Diagnostics.CodeAnalysis; namespace GSF { /// /// Represents a radix value codec for conversion of base-10 integer values to and from other base values. /// /// /// The primary use case of this class is to provide compact string encodings of integer values. /// The encodings produced by this class do not manage arbitrary sized bytes arrays, nor do they /// include padding - as a result, encodings are not intended to comply with RFC 3548. /// public class RadixCodec { #region [ Members ] // Nested Types private class RadixIntegerCodec where T : IComparable, IEquatable { #region [ Members ] private readonly RadixCodec m_parent; private readonly bool m_caseSensitive; private readonly int m_bitSize; private readonly T m_minValue; private readonly T m_zeroValue; private readonly Func m_abs; private readonly Func m_mod; private readonly Func m_divide; private readonly Func m_convert; private readonly string m_minRadix; #endregion #region [ Constructors ] public RadixIntegerCodec(RadixCodec parent, bool caseSensitive, int bitSize, T minValue, T zeroValue, Func abs, Func mod, Func divide, Func convert, string minRadix = null) { m_parent = parent; m_caseSensitive = caseSensitive; m_bitSize = bitSize; m_minValue = minValue; m_zeroValue = zeroValue; m_abs = abs; m_mod = mod; m_divide = divide; m_convert = convert; m_minRadix = minRadix ?? Encode(minValue); } #endregion #region [ Methods ] public T Decode(string value) { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value)); bool isNegative = false; long result = 0; long multiplier = 1; int radix = m_parent.Digits.Length; value = value.Trim(); if (!m_caseSensitive) value = value.ToUpperInvariant(); for (int i = value.Length - 1; i >= 0; i--) { if (i == 0 && value[i] == '-') { isNegative = true; break; } int digit = m_parent.Digits.IndexOf(value[i]); if (digit == -1) throw new ArgumentException($"Invalid characters in radix-{m_parent.Digits.Length} value: \"{value}\".", nameof(value)); result += digit * multiplier; multiplier *= radix; } return m_convert(result, isNegative); } public string Encode(T value) { // Check for minimum T value, this avoids a Math.Abs exception: // "Negating the minimum value of a twos complement number is invalid." if (value.Equals(m_minValue) && m_minValue.CompareTo(m_zeroValue) < 0) return m_minRadix; bool isNegative = value.CompareTo(m_zeroValue) < 0; char[] buffer = new char[m_bitSize]; int index = m_bitSize - 1; int radix = m_parent.Digits.Length; value = m_abs(value); do buffer[index--] = m_parent.Digits[m_mod(value, radix)]; while ((value = m_divide(value, radix)).CompareTo(m_zeroValue) != 0); string result = new string(buffer, index + 1, m_bitSize - index - 1); if (isNegative) result = $"-{result}"; return result; } #endregion } // Fields /// /// Defines the available digits for a radix value codec. /// public readonly string Digits; private readonly RadixIntegerCodec m_int16; private readonly RadixIntegerCodec m_uint16; private readonly RadixIntegerCodec m_int24; private readonly RadixIntegerCodec m_uint24; private readonly RadixIntegerCodec m_int32; private readonly RadixIntegerCodec m_uint32; private readonly RadixIntegerCodec m_int64; private readonly RadixIntegerCodec m_uint64; #endregion #region [ Constructors ] /// /// Creates a new . /// /// Digits to use for radix values. /// Determines if alphabetic radix characters are case sensitive. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public RadixCodec(string digits, bool caseSensitive) { if (string.IsNullOrWhiteSpace(digits)) throw new ArgumentNullException(nameof(digits)); if (digits.Length < 2) throw new ArgumentOutOfRangeException(nameof(digits), "Minimum of 2 characters required for radix value digits."); Digits = digits; // ReSharper disable once PossibleLossOfFraction RadixIntegerCodec int96 = new RadixIntegerCodec(this, caseSensitive, 64, decimal.MinValue, 0, Math.Abs, (i, d) => (int)(i % d), (i, d) => (ulong)i / (ulong)d, (v, n) => n ? -v : v); m_int64 = new RadixIntegerCodec(this, caseSensitive, 64, long.MinValue, 0L, Math.Abs, (i, d) => (int)(i % d), (i, d) => i / d, (v, n) => n ? -v : v, int96.Encode(long.MinValue)); m_uint64 = new RadixIntegerCodec(this, caseSensitive, 64, ulong.MinValue, 0UL, i => i, (i, d) => (int)(i % (ulong)d), (i, d) => i / (ulong)d, (v, n) => (ulong)(n ? -v : v)); m_int32 = new RadixIntegerCodec(this, caseSensitive, 32, int.MinValue, 0, Math.Abs, (i, d) => i % d, (i, d) => i / d, (v, n) => (int)(n ? -v : v), m_int64.Encode(int.MinValue)); m_uint32 = new RadixIntegerCodec(this, caseSensitive, 32, uint.MinValue, 0U, i => i, (i, d) => (int)(i % (uint)d), (i, d) => i / (uint)d, (v, n) => (uint)(n ? -v : v)); m_int24 = new RadixIntegerCodec(this, caseSensitive, 24, Int24.MinValue, 0, i => (Int24)Math.Abs(i), (i, d) => i % d, (i, d) => (Int24)(i / d), (v, n) => (Int24)(n ? -v : v), m_int32.Encode(Int24.MinValue)); m_uint24 = new RadixIntegerCodec(this, caseSensitive, 24, UInt24.MinValue, 0, i => i, (i, d) => (int)(i % (uint)d), (i, d) => (UInt24)(i / (uint)d), (v, n) => (UInt24)(uint)(n ? -v : v)); m_int16 = new RadixIntegerCodec(this, caseSensitive, 16, short.MinValue, 0, Math.Abs, (i, d) => i % d, (i, d) => (short)(i / d), (v, n) => (short)(n ? -v : v), m_int32.Encode(short.MinValue)); m_uint16 = new RadixIntegerCodec(this, caseSensitive, 16, ushort.MinValue, 0, i => i, (i, d) => i % (ushort)d, (i, d) => (ushort)(i / d), (v, n) => (ushort)(n ? -v : v)); } #endregion #region [ Methods ] /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(short value) => m_int16.Encode(value); /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(ushort value) => m_uint16.Encode(value); /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(Int24 value) => m_int24.Encode(value); /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(UInt24 value) => m_uint24.Encode(value); /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(int value) => m_int32.Encode(value); /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(uint value) => m_uint32.Encode(value); /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(long value) => m_int64.Encode(value); /// /// Converts integer value to a radix value. /// /// Integer value to convert. /// Radix value string. public string Encode(ulong value) => m_uint64.Encode(value); /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out short value) { try { value = m_int16.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out ushort value) { try { value = m_uint16.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out Int24 value) { try { value = m_int24.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out UInt24 value) { try { value = m_uint24.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out int value) { try { value = m_int32.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out uint value) { try { value = m_uint32.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out long value) { try { value = m_int64.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Attempts to convert a radix value to an integer value. /// /// Radix value to convert. /// Decoded integer value. /// true if decode succeeds; otherwise, false. public bool TryDecode(string radixValue, out ulong value) { try { value = m_uint64.Decode(radixValue); return true; } catch { value = 0; return false; } } /// /// Converts a radix value to an integer value. /// /// Integer type to convert /// Radix value to convert. /// Decoded integer value. /// Radix value is null. /// Invalid radix value character. /// Decoded radix value overflowed integer type. /// Only integer types Int16, UInt16, Int24, UInt24, Int32, UInt32, Int64 and UInt64 are supported. public T Decode(string radixValue) => (T)Decode(typeof(T), radixValue); /// /// Converts a radix value to an integer value. /// /// Integer type to convert. /// Radix value to convert. /// Decoded integer value. /// Radix value is null. /// Invalid radix value character. /// Decoded radix value overflowed integer type. /// Only integer types Int16, UInt16, Int24, UInt24, Int32, UInt32, Int64 and UInt64 are supported. public object Decode(Type type, string radixValue) { switch (Type.GetTypeCode(type)) { case TypeCode.Int16: return m_int16.Decode(radixValue); case TypeCode.UInt16: return m_uint16.Decode(radixValue); case TypeCode.Int32: if (type == typeof(Int24)) return m_int24.Decode(radixValue); return m_int32.Decode(radixValue); case TypeCode.UInt32: if (type == typeof(UInt24)) return m_uint24.Decode(radixValue); return m_uint32.Decode(radixValue); case TypeCode.Int64: return m_int64.Decode(radixValue); case TypeCode.UInt64: return m_uint64.Decode(radixValue); default: throw new ArgumentOutOfRangeException(nameof(type), "Only integer types Int16, UInt16, Int24, UInt24, Int32, UInt32, Int64 and UInt64 are supported."); } } #endregion #region [ Static ] // Static Fields private static RadixCodec s_radix2; private static RadixCodec s_radix8; private static RadixCodec s_radix16; private static RadixCodec s_radix32; private static RadixCodec s_radix36; private static RadixCodec s_radix64; private static RadixCodec s_radix86; // Static Properties /// /// Gets a radix-2 value (binary) encoding. /// /// /// int.MaxValue encodes to "1111111111111111111111111111111", 31 characters /// int.MinValue encodes to "-10000000000000000000000000000000", 33 characters /// uint.MaxValue encodes to "11111111111111111111111111111111", 32 characters /// long.MaxValue encodes to "111111111111111111111111111111111111111111111111111111111111111", 63 characters /// long.MinValue encodes to "-1000000000000000000000000000000000000000000000000000000000000000", 65 characters /// ulong.MaxValue encodes to "1111111111111111111111111111111111111111111111111111111111111111", 64 characters /// 12 public static RadixCodec Radix2 = s_radix2 ?? (s_radix2 = new RadixCodec("01", false)); /// /// Gets a radix-8 value (octal) encoding. /// /// /// int.MaxValue encodes to "17777777777", 11 characters /// int.MinValue encodes to "-20000000000", 12 characters /// uint.MaxValue encodes to "37777777777", 11 characters /// long.MaxValue encodes to "777777777777777777777", 21 characters /// long.MinValue encodes to "-1000000000000000000000", 23 characters /// ulong.MaxValue encodes to "1777777777777777777777", 22 characters /// 12345678 public static RadixCodec Radix8 = s_radix8 ?? (s_radix8 = new RadixCodec("01234567", false)); /// /// Gets a radix-16 value (hex) encoding. /// /// /// int.MaxValue encodes to "7FFFFFFF", 8 characters /// int.MinValue encodes to "-80000000", 9 characters /// uint.MaxValue encodes to "FFFFFFFF", 8 characters /// long.MaxValue encodes to "7FFFFFFFFFFFFFFF", 16 characters /// long.MinValue encodes to "-8000000000000000", 17 characters /// ulong.MaxValue encodes to "FFFFFFFFFFFFFFFF", 16 characters /// 1234567890123456 public static RadixCodec Radix16 = s_radix16 ?? (s_radix16 = new RadixCodec("0123456789ABCDEF", false)); /// /// Gets a radix-32 value encoding. /// /// /// int.MaxValue encodes to "1VVVVVV", 7 characters /// int.MinValue encodes to "-2000000", 8 characters /// uint.MaxValue encodes to "3VVVVVV", 7 characters /// long.MaxValue encodes to "7VVVVVVVVVVVV", 13 characters /// long.MinValue encodes to "-8000000000000", 14 characters /// ulong.MaxValue encodes to "FVVVVVVVVVVVV", 13 characters /// 12345678901234567890123456789012 public static RadixCodec Radix32 = s_radix32 ?? (s_radix32 = new RadixCodec("0123456789ABCDEFGHIJKLMNOPQRSTUV", false)); /// /// Gets a radix-36 value encoding. /// /// /// int.MaxValue encodes to "ZIK0ZJ", 6 characters -- ideal int size with reduced digit set /// int.MinValue encodes to "-ZIK0ZK", 7 characters /// uint.MaxValue encodes to "1Z141Z3", 7 characters /// long.MaxValue encodes to "1Y2P0IJ32E8E7", 13 characters /// long.MinValue encodes to "-1Y2P0IJ32E8E8", 14 characters /// ulong.MaxValue encodes to "3W5E11264SGSF", 13 characters /// 123456789012345678901234567890123456 public static RadixCodec Radix36 = s_radix36 ?? (s_radix36 = new RadixCodec("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", false)); /// /// Gets a radix-64 value encoding. /// /// /// int.MaxValue encodes to "1/////", 6 characters /// int.MinValue encodes to "-200000", 7 characters /// uint.MaxValue encodes to "3/////", 6 characters /// long.MaxValue encodes to "7//////////", 11 characters /// long.MinValue encodes to "-80000000000", 12 characters /// ulong.MaxValue encodes to "F//////////", 11 characters /// 1234567890123456789012345678901234567890123456789012345678901234 public static RadixCodec Radix64 = s_radix64 ?? (s_radix64 = new RadixCodec("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/", true)); /// /// Gets a radix-86 value encoding. /// /// /// int.MaxValue encodes to "$S2Jx", 5 characters -- base 86 reduces int size to 5 /// int.MinValue encodes to "-$S2Jy", 6 characters /// uint.MaxValue encodes to "1qu4dh", 6 characters /// long.MaxValue encodes to "1X2qL^UmlIt", 11 characters /// long.MinValue encodes to "-1X2qL^UmlIu", 12 characters /// ulong.MaxValue encodes to "2&5Sh]zLIbZ", 11 characters /// 1234567890123456789012345678901234567890123456789012345678901234567890123456 public static RadixCodec Radix86 = s_radix86 ?? (s_radix86 = new RadixCodec("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&*+;=?@[]^", true)); #endregion } }