/*
* 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
}