Arbitrary Precision Signed BigDecimal
Updated: Jun-15,2021
using System; using System.Diagnostics; using System.Numerics; using System.Runtime.InteropServices; using System.Text; [Serializable] [DebuggerDisplay("{DDisplay}")] public struct BigDecimal : IComparable, IComparable<BigDecimal>, IEquatable<BigDecimal> { private const int MaxFactorials = 200; private static int _bitWidth = 2048; public static float MaxPrecision = _bitWidth / 64f * 20f; private static readonly BigInteger DoublePrecision = BigInteger.Pow(10, 308); private static readonly BigInteger DoubleMaxValue = (BigInteger)double.MaxValue; private static readonly BigInteger DoubleMinValue = (BigInteger)double.MinValue; private static readonly BigInteger DecimalPrecision = BigInteger.Pow(10, 28); private static readonly BigInteger DecimalMaxValue = (BigInteger)decimal.MaxValue; private static readonly BigInteger DecimalMinValue = (BigInteger)decimal.MinValue; private static BigDecimal[] Factorials; private int _scale; private BigInteger _unscaledValue; public BigDecimal(BigInteger value) : this(value, 0) { } public BigDecimal(xIntX value) : this((BigInteger)value, 0) { } public BigDecimal(BigIntX value) : this((BigInteger)value, 0) { } public BigDecimal(BigInteger value, int scale) { _unscaledValue = value; _scale = scale; } public BigDecimal(long value) : this(value, 0) { } public BigDecimal(double value) : this((decimal)value) { } public BigDecimal(float value) : this((decimal)value) { } public BigDecimal(byte[] value) { var number = new byte[value.Length - 4]; var flags = new byte[4]; Array.Copy(value, 0, number, 0, number.Length); Array.Copy(value, value.Length - 4, flags, 0, 4); _unscaledValue = new BigInteger(number); _scale = BitConverter.ToInt32(flags, 0); } public BigDecimal(BigRational value) { var num = (BigDecimal)value.Numerator; var den = (BigDecimal)value.Denominator; var bigDecRes = num / den; _unscaledValue = bigDecRes._unscaledValue; _scale = bigDecRes.Scale; } public BigDecimal(BigDecimal value) { _unscaledValue = value._unscaledValue; _scale = value.Scale; } public BigDecimal(decimal value) { var bytes = new byte[16]; var bits = decimal.GetBits(value); var lo = bits[0]; var mid = bits[1]; var hi = bits[2]; var 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); 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; } public BigDecimal(string value) { if (!value.ContainsOnly("0123456789+-.eE")) throw new Exception($"Input value must only contain these '0123456789+-.eE', value'{value}"); var len = value.Length; var start = 0; var point = 0; var dot = -1; var negative = false; if (value[0] == '+') { ++start; ++point; } else if (value[0] == '-') { ++start; ++point; negative = true; } while (point < len) { var c = value[point]; if (c == '.') { if (dot >= 0) throw new Exception($"There are multiple '.'s in the value {value}"); dot = point; } else if (c == 'e' || c == 'E') { break; } ++point; } string val; if (dot >= 0) { val = value.Substring(start, dot) + value.Substring(dot + 1, point - (dot + 1)); _scale = point - 1 - dot; } else { val = value.Substring(start, point); _scale = 0; } if (val.Length == 0) throw new Exception($"There are no digits in the value {value}"); if (negative) val = "-" + val; _unscaledValue = val.BigIntegerBase10(); if (point < len) try { point++; switch (value[point]) { case '+': { point++; if (point >= len) throw new Exception($"No exponent following e or E. Value {value}"); var scale = value.Substring(point); _scale -= int.Parse(scale); return; } case '-': { point++; if (point >= len) throw new Exception($"No exponent following e or E. Value {value}"); var scale = value.Substring(point); _scale += int.Parse(scale); return; } default: throw new Exception($"Malformed exponent in value {value}"); } } catch (Exception ex) { throw new Exception($"Malformed exponent in value {value}"); } } public int BitWidth { get => _bitWidth; set { _bitWidth = value; MaxPrecision = _bitWidth / 64f * 20f; } } private string DDisplay => ToString(); public static BigDecimal Zero { get; } = new(BigInteger.Zero); public static BigDecimal One { get; } = new(BigInteger.One); public static BigDecimal MinusOne { get; } = new(BigInteger.MinusOne); public bool IsEven => _unscaledValue.IsEven; public bool IsOne => _unscaledValue.IsOne; public bool IsPowerOfTwo => _unscaledValue.IsPowerOfTwo; public bool IsZero => _unscaledValue.IsZero; public int Sign => _unscaledValue.Sign; public int Scale { get => _scale; private set => _scale = value; } public BigInteger UnscaledValue => _unscaledValue; public BigDecimal WholePart => BigInteger.Divide(_unscaledValue, BigInteger.Pow(10, Scale)); public BigDecimal FractionalPart => this - WholePart; public int DecimalPlaces { get { var a = new BigDecimal(_unscaledValue); var dPlaces = 0; if (a.Sign == 0) return 1; if (a.Sign < 0) try { a = -a; } catch (Exception ex) { return 0; } var biRadix = new BigDecimal(10); while (a > 0) try { a /= biRadix; dPlaces++; } catch (Exception ex) { break; } return dPlaces; } } int IComparable.CompareTo(object obj) { if (obj == null) return 1; if (!(obj is BigDecimal)) throw new Exception("Argument must be of type BigDecimal."); return Compare(this, (BigDecimal)obj); } public int CompareTo(BigDecimal other) { return Compare(this, other); } public bool Equals(BigDecimal other) { return _unscaledValue == other._unscaledValue && _scale == other.Scale; } public override bool Equals(object obj) { if (obj == null) return false; if (!(obj is BigDecimal)) return false; return Equals((BigDecimal)obj); } public static BigDecimal Round(BigDecimal number, int decimalPlaces) { BigDecimal power = BigInteger.Pow(10, decimalPlaces); number *= power; return number >= 0 ? (BigInteger)(number + 0.5) / power : (BigInteger)(number - 0.5) / power; } public void Round(int decimalPlaces) { var number = this; BigDecimal power = BigInteger.Pow(10, decimalPlaces); number *= power; var n = number >= 0 ? (BigInteger)(number + 0.5) / power : (BigInteger)(number - 0.5) / power; _unscaledValue = n._unscaledValue; _scale = n.Scale; } public override int GetHashCode() { return UnscaledValue.GetHashCode() ^ _scale.GetHashCode(); } public override string ToString() { return ToStringInt(); } public string ToString(IFormatProvider provider) { var number = _unscaledValue.ToString("G"); if (_scale > 0 && WholePart != 0 && number.Length - _scale >= 0) return number.Insert(number.Length - _scale, "."); return _unscaledValue.ToString(provider); } private string ToStringInt() { var number = _unscaledValue.ToString("G"); if (_scale > 0 && WholePart != 0 && number.Length - _scale >= 0) return number.Insert(number.Length - _scale, "."); StringBuilder buf; var intString = _unscaledValue.ToString(); var insertionPoint = intString.Length - _scale; if (insertionPoint == 0) return (Sign < 0 ? "-0." : "0.") + intString; if (insertionPoint > 0) { buf = new StringBuilder(intString); buf.Insert(insertionPoint, '.'); if (Sign < 0) buf.Insert(0, '-'); } else { buf = new StringBuilder(3 - insertionPoint + intString.Length); buf.Append(Sign < 0 ? "-0." : "0."); for (var i = 0; i < -insertionPoint; i++) buf.Append('0'); buf.Append(intString); } if (_scale == 0) buf.Append("0"); return buf.ToString(); } public static BigDecimal Parse(string value) { return new BigDecimal(value); } public byte[] ToByteArray() { var unscaledValue = _unscaledValue.ToByteArray(); var scale = BitConverter.GetBytes(_scale); var bytes = new byte[unscaledValue.Length + scale.Length]; Array.Copy(unscaledValue, 0, bytes, 0, unscaledValue.Length); Array.Copy(scale, 0, bytes, unscaledValue.Length, scale.Length); return bytes; } public (byte[] unscaledValue, byte[] scale) ToByteArrays() { return (_unscaledValue.ToByteArray(), BitConverter.GetBytes(_scale)); } public static BigDecimal Abs(BigDecimal value) { return value._unscaledValue.Sign < 0 ? -value : value; } public static BigDecimal Negate(BigDecimal value) { return new BigDecimal(BigInteger.Negate(value._unscaledValue), value.Scale); } public static BigDecimal Add(BigDecimal x, BigDecimal y) { return x + y; } public static BigDecimal Subtract(BigDecimal x, BigDecimal y) { return x - y; } public static BigDecimal Multiply(BigDecimal x, BigDecimal y) { return x * y; } public static BigDecimal Divide(BigDecimal dividend, BigDecimal divisor) { return dividend / divisor; } public static BigDecimal Pow(BigDecimal baseValue, BigInteger exponent) { if (exponent.Sign == 0) return One; if (exponent.Sign < 0) { if (baseValue == Zero) throw new Exception("Cannot raise zero to a negative power."); baseValue = One / baseValue; exponent = BigInteger.Negate(exponent); } var result = baseValue; while (exponent > BigInteger.One) { result *= baseValue; exponent--; } return result; } public static int Compare(BigDecimal r1, BigDecimal r2) { return (r1 - r2)._unscaledValue.Sign; } public static bool operator ==(BigDecimal x, BigDecimal y) { return x.Equals(y); } public static bool operator !=(BigDecimal x, BigDecimal y) { return !x.Equals(y); } public static bool operator <(BigDecimal x, BigDecimal y) { return Compare(x, y) < 0; } public static bool operator <=(BigDecimal x, BigDecimal y) { return Compare(x, y) <= 0; } public static bool operator >(BigDecimal x, BigDecimal y) { return Compare(x, y) > 0; } public static bool operator >=(BigDecimal x, BigDecimal y) { return Compare(x, y) >= 0; } public static BigDecimal operator +(BigDecimal value) { return value; } public static BigDecimal operator -(BigDecimal value) { return new BigDecimal(-value._unscaledValue, value.Scale); } public static BigDecimal operator ++(BigDecimal value) { return value + One; } public static BigDecimal operator --(BigDecimal value) { return value - One; } public static BigDecimal operator +(BigDecimal left, BigDecimal right) { BigDecimal ret; if (left.Scale >= right.Scale) { ret = left; } else { var value = left._unscaledValue * BigInteger.Pow(10, right.Scale - left.Scale); ret = new BigDecimal(value, right.Scale); } BigDecimal ret1; if (right.Scale >= left.Scale) { ret1 = right; } else { var value1 = right._unscaledValue * BigInteger.Pow(10, left.Scale - right.Scale); ret1 = new BigDecimal(value1, left.Scale); } return new BigDecimal(ret._unscaledValue + ret1._unscaledValue, ret.Scale); } public static BigDecimal operator -(BigDecimal left, BigDecimal right) { BigDecimal ret; if (left.Scale >= right.Scale) { ret = left; } else { var value = left._unscaledValue * BigInteger.Pow(10, right.Scale - left.Scale); ret = new BigDecimal(value, right.Scale); } BigDecimal ret1; if (right.Scale >= left.Scale) { ret1 = right; } else { var value1 = right._unscaledValue * BigInteger.Pow(10, left.Scale - right.Scale); ret1 = new BigDecimal(value1, left.Scale); } return new BigDecimal(ret._unscaledValue - ret1._unscaledValue, ret.Scale); } public static BigDecimal operator *(BigDecimal left, BigDecimal right) { return new BigDecimal(left._unscaledValue * right._unscaledValue, left.Scale + right.Scale); } public static BigDecimal operator /(BigDecimal left, BigDecimal right) { var value = left._unscaledValue; var scale = left.Scale; while (scale > 0 && value % 10 == 0) { value /= 10; scale--; } var v1 = new BigDecimal(value, scale); var value1 = right._unscaledValue; var scale1 = right.Scale; while (scale1 > 0 && value1 % 10 == 0) { value1 /= 10; scale1--; } var v2 = new BigDecimal(value1, scale1); while (v1.Scale > 0 || v2.Scale > 0) { if (v1.Scale > 0) v1 = new BigDecimal(v1._unscaledValue, v1.Scale - 1); else v1 = new BigDecimal(v1._unscaledValue * 10, 0); if (v2.Scale > 0) v2 = new BigDecimal(v2._unscaledValue, v2.Scale - 1); else v2 = new BigDecimal(v2._unscaledValue * 10, 0); } var factor = 0; var v1Value = v1._unscaledValue; while (factor < (int)MaxPrecision && v1Value % v2._unscaledValue != 0) { v1Value *= 10; factor++; } return new BigDecimal(v1Value / v2._unscaledValue, factor); } public static BigDecimal operator %(BigDecimal left, BigDecimal right) { var value = left._unscaledValue; var scale = left.Scale; while (scale > 0 && value % 10 == 0) { value /= 10; scale--; } var v1 = new BigDecimal(value, scale); var value1 = right._unscaledValue; var scale1 = right.Scale; while (scale1 > 0 && value1 % 10 == 0) { value1 /= 10; scale1--; } var v2 = new BigDecimal(value1, scale1); while (v1.Scale > 0 || v2.Scale > 0) { if (v1.Scale > 0) v1 = new BigDecimal(v1._unscaledValue, v1.Scale - 1); else v1 = new BigDecimal(v1._unscaledValue * 10, 0); if (v2.Scale > 0) v2 = new BigDecimal(v2._unscaledValue, v2.Scale - 1); else v2 = new BigDecimal(v2._unscaledValue * 10, 0); } return new BigDecimal(v1._unscaledValue % v2._unscaledValue); } public static explicit operator sbyte(BigDecimal value) { return (sbyte)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator ushort(BigDecimal value) { return (ushort)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator uint(BigDecimal value) { return (uint)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator ulong(BigDecimal value) { return (ulong)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator byte(BigDecimal value) { return (byte)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator short(BigDecimal value) { return (short)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator int(BigDecimal value) { return (int)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator long(BigDecimal value) { return (long)BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator BigInteger(BigDecimal value) { return BigInteger.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator xIntX(BigDecimal value) { return xIntX.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator BigIntX(BigDecimal value) { return BigIntX.Divide(value._unscaledValue, BigInteger.Pow(10, value.Scale)); } public static explicit operator float(BigDecimal value) { return (float)(double)value; } public static explicit operator double(BigDecimal value) { var factor = BigInteger.Pow(10, value.Scale); if (SafeCastToDouble(value._unscaledValue) && SafeCastToDouble(factor)) return (double)value._unscaledValue / (double)factor; var dnv = value._unscaledValue * DoublePrecision / factor; if (dnv.IsZero) return value.Sign < 0 ? BitConverter.Int64BitsToDouble(unchecked((long)0x8000000000000000)) : 0d; double result = 0; var isDouble = false; var scale = 308; while (scale > 0) { if (!isDouble) { if (SafeCastToDouble(dnv)) { result = (double)dnv; isDouble = true; } else { dnv /= 10; } } result /= 10; scale--; } if (!isDouble) return value.Sign < 0 ? double.NegativeInfinity : double.PositiveInfinity; return result; } public static explicit operator decimal(BigDecimal value) { var factor = BigInteger.Pow(10, value.Scale); if (SafeCastToDecimal(value._unscaledValue) && SafeCastToDecimal(factor)) return (decimal)value._unscaledValue / (decimal)factor; var dnv = value._unscaledValue * DecimalPrecision / factor; if (dnv.IsZero) return decimal.Zero; for (var scale = 28; scale >= 0; scale--) if (!SafeCastToDecimal(dnv)) { dnv /= 10; } else { var dec = new DecimalUInt32(); dec.dec = (decimal)dnv; dec.flags = (dec.flags & ~0x00FF0000) | (scale << 16); return dec.dec; } throw new Exception("Value was either too large or too small for a Decimal."); } public static implicit operator BigDecimal(sbyte value) { return new BigDecimal(value); } public static implicit operator BigDecimal(ushort value) { return new BigDecimal(value); } public static implicit operator BigDecimal(uint value) { return new BigDecimal(value); } public static implicit operator BigDecimal(ulong value) { return new BigDecimal((BigInteger)value); } public static implicit operator BigDecimal(byte value) { return new BigDecimal(value); } public static implicit operator BigDecimal(short value) { return new BigDecimal(value); } public static implicit operator BigDecimal(int value) { return new BigDecimal(value); } public static implicit operator BigDecimal(long value) { return new BigDecimal(value); } public static implicit operator BigDecimal(BigInteger value) { return new BigDecimal(value); } public static implicit operator BigDecimal(xIntX value) { return new BigDecimal(value); } public static implicit operator BigDecimal(BigIntX value) { return new BigDecimal(value); } public static implicit operator BigDecimal(float value) { return new BigDecimal(value); } public static implicit operator BigDecimal(double value) { return new BigDecimal(value); } public static implicit operator BigDecimal(decimal value) { return new BigDecimal(value); } public static implicit operator BigDecimal(BigRational value) { return new BigDecimal(value); } private static bool SafeCastToDouble(BigInteger value) { return DoubleMinValue <= value && value <= DoubleMaxValue; } private static bool SafeCastToDecimal(BigInteger value) { return DecimalMinValue <= value && value <= DecimalMaxValue; } private static BigInteger GetLastDigit(BigInteger value) { return value % new BigInteger(10); } private static int GetNumberOfDigits(BigInteger value) { return BigInteger.Abs(value).ToString().Length; } private static BigDecimal Factorial(BigDecimal x) { BigDecimal r = 1; BigDecimal c = 1; while (c <= x) { r *= c; c++; } return r; } public static BigDecimal Exp(BigDecimal x) { BigDecimal r = 0; BigDecimal r1 = 0; var k = 0; while (true) { r += Pow(x, k) / Factorial(k); if (r == r1) break; r1 = r; k++; } return r; } public static BigDecimal Sine(BigDecimal ar, int n) { if (Factorials == null) { Factorials = new BigDecimal[MaxFactorials]; for (var i = 0; i < MaxFactorials; i++) Factorials[i] = new BigDecimal(0, 0); for (var i = 1; i < MaxFactorials + 1; i++) Factorials[i - 1] = Factorial(i); } var sin = ar; for (var i = 1; i <= n; i++) { var trm = Pow(ar, i * 2 + 1); trm /= Factorials[i * 2]; if ((i & 1) == 1) sin -= trm; else sin += trm; } return sin; } public static BigDecimal Atan(BigDecimal ar, int n) { var atan = ar; for (var i = 1; i <= n; i++) { var trm = Pow(ar, i * 2 + 1); trm /= i * 2; if ((i & 1) == 1) atan -= trm; else atan += trm; } return atan; } public static BigDecimal Cosine(BigDecimal ar, int n) { if (Factorials == null) { Factorials = new BigDecimal[MaxFactorials]; for (var i = 0; i < MaxFactorials; i++) Factorials[i] = new BigDecimal(0, 0); for (var i = 1; i < MaxFactorials + 1; i++) Factorials[i - 1] = Factorial(i); } BigDecimal cos = 1.0; for (var i = 1; i <= n; i++) { var trm = Pow(ar, i * 2); trm /= Factorials[i * 2 - 1]; if ((i & 1) == 1) cos -= trm; else cos += trm; } return cos; } public static BigDecimal GetE(int n) { BigDecimal e = 1.0; var c = n; while (c > 0) { BigDecimal f = 0; if (c == 1) { f = 1; } else { var i = c - 1; f = c; while (i > 0) { f *= i; i--; } } c--; e += 1.0 / f; } return e; } public static BigDecimal Tangent(BigDecimal ar, int n) { return Sine(ar, n) / Cosine(ar, n); } public static BigDecimal CoTangent(BigDecimal ar, int n) { return Cosine(ar, n) / Sine(ar, n); } public static BigDecimal Secant(BigDecimal ar, int n) { return 1.0 / Cosine(ar, n); } public static BigDecimal NthRoot(BigDecimal value, int nth) { BigDecimal lx; var a = value; var n = nth; BigDecimal s = 1.0; do { var t = s; lx = a / Pow(s, n - 1); var r = (n - 1) * s; s = (lx + r) / n; } while (lx != s); return s; } public static BigDecimal LogN(BigDecimal value) { var E = GetE(MaxFactorials); BigDecimal a; var p = value; BigDecimal n = 0.0; while (p >= E) { p /= E; n++; } n += p / E; p = value; do { a = n; var lx = p / Exp(n - 1.0); var r = (n - 1.0) * E; n = (lx + r) / E; } while (n != a); return n; } public static BigDecimal Log(BigDecimal n, int b) { return LogN(n) / LogN(b); } public static BigDecimal CoSecant(BigDecimal ar, int n) { return 1.0 / Sine(ar, n); } public static BigDecimal Ceiling(BigDecimal value, int precision) { var v1 = new BigDecimal(value); v1 = RemoveTrailingZeros(v1); var diff = GetNumberOfDigits(v1._unscaledValue) - precision; if (diff > 0) { for (var i = 0; i < diff; i++) { v1._unscaledValue = BigInteger.Divide(v1._unscaledValue, 10); v1.Scale--; } if (v1._unscaledValue.Sign < 0) v1._unscaledValue--; else v1._unscaledValue++; } return v1; } public static BigDecimal Floor(BigDecimal value, int precision) { var v1 = new BigDecimal(value); v1 = RemoveTrailingZeros(v1); var diff = GetNumberOfDigits(v1._unscaledValue) - precision; if (diff > 0) { for (var i = 0; i < diff; i++) { v1._unscaledValue = BigInteger.Divide(v1._unscaledValue, 10); v1.Scale--; } if (v1._unscaledValue.Sign > 0) v1._unscaledValue--; else v1._unscaledValue++; } return v1; } private static BigDecimal RemoveTrailingZeros(BigDecimal value) { var v1 = new BigDecimal(value); BigInteger remainder; do { var shortened = BigInteger.DivRem(v1._unscaledValue, 10, out remainder); if (remainder == BigInteger.Zero) { v1._unscaledValue = shortened; v1.Scale--; } } while (remainder == BigInteger.Zero); return v1; } public BigDecimal Min(BigDecimal value) { return CompareTo(value) <= 0 ? this : value; } public BigDecimal Max(BigDecimal value) { return CompareTo(value) >= 0 ? this : value; } public static BigDecimal Sqrt(BigDecimal value) { return BigRational.Sqrt(value); } [StructLayout(LayoutKind.Explicit)] internal struct DecimalUInt32 { [FieldOffset(0)] public decimal dec; [FieldOffset(0)] public int flags; } }