/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Globalization; using System.Numerics; namespace Avro { /// /// Represents a big decimal. /// #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CA2225 // Operator overloads have named alternates public struct AvroDecimal : IConvertible, IFormattable, IComparable, IComparable, IEquatable { /// /// Initializes a new instance of the class from a given double. /// /// The double value. public AvroDecimal(double value) : this((decimal)value) { } /// /// Initializes a new instance of the class from a given float. /// /// The float value. public AvroDecimal(float value) : this((decimal)value) { } /// /// Initializes a new instance of the class from a given decimal. /// /// The decimal value. public AvroDecimal(decimal value) { var bytes = GetBytesFromDecimal(value); var unscaledValueBytes = new byte[12]; Array.Copy(bytes, unscaledValueBytes, unscaledValueBytes.Length); var unscaledValue = new BigInteger(unscaledValueBytes); var scale = bytes[14]; if (bytes[15] == 128) unscaledValue *= BigInteger.MinusOne; UnscaledValue = unscaledValue; Scale = scale; } /// /// Initializes a new instance of the class from a given int. /// /// The int value. public AvroDecimal(int value) : this(new BigInteger(value), 0) { } /// /// Initializes a new instance of the class from a given long. /// /// The long value. public AvroDecimal(long value) : this(new BigInteger(value), 0) { } /// /// Initializes a new instance of the class from a given unsigned int. /// /// The unsigned int value. public AvroDecimal(uint value) : this(new BigInteger(value), 0) { } /// /// Initializes a new instance of the class from a given unsigned long. /// /// The unsigned long value. public AvroDecimal(ulong value) : this(new BigInteger(value), 0) { } /// /// Initializes a new instance of the class from a given /// and a scale. /// /// The double value. /// The scale. public AvroDecimal(BigInteger unscaledValue, int scale) { UnscaledValue = unscaledValue; Scale = scale; } /// /// Gets the unscaled integer value represented by the current . /// public BigInteger UnscaledValue { get; } /// /// Gets the scale of the current . /// public int Scale { get; } /// /// Gets the sign of the current . /// internal int Sign { get { return UnscaledValue.Sign; } } /// /// Converts the current to a string. /// /// A string representation of the numeric value. public override string ToString() { var number = UnscaledValue.ToString($"D{Scale + 1}", CultureInfo.CurrentCulture); if (Scale > 0) return number.Insert(number.Length - Scale, CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); return number; } public static bool operator ==(AvroDecimal left, AvroDecimal right) { return left.Equals(right); } public static bool operator !=(AvroDecimal left, AvroDecimal right) { return !left.Equals(right); } public static bool operator >(AvroDecimal left, AvroDecimal right) { return left.CompareTo(right) > 0; } public static bool operator >=(AvroDecimal left, AvroDecimal right) { return left.CompareTo(right) >= 0; } public static bool operator <(AvroDecimal left, AvroDecimal right) { return left.CompareTo(right) < 0; } public static bool operator <=(AvroDecimal left, AvroDecimal right) { return left.CompareTo(right) <= 0; } public static bool operator ==(AvroDecimal left, decimal right) { return left.Equals(right); } public static bool operator !=(AvroDecimal left, decimal right) { return !left.Equals(right); } public static bool operator >(AvroDecimal left, decimal right) { return left.CompareTo(right) > 0; } public static bool operator >=(AvroDecimal left, decimal right) { return left.CompareTo(right) >= 0; } public static bool operator <(AvroDecimal left, decimal right) { return left.CompareTo(right) < 0; } public static bool operator <=(AvroDecimal left, decimal right) { return left.CompareTo(right) <= 0; } public static bool operator ==(decimal left, AvroDecimal right) { return left.Equals(right); } public static bool operator !=(decimal left, AvroDecimal right) { return !left.Equals(right); } public static bool operator >(decimal left, AvroDecimal right) { return left.CompareTo(right) > 0; } public static bool operator >=(decimal left, AvroDecimal right) { return left.CompareTo(right) >= 0; } public static bool operator <(decimal left, AvroDecimal right) { return left.CompareTo(right) < 0; } public static bool operator <=(decimal left, AvroDecimal right) { return left.CompareTo(right) <= 0; } public static explicit operator byte(AvroDecimal value) { return ToByte(value); } /// /// Creates a byte from a given . /// /// The . /// A byte. public static byte ToByte(AvroDecimal value) { return value.ToType(); } public static explicit operator sbyte(AvroDecimal value) { return ToSByte(value); } /// /// Creates a signed byte from a given . /// /// The . /// A signed byte. public static sbyte ToSByte(AvroDecimal value) { return value.ToType(); } public static explicit operator short(AvroDecimal value) { return ToInt16(value); } /// /// Creates a short from a given . /// /// The . /// A short. public static short ToInt16(AvroDecimal value) { return value.ToType(); } public static explicit operator int(AvroDecimal value) { return ToInt32(value); } /// /// Creates an int from a given . /// /// The . /// An int. public static int ToInt32(AvroDecimal value) { return value.ToType(); } public static explicit operator long(AvroDecimal value) { return ToInt64(value); } /// /// Creates a long from a given . /// /// The . /// A long. public static long ToInt64(AvroDecimal value) { return value.ToType(); } public static explicit operator ushort(AvroDecimal value) { return ToUInt16(value); } /// /// Creates an unsigned short from a given . /// /// The . /// An unsigned short. public static ushort ToUInt16(AvroDecimal value) { return value.ToType(); } public static explicit operator uint(AvroDecimal value) { return ToUInt32(value); } /// /// Creates an unsigned int from a given . /// /// The . /// An unsigned int. public static uint ToUInt32(AvroDecimal value) { return value.ToType(); } public static explicit operator ulong(AvroDecimal value) { return ToUInt64(value); } /// /// Creates an unsigned long from a given . /// /// The . /// An unsigned long. public static ulong ToUInt64(AvroDecimal value) { return value.ToType(); } public static explicit operator float(AvroDecimal value) { return ToSingle(value); } /// /// Creates a double from a given . /// /// The . /// A double. public static float ToSingle(AvroDecimal value) { return value.ToType(); } public static explicit operator double(AvroDecimal value) { return ToDouble(value); } /// /// Creates a double from a given . /// /// The . /// A double. public static double ToDouble(AvroDecimal value) { return value.ToType(); } public static explicit operator decimal(AvroDecimal value) { return ToDecimal(value); } /// /// Creates a decimal from a given . /// /// The . /// A decimal. public static decimal ToDecimal(AvroDecimal value) { return value.ToType(); } public static explicit operator BigInteger(AvroDecimal value) { return ToBigInteger(value); } /// /// Creates a from a given . /// /// The . /// A . public static BigInteger ToBigInteger(AvroDecimal value) { var scaleDivisor = BigInteger.Pow(new BigInteger(10), value.Scale); var scaledValue = BigInteger.Divide(value.UnscaledValue, scaleDivisor); return scaledValue; } public static implicit operator AvroDecimal(byte value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(sbyte value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(short value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(int value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(long value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(ushort value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(uint value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(ulong value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(float value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(double value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(decimal value) { return new AvroDecimal(value); } public static implicit operator AvroDecimal(BigInteger value) { return new AvroDecimal(value, 0); } /// /// Converts the numeric value of the current to a given type. /// /// The type to which the value of the current should be converted. /// A value of type converted from the current . public T ToType() where T : struct { return (T)((IConvertible)this).ToType(typeof(T), null); } /// /// Converts the numeric value of the current to a given type. /// /// The type to which the value of the current should be converted. /// An System.IFormatProvider interface implementation that supplies culture-specific formatting information. /// object IConvertible.ToType(Type conversionType, IFormatProvider provider) { var scaleDivisor = BigInteger.Pow(new BigInteger(10), Scale); var remainder = BigInteger.Remainder(UnscaledValue, scaleDivisor); var scaledValue = BigInteger.Divide(UnscaledValue, scaleDivisor); if (scaledValue > new BigInteger(Decimal.MaxValue)) throw new ArgumentOutOfRangeException("value", "The value " + UnscaledValue + " cannot fit into " + conversionType.Name + "."); var leftOfDecimal = (decimal)scaledValue; var rightOfDecimal = ((decimal)remainder) / ((decimal)scaleDivisor); var value = leftOfDecimal + rightOfDecimal; return Convert.ChangeType(value, conversionType, provider); } /// /// Returns a value that indicates whether the current and a specified object /// have the same value. /// /// The object to compare. /// true if the obj argument is an object, and its value /// is equal to the value of the current instance; otherwise false. /// public override bool Equals(object obj) { return (obj is AvroDecimal) && Equals((AvroDecimal)obj); } /// /// Returns the hash code for the current . /// /// The hash code. public override int GetHashCode() { return UnscaledValue.GetHashCode() ^ Scale.GetHashCode(); } /// /// Returns the for the current . /// /// . TypeCode IConvertible.GetTypeCode() { return TypeCode.Object; } /// /// Converts the current to a boolean. /// /// The format provider. /// true or false, which reflects the value of the current . bool IConvertible.ToBoolean(IFormatProvider provider) { return Convert.ToBoolean(this, provider); } /// /// Converts the current to a byte. /// /// The format provider. /// A byte. byte IConvertible.ToByte(IFormatProvider provider) { return Convert.ToByte(this, provider); } /// /// Converts the current to a char. /// /// The format provider. /// This method always throws an . char IConvertible.ToChar(IFormatProvider provider) { throw new InvalidCastException("Cannot cast BigDecimal to Char"); } /// /// Converts the current to a . /// /// The format provider. /// This method always throws an . DateTime IConvertible.ToDateTime(IFormatProvider provider) { throw new InvalidCastException("Cannot cast BigDecimal to DateTime"); } /// /// Converts the current to a decimal. /// /// The format provider. /// A decimal. decimal IConvertible.ToDecimal(IFormatProvider provider) { return Convert.ToDecimal(this, provider); } /// /// Converts the current to a double. /// /// The format provider. /// A double. double IConvertible.ToDouble(IFormatProvider provider) { return Convert.ToDouble(this, provider); } /// /// Converts the current to a short. /// /// The format provider. /// A short. short IConvertible.ToInt16(IFormatProvider provider) { return Convert.ToInt16(this, provider); } /// /// Converts the current to an int. /// /// The format provider. /// An int. int IConvertible.ToInt32(IFormatProvider provider) { return Convert.ToInt32(this, provider); } /// /// Converts the current to a long. /// /// The format provider. /// A long. long IConvertible.ToInt64(IFormatProvider provider) { return Convert.ToInt64(this, provider); } /// /// Converts the current to a signed byte. /// /// The format provider. /// A signed byte. sbyte IConvertible.ToSByte(IFormatProvider provider) { return Convert.ToSByte(this, provider); } /// /// Converts the current to a float. /// /// The format provider. /// A float. float IConvertible.ToSingle(IFormatProvider provider) { return Convert.ToSingle(this, provider); } /// /// Converts the current to a string. /// /// The format provider. /// A string. string IConvertible.ToString(IFormatProvider provider) { return Convert.ToString(this, provider); } /// /// Converts the current to an unsigned short. /// /// The format provider. /// An unsigned short. ushort IConvertible.ToUInt16(IFormatProvider provider) { return Convert.ToUInt16(this, provider); } /// /// Converts the current to an unsigned int. /// /// The format provider. /// An unsigned int. uint IConvertible.ToUInt32(IFormatProvider provider) { return Convert.ToUInt32(this, provider); } /// /// Converts the current to an unsigned long. /// /// The format provider. /// An unsigned long. ulong IConvertible.ToUInt64(IFormatProvider provider) { return Convert.ToUInt64(this, provider); } /// /// Converts the current to a string. /// /// /// The format provider. /// A string representation of the numeric value. public string ToString(string format, IFormatProvider formatProvider) { return ToString(); } /// /// Compares the value of the current to the value of another object. /// /// The object to compare. /// A value that indicates the relative order of the objects being compared. public int CompareTo(object obj) { if (obj == null) return 1; if (!(obj is AvroDecimal)) throw new ArgumentException("Compare to object must be a BigDecimal", nameof(obj)); return CompareTo((AvroDecimal)obj); } /// /// Compares the value of the current to the value of another /// . /// /// The to compare. /// A value that indicates the relative order of the /// instances being compared. public int CompareTo(AvroDecimal other) { var unscaledValueCompare = UnscaledValue.CompareTo(other.UnscaledValue); var scaleCompare = Scale.CompareTo(other.Scale); // if both are the same value, return the value if (unscaledValueCompare == scaleCompare) return unscaledValueCompare; // if the scales are both the same return unscaled value if (scaleCompare == 0) return unscaledValueCompare; var scaledValue = BigInteger.Divide(UnscaledValue, BigInteger.Pow(new BigInteger(10), Scale)); var otherScaledValue = BigInteger.Divide(other.UnscaledValue, BigInteger.Pow(new BigInteger(10), other.Scale)); return scaledValue.CompareTo(otherScaledValue); } /// /// Returns a value that indicates whether the current has the same /// value as another . /// /// The to compare. /// true if the current has the same value as ; /// otherwise false. public bool Equals(AvroDecimal other) { return Scale == other.Scale && UnscaledValue == other.UnscaledValue; } private static byte[] GetBytesFromDecimal(decimal d) { byte[] bytes = new byte[16]; int[] bits = decimal.GetBits(d); int lo = bits[0]; int mid = bits[1]; int hi = bits[2]; int flags = bits[3]; bytes[0] = (byte)lo; bytes[1] = (byte)(lo >> 8); bytes[2] = (byte)(lo >> 0x10); bytes[3] = (byte)(lo >> 0x18); bytes[4] = (byte)mid; bytes[5] = (byte)(mid >> 8); bytes[6] = (byte)(mid >> 0x10); bytes[7] = (byte)(mid >> 0x18); bytes[8] = (byte)hi; bytes[9] = (byte)(hi >> 8); bytes[10] = (byte)(hi >> 0x10); bytes[11] = (byte)(hi >> 0x18); bytes[12] = (byte)flags; bytes[13] = (byte)(flags >> 8); bytes[14] = (byte)(flags >> 0x10); bytes[15] = (byte)(flags >> 0x18); return bytes; } } #pragma warning restore CA2225 // Operator overloads have named alternates #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member }