Int256.cs

Int256 Bit Class

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;

[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[TypeConverter(typeof(Int256Converter))]
[DebuggerDisplay("{DDisplay}")]
public struct Int256 : IComparable<Int256>, IComparable, IEquatable<Int256>, IConvertible, IFormattable
{
    public ulong Bytes24To32;
    public ulong Bytes16To24;
    public ulong Bytes8To16;
    public ulong Bytes0To8;
    private const ulong HiNeg = 0x8000000000000000;

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string DDisplay => ToString();

    public static Int256 Zero = new Int256(0);
    public static Int256 Ten = new Int256(10);
    public static Int256 One = new Int256(1);
    public static Int256 MaxValue = GetMaxValue();
    public static Int256 MinValue = GetMinValue();

    private static Int256 GetMaxValue()
    {
        return new Int256(long.MaxValue, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue);
    }

    private static Int256 GetMinValue()
    {
        return -GetMaxValue();
    }
    public Int256(string value)
    {
        TryParse(value, out var result);
        Bytes24To32 = result.Bytes24To32;
        Bytes16To24 = result.Bytes16To24;
        Bytes8To16 = result.Bytes8To16;
        Bytes0To8 = result.Bytes0To8;
    }
    public Int256(byte value)
    {
        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = value;
    }

    public Int256(bool value)
    {
        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = (ulong) (value ? 1 : 0);
    }

    public Int256(char value)
    {
        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = value;
    }

    public Int256(decimal value)
    {
        if (value < 0)
        {
            var n = -new Int256(-value);
            Bytes24To32 = n.Bytes24To32;
            Bytes16To24 = n.Bytes16To24;
            Bytes8To16 = n.Bytes8To16;
            Bytes0To8 = n.Bytes0To8;
            return;
        }

        var bits = decimal.GetBits(value);
        Bytes24To32 = 0;
        Bytes16To24 = (uint) bits[2];
        Bytes8To16 = (uint) bits[1];
        Bytes0To8 = (uint) bits[0];
        if (value < 0)
        {
            Bytes24To32 = ~Bytes24To32;
            Bytes16To24 = ~Bytes16To24;
            Bytes8To16 = ~Bytes8To16;
            Bytes0To8 = ~Bytes0To8;
        }
    }

    public Int256(double value)
        : this((decimal) value)
    {
    }

    public Int256(float value)
        : this((decimal) value)
    {
    }

    public Int256(short value)
    {
        if (value < 0)
        {
            var n = -new Int256(-(value + 1)) - 1;
            Bytes24To32 = n.Bytes24To32;
            Bytes16To24 = n.Bytes16To24;
            Bytes8To16 = n.Bytes8To16;
            Bytes0To8 = n.Bytes0To8;
            return;
        }

        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = (ulong) value;
    }

    public Int256(int value)
    {
        if (value < 0)
        {
            var n = -new Int256(-(value + 1)) - 1;
            Bytes24To32 = n.Bytes24To32;
            Bytes16To24 = n.Bytes16To24;
            Bytes8To16 = n.Bytes8To16;
            Bytes0To8 = n.Bytes0To8;
            return;
        }

        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = (ulong) value;
    }

    public Int256(long value)
    {
        if (value < 0)
        {
            var n = -new Int256(-(value + 1)) - 1;
            Bytes24To32 = n.Bytes24To32;
            Bytes16To24 = n.Bytes16To24;
            Bytes8To16 = n.Bytes8To16;
            Bytes0To8 = n.Bytes0To8;
            return;
        }

        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = (ulong) value;
    }

    public Int256(sbyte value)
    {
        if (value < 0)
        {
            var n = -new Int256(-(value + 1)) - 1;
            Bytes24To32 = n.Bytes24To32;
            Bytes16To24 = n.Bytes16To24;
            Bytes8To16 = n.Bytes8To16;
            Bytes0To8 = n.Bytes0To8;
            return;
        }

        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = (ulong) value;
    }

    public Int256(ushort value)
    {
        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = value;
    }

    public Int256(uint value)
    {
        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = value;
    }

    public Int256(ulong value)
    {
        Bytes24To32 = 0;
        Bytes16To24 = 0;
        Bytes8To16 = 0;
        Bytes0To8 = value;
    }

    public Int256(Guid value)
        : this(value.ToByteArray())
    {
    }

    public Int256(byte[] value)
    {
        if (value == null)
            throw new Exception("Value cannot be null.");
        if (value.Length != 32)
            throw new Exception("Values length must be 32 bytes.");
        Bytes24To32 = BitConverter.ToUInt64(value, 24);
        Bytes16To24 = BitConverter.ToUInt64(value, 16);
        Bytes8To16 = BitConverter.ToUInt64(value, 8);
        Bytes0To8 = BitConverter.ToUInt64(value, 0);
    }

    public Int256(ulong msbh, ulong msbl, ulong lsbh, ulong lsbl)
    {
        Bytes24To32 = msbh;
        Bytes16To24 = msbl;
        Bytes8To16 = lsbh;
        Bytes0To8 = lsbl;
    }

    public Int256(int sign, uint[] array)
    {
        if (array == null)
            throw new Exception("Array cannot be null.");
        var msbh = new byte[8];
        var msbl = new byte[8];
        var lsbh = new byte[8];
        var lsbl = new byte[8];
        if (array.Length > 0)
        {
            Array.Copy(BitConverter.GetBytes(array[0]), 0, lsbl, 0, 4);
            if (array.Length > 1)
            {
                Array.Copy(BitConverter.GetBytes(array[1]), 0, lsbl, 4, 4);
                if (array.Length > 2)
                {
                    Array.Copy(BitConverter.GetBytes(array[2]), 0, lsbh, 0, 4);
                    if (array.Length > 3)
                    {
                        Array.Copy(BitConverter.GetBytes(array[3]), 0, lsbh, 4, 4);
                        if (array.Length > 4)
                        {
                            Array.Copy(BitConverter.GetBytes(array[4]), 0, msbl, 0, 4);
                            if (array.Length > 5)
                            {
                                Array.Copy(BitConverter.GetBytes(array[5]), 0, msbl, 4, 4);
                                if (array.Length > 6)
                                {
                                    Array.Copy(BitConverter.GetBytes(array[6]), 0, msbh, 0, 4);
                                    if (array.Length > 7)
                                        Array.Copy(BitConverter.GetBytes(array[7]), 0, msbh, 4, 4);
                                }
                            }
                        }
                    }
                }
            }
        }

        Bytes24To32 = BitConverter.ToUInt64(msbh, 0);
        Bytes16To24 = BitConverter.ToUInt64(msbl, 0);
        Bytes8To16 = BitConverter.ToUInt64(lsbh, 0);
        Bytes0To8 = BitConverter.ToUInt64(lsbl, 0);
        if (sign < 0)
            Bytes24To32 |= HiNeg;
        else
            Bytes24To32 &= ~HiNeg;
    }

    public ulong MSBH => Bytes24To32;
    public ulong MSBL => Bytes16To24;
    public ulong LSBH => Bytes8To16;
    public ulong LSBL => Bytes0To8;

    public int BitWidth
    {
        get
        {
            Int256 bitWidth = 1;
            var v = this;
            while ((v >>= 1) > 0)
                bitWidth++;
            if (bitWidth < 8)
                bitWidth = 8;
            while (bitWidth % 8 != 0)
                bitWidth++;
            return (int) bitWidth;
        }
    }

    public int Sign
    {
        get
        {
            if (Bytes24To32 == 0 && Bytes16To24 == 0 && Bytes8To16 == 0 && Bytes0To8 == 0)
                return 0;
            return (Bytes24To32 & HiNeg) == 0 ? 1 : -1;
        }
    }

    public override int GetHashCode()
    {
        return MSBH.GetHashCode() ^ MSBL.GetHashCode() ^ LSBH.GetHashCode() ^ LSBL.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }

    public bool Equals(Int256 obj)
    {
        return Bytes24To32 == obj.Bytes24To32 && Bytes16To24 == obj.Bytes16To24 && Bytes8To16 == obj.Bytes8To16 &&
               Bytes0To8 == obj.Bytes0To8;
    }

    public override string ToString()
    {
        return ToString(null, null);
    }

    public string ToString(string format)
    {
        return ToString(format, null);
    }

    public string ToString(string format, IFormatProvider formatProvider)
    {
        if (formatProvider == null)
            formatProvider = CultureInfo.CurrentCulture;
        if (!string.IsNullOrEmpty(format))
        {
            var ch = format[0];
            if (ch == 'x' || ch == 'X')
            {
                int.TryParse(format.Substring(1).Trim(), out var min);
                return ToHexString(ch == 'X', min);
            }

            if (ch != 'G' && ch != 'g' && ch != 'D' && ch != 'd')
                throw new NotSupportedException("Not supported format: " + format);
        }

        return ToString((NumberFormatInfo) formatProvider.GetFormat(typeof(NumberFormatInfo)));
    }

    private string ToHexString(bool caps, int min)
    {
        var bytes = ToByteArray().Invert();
        var sb = new StringBuilder();
        var x = caps ? "X" : "x";
        foreach (var b in bytes)
        {
            var hex = b.ToString($"{x}2");
            sb.Append(hex);
        }

        return sb.ToString();
    }

    private string ToString(NumberFormatInfo info)
    {
        if (Sign == 0)
            return "0";
        var sb = new StringBuilder();
        var current = this;
        current.Bytes24To32 &= ~HiNeg;
        while (true)
        {
            current = DivRem(current, Ten, out var r);
            if (r.Bytes0To8 > 0 || current.Sign != 0 || sb.Length == 0)
                sb.Insert(0, (char) ('0' + r.Bytes0To8));
            if (current.Sign == 0)
                break;
        }

        var s = sb.ToString();
        if (Sign < 0 && s != "0")
            return info.NegativeSign + s;
        return s;
    }

    TypeCode IConvertible.GetTypeCode()
    {
        return TypeCode.Object;
    }

    bool IConvertible.ToBoolean(IFormatProvider provider)
    {
        return (bool) this;
    }

    byte IConvertible.ToByte(IFormatProvider provider)
    {
        return (byte) this;
    }

    char IConvertible.ToChar(IFormatProvider provider)
    {
        return (char) this;
    }

    DateTime IConvertible.ToDateTime(IFormatProvider provider)
    {
        throw new InvalidCastException();
    }

    decimal IConvertible.ToDecimal(IFormatProvider provider)
    {
        return (decimal) this;
    }

    double IConvertible.ToDouble(IFormatProvider provider)
    {
        return (double) this;
    }

    short IConvertible.ToInt16(IFormatProvider provider)
    {
        return (short) this;
    }

    int IConvertible.ToInt32(IFormatProvider provider)
    {
        return (int) this;
    }

    long IConvertible.ToInt64(IFormatProvider provider)
    {
        return (int) this;
    }

    sbyte IConvertible.ToSByte(IFormatProvider provider)
    {
        return (sbyte) this;
    }

    float IConvertible.ToSingle(IFormatProvider provider)
    {
        return (float) this;
    }

    string IConvertible.ToString(IFormatProvider provider)
    {
        return ToString(null, provider);
    }

    public bool TryConvert(Type conversionType, IFormatProvider provider, out object value)
    {
        if (conversionType == typeof(bool))
        {
            value = (bool) this;
            return true;
        }

        if (conversionType == typeof(byte))
        {
            value = (byte) this;
            return true;
        }

        if (conversionType == typeof(char))
        {
            value = (char) this;
            return true;
        }

        if (conversionType == typeof(decimal))
        {
            value = (decimal) this;
            return true;
        }

        if (conversionType == typeof(double))
        {
            value = (double) this;
            return true;
        }

        if (conversionType == typeof(short))
        {
            value = (short) this;
            return true;
        }

        if (conversionType == typeof(int))
        {
            value = (int) this;
            return true;
        }

        if (conversionType == typeof(long))
        {
            value = (long) this;
            return true;
        }

        if (conversionType == typeof(sbyte))
        {
            value = (sbyte) this;
            return true;
        }

        if (conversionType == typeof(float))
        {
            value = (float) this;
            return true;
        }

        if (conversionType == typeof(string))
        {
            value = ToString(null, provider);
            return true;
        }

        if (conversionType == typeof(ushort))
        {
            value = (ushort) this;
            return true;
        }

        if (conversionType == typeof(uint))
        {
            value = (uint) this;
            return true;
        }

        if (conversionType == typeof(ulong))
        {
            value = (ulong) this;
            return true;
        }

        if (conversionType == typeof(byte[]))
        {
            value = ToByteArray();
            return true;
        }

        if (conversionType == typeof(Guid))
        {
            value = new Guid(ToByteArray());
            return true;
        }

        value = null;
        return false;
    }

    public static Int256 Parse(string value)
    {
        return Parse(value, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
    }

    public static Int256 Parse(string value, NumberStyles style)
    {
        return Parse(value, style, NumberFormatInfo.CurrentInfo);
    }

    public static Int256 Parse(string value, IFormatProvider provider)
    {
        return Parse(value, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
    }

    public static Int256 Parse(string value, NumberStyles style, IFormatProvider provider)
    {
        if (!TryParse(value, style, provider, out var result))
            throw new Exception($"TryParse value {value} failure.");
        return result;
    }

    public static bool TryParse(string value, out Int256 result)
    {
        return TryParse(value, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
    }

    public static bool TryParse(string value, NumberStyles style, IFormatProvider provider, out Int256 result)
    {
        result = Zero;
        if (string.IsNullOrEmpty(value))
            return false;
        if (value.StartsWith("x", StringComparison.OrdinalIgnoreCase))
        {
            style |= NumberStyles.AllowHexSpecifier;
            value = value.Substring(1);
        }
        else
        {
            if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
            {
                style |= NumberStyles.AllowHexSpecifier;
                value = value.Substring(2);
            }
        }

        if ((style & NumberStyles.AllowHexSpecifier) == NumberStyles.AllowHexSpecifier)
            return TryParseHex(value, out result);
        return TryParseNum(value, out result);
    }

    private static bool TryParseHex(string value, out Int256 result)
    {
        if (value.Length > 64)
            throw new OverflowException();
        result = Zero;
        var pos = 0;
        for (var i = value.Length - 1; i >= 0; i--)
        {
            var ch = value[i];
            ulong bch;
            if (ch >= '0' && ch <= '9')
                bch = (ulong) (ch - '0');
            else if (ch >= 'A' && ch <= 'F')
                bch = (ulong) (ch - 'A' + 10);
            else if (ch >= 'a' && ch <= 'f')
                bch = (ulong) (ch - 'a' + 10);
            else
                return false;
            if (pos < 64)
                result.Bytes0To8 |= bch << pos;
            else if (pos < 128)
                result.Bytes8To16 |= bch << pos;
            else if (pos < 192)
                result.Bytes16To24 |= bch << pos;
            else if (pos < 256)
                result.Bytes24To32 |= bch << pos;
            pos += 4;
        }

        return true;
    }

    private static bool TryParseNum(string value, out Int256 result)
    {
        result = Zero;
        foreach (var ch in value)
        {
            byte b;
            if (ch >= '0' && ch <= '9')
                b = (byte) (ch - '0');
            else
                return false;
            result = Ten * result;
            result += b;
        }

        return true;
    }

    public object ToType(Type conversionType, IFormatProvider provider)
    {
        object value;
        if (TryConvert(conversionType, provider, out value))
            return value;
        throw new InvalidCastException();
    }

    ushort IConvertible.ToUInt16(IFormatProvider provider)
    {
        if (Bytes8To16 != 0)
            throw new OverflowException();
        return Convert.ToUInt16(Bytes0To8);
    }

    uint IConvertible.ToUInt32(IFormatProvider provider)
    {
        if (Bytes8To16 != 0)
            throw new OverflowException();
        return Convert.ToUInt32(Bytes0To8);
    }

    ulong IConvertible.ToUInt64(IFormatProvider provider)
    {
        if (Bytes8To16 != 0)
            throw new OverflowException();
        return Bytes0To8;
    }

    int IComparable.CompareTo(object obj)
    {
        return Compare(this, obj);
    }

    public static int Compare(Int256 left, object right)
    {
        if (right is Int256)
            return Compare(left, (Int256) right);
        if (right is bool)
            return Compare(left, new Int256((bool) right));
        if (right is byte)
            return Compare(left, new Int256((byte) right));
        if (right is char)
            return Compare(left, new Int256((char) right));
        if (right is decimal)
            return Compare(left, new Int256((decimal) right));
        if (right is double)
            return Compare(left, new Int256((double) right));
        if (right is short)
            return Compare(left, new Int256((short) right));
        if (right is int)
            return Compare(left, new Int256((int) right));
        if (right is long)
            return Compare(left, new Int256((long) right));
        if (right is sbyte)
            return Compare(left, new Int256((sbyte) right));
        if (right is float)
            return Compare(left, new Int256((float) right));
        if (right is ushort)
            return Compare(left, new Int256((ushort) right));
        if (right is uint)
            return Compare(left, new Int256((uint) right));
        if (right is ulong)
            return Compare(left, new Int256((ulong) right));
        var bytes = right as byte[];
        if (bytes != null && bytes.Length != 32)
            return Compare(left, new Int256(bytes));
        if (right is Guid)
            return Compare(left, new Int256((Guid) right));
        throw new ArgumentException();
    }

    public static int Compare(Int256 left, Int256 right)
    {
        var leftSign = left.Sign;
        var rightSign = right.Sign;
        if (leftSign == 0 && rightSign == 0)
            return 0;
        if (leftSign >= 0 && rightSign < 0)
            return 1;
        if (leftSign < 0 && rightSign >= 0)
            return -1;
        if (left.Bytes24To32 != right.Bytes24To32)
            return left.Bytes24To32.CompareTo(right.Bytes24To32);
        if (left.Bytes16To24 != right.Bytes16To24)
            return left.Bytes16To24.CompareTo(right.Bytes16To24);
        if (left.Bytes8To16 != right.Bytes8To16)
            return left.Bytes8To16.CompareTo(right.Bytes8To16);
        return left.Bytes0To8.CompareTo(right.Bytes0To8);
    }

    public int CompareTo(Int256 value)
    {
        return Compare(this, value);
    }

    public static implicit operator Int256(bool value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(byte value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(char value)
    {
        return new Int256(value);
    }

    public static explicit operator Int256(decimal value)
    {
        return new Int256(value);
    }

    public static explicit operator Int256(double value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(short value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(int value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(long value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(sbyte value)
    {
        return new Int256(value);
    }

    public static explicit operator Int256(float value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(ushort value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(uint value)
    {
        return new Int256(value);
    }

    public static implicit operator Int256(ulong value)
    {
        return new Int256(value);
    }

    public static explicit operator bool(Int256 value)
    {
        return value.Sign != 0;
    }

    public static explicit operator byte(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        if (value.Sign < 0 || value.Bytes0To8 > 0xFF)
            throw new OverflowException();
        return (byte) value.Bytes0To8;
    }

    public static explicit operator char(Int256 value)
    {
        if (value.Sign == 0)
            return (char) 0;
        if (value.Sign < 0 || value.Bytes0To8 > 0xFFFF)
            throw new OverflowException();
        return (char) (ushort) value.Bytes0To8;
    }

    public static explicit operator decimal(Int256 value)
    {
        return value.Sign == 0
            ? 0
            : new decimal((int) (value.Bytes0To8 & 0xFFFFFFFF), (int) (value.Bytes8To16 & 0xFFFFFFFF),
                (int) (value.Bytes16To24 & 0xFFFFFFFF), value.Sign < 0, 0);
    }

    public static explicit operator double(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        var nfi = CultureInfo.InvariantCulture.NumberFormat;
        if (!double.TryParse(value.ToString(nfi), NumberStyles.Number, nfi, out var d))
            throw new OverflowException();
        return d;
    }

    public static explicit operator float(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        var nfi = CultureInfo.InvariantCulture.NumberFormat;
        if (!float.TryParse(value.ToString(nfi), NumberStyles.Number, nfi, out var f))
            throw new OverflowException();
        return f;
    }

    public static explicit operator short(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        if (value.Bytes0To8 > 0x8000)
            throw new OverflowException();
        if (value.Bytes0To8 == 0x8000 && value.Sign > 0)
            throw new OverflowException();
        return (short) ((int) value.Bytes0To8 * value.Sign);
    }

    public static explicit operator int(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        if (value.Bytes0To8 > 0x80000000)
            throw new OverflowException();
        if (value.Bytes0To8 == 0x80000000 && value.Sign > 0)
            throw new OverflowException();
        return (int) value.Bytes0To8 * value.Sign;
    }

    public static explicit operator long(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        if (value.Bytes0To8 > long.MaxValue)
            throw new OverflowException();
        return (long) value.Bytes0To8 * value.Sign;
    }

    public static explicit operator uint(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        if (value.Sign < 0 || value.Bytes0To8 > uint.MaxValue)
            throw new OverflowException();
        return (uint) value.Bytes0To8;
    }

    public static explicit operator ushort(Int256 value)
    {
        if (value.Sign == 0)
            return 0;
        if (value.Sign < 0 || value.Bytes0To8 > ushort.MaxValue)
            throw new OverflowException();
        return (ushort) value.Bytes0To8;
    }

    public static explicit operator ulong(Int256 value)
    {
        if (value.Sign < 0 || value.Bytes8To16 != 0)
            throw new OverflowException();
        return value.Bytes0To8;
    }

    public static bool operator >(Int256 left, Int256 right)
    {
        return Compare(left, right) > 0;
    }

    public static bool operator <(Int256 left, Int256 right)
    {
        return Compare(left, right) < 0;
    }

    public static bool operator >=(Int256 left, Int256 right)
    {
        return Compare(left, right) >= 0;
    }

    public static bool operator <=(Int256 left, Int256 right)
    {
        return Compare(left, right) <= 0;
    }

    public static bool operator !=(Int256 left, Int256 right)
    {
        return Compare(left, right) != 0;
    }

    public static bool operator ==(Int256 left, Int256 right)
    {
        return Compare(left, right) == 0;
    }

    public static Int256 operator +(Int256 value)
    {
        return value;
    }
    public static Int256 operator ~(Int256 value)
    {
        return -(value + One);
    }
    public static Int256 operator -(Int256 value)
    {
        return Negate(value);
    }

    public static Int256 operator ++(Int256 value)
    {
        return value + 1;
    }

    public static Int256 operator --(Int256 value)
    {
        return value - 1;
    }

    public static Int256 Negate(Int256 value)
    {
        return new Int256(~value.Bytes24To32, ~value.Bytes16To24, ~value.Bytes8To16, ~value.Bytes0To8) + 1;
    }

    public Int256 ToAbs()
    {
        return Abs(this);
    }

    public static Int256 Abs(Int256 value)
    {
        if (value.Sign < 0)
            return -value;
        return value;
    }

    public static Int256 operator +(Int256 left, Int256 right)
    {
        left.Bytes24To32 += right.Bytes24To32;
        left.Bytes16To24 += right.Bytes16To24;
        if (left.Bytes16To24 < right.Bytes16To24)
            left.Bytes24To32++;
        left.Bytes8To16 += right.Bytes8To16;
        if (left.Bytes8To16 < right.Bytes8To16)
        {
            left.Bytes16To24++;
            if (left.Bytes16To24 < left.Bytes16To24 - 1)
                left.Bytes24To32++;
        }

        left.Bytes0To8 += right.Bytes0To8;
        if (left.Bytes0To8 < right.Bytes0To8)
        {
            left.Bytes8To16++;
            if (left.Bytes8To16 < left.Bytes8To16 - 1)
            {
                left.Bytes16To24++;
                if (left.Bytes16To24 < left.Bytes16To24 - 1)
                    left.Bytes24To32++;
            }
        }

        return left;
    }

    public static Int256 operator -(Int256 left, Int256 right)
    {
        return left + -right;
    }

    public static Int256 Add(Int256 left, Int256 right)
    {
        return left + right;
    }

    public static Int256 Subtract(Int256 left, Int256 right)
    {
        return left - right;
    }

    public static Int256 Divide(Int256 dividend, Int256 divisor)
    {
        return DivRem(dividend, divisor, out var integer);
    }

    public static Int256 DivRem(Int256 dividend, Int256 divisor, out Int256 remainder)
    {
        if (divisor == 0)
            throw new DivideByZeroException();
        DivRem(dividend.ToUIn32Array(), divisor.ToUIn32Array(), out var quotient, out var rem);
        remainder = new Int256(1, rem);
        return new Int256(dividend.Sign * divisor.Sign, quotient);
    }

    private static void DivRem(uint[] dividend, uint[] divisor, out uint[] quotient, out uint[] remainder)
    {
        const ulong hiBit = 0x100000000;
        var divisorLen = GetLength(divisor);
        var dividendLen = GetLength(dividend);
        if (divisorLen <= 1)
        {
            ulong rem = 0;
            var div = divisor[0];
            quotient = new uint[dividendLen];
            remainder = new uint[1];
            for (var i = dividendLen - 1; i >= 0; i--)
            {
                rem *= hiBit;
                rem += dividend[i];
                var q = rem / div;
                rem -= q * div;
                quotient[i] = (uint) q;
            }

            remainder[0] = (uint) rem;
            return;
        }

        if (dividendLen >= divisorLen)
        {
            var shift = GetNormalizeShift(divisor[divisorLen - 1]);
            var normDividend = new uint[dividendLen + 1];
            var normDivisor = new uint[divisorLen];
            Normalize(dividend, dividendLen, normDividend, shift);
            Normalize(divisor, divisorLen, normDivisor, shift);
            quotient = new uint[dividendLen - divisorLen + 1];
            for (var j = dividendLen - divisorLen; j >= 0; j--)
            {
                var dx = hiBit * normDividend[j + divisorLen] + normDividend[j + divisorLen - 1];
                var qj = dx / normDivisor[divisorLen - 1];
                dx -= qj * normDivisor[divisorLen - 1];
                do
                {
                    if (qj < hiBit && qj * normDivisor[divisorLen - 2] <= dx * hiBit + normDividend[j + divisorLen - 2])
                        break;
                    qj -= 1L;
                    dx += normDivisor[divisorLen - 1];
                } while (dx < hiBit);

                long di = 0;
                long dj;
                var index = 0;
                while (index < divisorLen)
                {
                    var dqj = normDivisor[index] * qj;
                    dj = normDividend[index + j] - (uint) dqj - di;
                    normDividend[index + j] = (uint) dj;
                    dqj = dqj >> 32;
                    dj = dj >> 32;
                    di = (long) dqj - dj;
                    index++;
                }

                dj = normDividend[j + divisorLen] - di;
                normDividend[j + divisorLen] = (uint) dj;
                quotient[j] = (uint) qj;
                if (dj < 0)
                {
                    quotient[j]--;
                    ulong sum = 0;
                    for (index = 0; index < divisorLen; index++)
                    {
                        sum = normDivisor[index] + normDividend[j + index] + sum;
                        normDividend[j + index] = (uint) sum;
                        sum = sum >> 32;
                    }

                    sum += normDividend[j + divisorLen];
                    normDividend[j + divisorLen] = (uint) sum;
                }
            }

            remainder = Unnormalize(normDividend, shift);
            return;
        }

        quotient = new uint[0];
        remainder = dividend;
    }

    private static int GetLength(uint[] uints)
    {
        var index = uints.Length - 1;
        while (index >= 0 && uints[index] == 0)
            index--;
        return index + 1;
    }

    private static int GetNormalizeShift(uint ui)
    {
        var shift = 0;
        if ((ui & 0xffff0000) == 0)
        {
            ui = ui << 16;
            shift += 16;
        }

        if ((ui & 0xff000000) == 0)
        {
            ui = ui << 8;
            shift += 8;
        }

        if ((ui & 0xf0000000) == 0)
        {
            ui = ui << 4;
            shift += 4;
        }

        if ((ui & 0xc0000000) == 0)
        {
            ui = ui << 2;
            shift += 2;
        }

        if ((ui & 0x80000000) == 0)
            shift++;
        return shift;
    }

    private static uint[] Unnormalize(uint[] normalized, int shift)
    {
        var len = GetLength(normalized);
        var unnormalized = new uint[len];
        if (shift > 0)
        {
            var rshift = 32 - shift;
            uint r = 0;
            for (var i = len - 1; i >= 0; i--)
            {
                unnormalized[i] = (normalized[i] >> shift) | r;
                r = normalized[i] << rshift;
            }
        }
        else
        {
            for (var j = 0; j < len; j++)
                unnormalized[j] = normalized[j];
        }

        return unnormalized;
    }

    private static void Normalize(uint[] unormalized, int len, uint[] normalized, int shift)
    {
        int i;
        uint n = 0;
        if (shift > 0)
        {
            var rShift = 32 - shift;
            for (i = 0; i < len; i++)
            {
                normalized[i] = (unormalized[i] << shift) | n;
                n = unormalized[i] >> rShift;
            }
        }
        else
        {
            i = 0;
            while (i < len)
            {
                normalized[i] = unormalized[i];
                i++;
            }
        }

        while (i < normalized.Length)
            normalized[i++] = 0;
        if (n != 0)
            normalized[len] = n;
    }

    public static Int256 Remainder(Int256 dividend, Int256 divisor)
    {
        DivRem(dividend, divisor, out var remainder);
        return remainder;
    }

    public static Int256 Max(Int256 left, Int256 right)
    {
        return left.CompareTo(right) < 0 ? right : left;
    }

    public static Int256 Min(Int256 left, Int256 right)
    {
        return left.CompareTo(right) <= 0 ? left : right;
    }

    public static int GetBitWidth(Int256 n)
    {
        Int256 bitWidth = 1;
        var v = n;
        while ((v >>= 1) > 0)
            bitWidth++;
        if (bitWidth < 8)
            bitWidth = 8;
        while (bitWidth % 8 != 0)
            bitWidth++;
        return (int) bitWidth;
    }

    public static Int256 operator %(Int256 dividend, Int256 divisor)
    {
        return Remainder(dividend, divisor);
    }

    public static Int256 operator /(Int256 dividend, Int256 divisor)
    {
        return Divide(dividend, divisor);
    }

    public ulong[] ToUIn64Array()
    {
        return new[] {Bytes0To8, Bytes8To16, Bytes16To24, Bytes24To32};
    }

    public uint[] ToUIn32Array()
    {
        var uia = new uint[8];
        var ula = ToUIn64Array();
        Buffer.BlockCopy(ula, 0, uia, 0, 32);
        return uia;
    }

    public byte[] ToByteArray()
    {
        var ba = new byte[32];
        var ula = ToUIn64Array();
        Buffer.BlockCopy(ula, 0, ba, 0, 32);
        return ba;
    }

    public static Int256 Multiply(Int256 left, Int256 right)
    {
        var xInts = left.ToUIn32Array();
        var yInts = right.ToUIn32Array();
        var mulInts = new uint[16];
        for (var i = 0; i < xInts.Length; i++)
        {
            var index = i;
            ulong remainder = 0;
            foreach (var yi in yInts)
            {
                remainder = remainder + (ulong) xInts[i] * yi + mulInts[index];
                mulInts[index++] = (uint) remainder;
                remainder = remainder >> 32;
            }

            while (remainder != 0)
            {
                remainder += mulInts[index];
                mulInts[index++] = (uint) remainder;
                remainder = remainder >> 32;
            }
        }

        return new Int256(left.Sign * right.Sign, mulInts);
    }

    public static Int256 operator *(Int256 left, Int256 right)
    {
        return Multiply(left, right);
    }

    public static Int256 operator >>(Int256 value, int shift)
    {
        if (shift == 0)
            return value;
        if (shift == int.MinValue)
            return value << int.MaxValue << 1;
        if (shift < 0)
            return value << -shift;
        var digitShift = shift / 32;
        var smallShift = shift - digitShift * 32;
        var xd = value.ToUIn32Array();
        var xl = xd.Length;
        if (value.Sign < 0)
        {
            if (shift >= 32 * xl)
                return new Int256(-1);
            var zd = new uint[xl];
            Array.Copy(xd, zd, xl);
            xd = zd;
            TwosComplement(xd);
        }

        var length = xl - digitShift;
        if (length < 0)
            length = 0;
        var d = new uint[length];
        if (smallShift == 0)
        {
            for (var index = xl - 1; index >= digitShift; --index)
                d[index - digitShift] = xd[index];
        }
        else
        {
            var carryShift = 32 - smallShift;
            uint carry = 0;
            for (var index = xl - 1; index >= digitShift; --index)
            {
                var rot = xd[index];
                d[index - digitShift] = !(value.Sign < 0) || index != xl - 1
                    ? (rot >> smallShift) | carry
                    : (rot >> smallShift) | (uint) (-1 << carryShift);
                carry = rot << carryShift;
            }
        }

        if (value.Sign < 0)
            TwosComplement(d);
        return new Int256(value.Sign, d);
    }

    private static void TwosComplement(uint[] d)
    {
        uint v = 0;
        var i = 0;
        for (; i < d.Length; i++)
        {
            v = ~d[i] + 1;
            d[i] = v;
            if (v != 0)
            {
                i++;
                break;
            }
        }

        if (v != 0)
        {
            for (; i < d.Length; i++)
                d[i] = ~d[i];
        }
        else
        {
            var len = d.Length + 1;
            var r = new uint[len];
            var n = Math.Min(d.Length, len);
            for (var j = 0; j < n; j++)
                r[j] = d[j];
            d = r;
            d[d.Length - 1] = 1;
        }
    }

    public static Int256 operator <<(Int256 value, int shift)
    {
        if (shift == 0)
            return value;
        if (shift == int.MinValue)
            return value >> int.MaxValue >> 1;
        if (shift < 0)
            return value >> -shift;
        var digitShift = shift / 32;
        var smallShift = shift - digitShift * 32;
        var xd = value.ToUIn32Array();
        var xl = xd.Length;
        var zd = new uint[xl + digitShift + 1];
        if (smallShift == 0)
        {
            for (var index = 0; index < xl; ++index)
                zd[index + digitShift] = xd[index];
        }
        else
        {
            var carryShift = 32 - smallShift;
            uint carry = 0;
            int index;
            for (index = 0; index < xl; ++index)
            {
                var rot = xd[index];
                zd[index + digitShift] = (rot << smallShift) | carry;
                carry = rot >> carryShift;
            }

            zd[index + digitShift] = carry;
        }

        return new Int256(value.Sign, zd);
    }

    public static Int256 operator |(Int256 left, Int256 right)
    {
        if (left == 0)
            return right;
        if (right == 0)
            return left;
        var x       = left.ToUIn32Array();
        var y       = right.ToUIn32Array();
        var z       = new uint[Math.Max(x.Length, y.Length)];
        var xExtend = left.Sign  < 0 ? uint.MaxValue : 0;
        var yExtend = right.Sign < 0 ? uint.MaxValue : 0;
        for (var i = 0; i < z.Length; i++)
        {
            var xu = i < x.Length ? x[i] : xExtend;
            var yu = i < y.Length ? y[i] : yExtend;
            z[i] = xu | yu;
        }

        return new Int256(left.Sign * right.Sign, z);
    }

    public static Int256 operator ^(Int256 left, Int256 right)
    {
        var x       = left.ToUIn32Array();
        var y       = right.ToUIn32Array();
        var z       = new uint[Math.Max(x.Length, y.Length)];
        var xExtend = left.Sign  < 0 ? uint.MaxValue : 0;
        var yExtend = right.Sign < 0 ? uint.MaxValue : 0;
        for (var i = 0; i < z.Length; i++)
        {
            var xu = i < x.Length ? x[i] : xExtend;
            var yu = i < y.Length ? y[i] : yExtend;
            z[i] = xu ^ yu;
        }

        return new Int256(left.Sign * right.Sign, z);
    }

    public static Int256 operator &(Int256 left, Int256 right)
    {
        if (left == 0 || right == 0)
            return Zero;
        var x       = left.ToUIn32Array();
        var y       = right.ToUIn32Array();
        var z       = new uint[Math.Max(x.Length, y.Length)];
        var xExtend = left.Sign  < 0 ? uint.MaxValue : 0;
        var yExtend = right.Sign < 0 ? uint.MaxValue : 0;
        for (var i = 0; i < z.Length; i++)
        {
            var xu = i < x.Length ? x[i] : xExtend;
            var yu = i < y.Length ? y[i] : yExtend;
            z[i] = xu & yu;
        }

        return new Int256(left.Sign * right.Sign, z);
    }

    public class Int256Converter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value != null)
                if (TryParse($"{value}", out var i))
                    return i;
            return new Int256();
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value,
            Type destinationType)
        {
            return destinationType == typeof(string)
                ? $"{value}"
                : base.ConvertTo(context, culture, value, destinationType);
        }
    }
}