-
-
Save KTSnowy/ea18a669de191d52dadcde1904149757 to your computer and use it in GitHub Desktop.
BigDecimal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Numerics; | |
namespace Common | |
{ | |
/// <summary> | |
/// Arbitrary precision decimal. | |
/// All operations are exact, except for division. Division never determines more digits than the given precision. | |
/// Source: https://gist.github.com/JcBernack/0b4eef59ca97ee931a2f45542b9ff06d | |
/// Based on https://stackoverflow.com/a/4524254 | |
/// Author: Jan Christoph Bernack (contact: jc.bernack at gmail.com) | |
/// License: public domain | |
/// </summary> | |
public struct BigDecimal | |
: IComparable | |
, IComparable<BigDecimal> | |
{ | |
/// <summary> | |
/// Specifies whether the significant digits should be truncated to the given precision after each operation. | |
/// </summary> | |
public static bool AlwaysTruncate = false; | |
/// <summary> | |
/// Sets the maximum precision of division operations. | |
/// If AlwaysTruncate is set to true all operations are affected. | |
/// </summary> | |
public static int Precision = 50; | |
public BigInteger Mantissa { get; set; } | |
public int Exponent { get; set; } | |
public BigDecimal(BigInteger mantissa, int exponent) | |
: this() | |
{ | |
Mantissa = mantissa; | |
Exponent = exponent; | |
Normalize(); | |
if (AlwaysTruncate) | |
{ | |
Truncate(); | |
} | |
} | |
/// <summary> | |
/// Removes trailing zeros on the mantissa | |
/// </summary> | |
public void Normalize() | |
{ | |
if (Mantissa.IsZero) | |
{ | |
Exponent = 0; | |
} | |
else | |
{ | |
BigInteger remainder = 0; | |
while (remainder == 0) | |
{ | |
var shortened = BigInteger.DivRem(Mantissa, 10, out remainder); | |
if (remainder == 0) | |
{ | |
Mantissa = shortened; | |
Exponent++; | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// Truncate the number to the given precision by removing the least significant digits. | |
/// </summary> | |
/// <returns>The truncated number</returns> | |
public BigDecimal Truncate(int precision) | |
{ | |
// copy this instance (remember it's a struct) | |
var shortened = this; | |
// save some time because the number of digits is not needed to remove trailing zeros | |
shortened.Normalize(); | |
// remove the least significant digits, as long as the number of digits is higher than the given Precision | |
while (NumberOfDigits(shortened.Mantissa) > precision) | |
{ | |
shortened.Mantissa /= 10; | |
shortened.Exponent++; | |
} | |
// normalize again to make sure there are no trailing zeros left | |
shortened.Normalize(); | |
return shortened; | |
} | |
public BigDecimal Truncate() | |
{ | |
return Truncate(Precision); | |
} | |
public BigDecimal Floor() | |
{ | |
return Truncate(BigDecimal.NumberOfDigits(Mantissa) + Exponent); | |
} | |
public static int NumberOfDigits(BigInteger value) | |
{ | |
// do not count the sign | |
//return (value * value.Sign).ToString().Length; | |
// faster version | |
return (int)Math.Ceiling(BigInteger.Log10(value * value.Sign)); | |
} | |
#region Conversions | |
public static implicit operator BigDecimal(int value) | |
{ | |
return new BigDecimal(value, 0); | |
} | |
public static implicit operator BigDecimal(double value) | |
{ | |
var mantissa = (BigInteger) value; | |
var exponent = 0; | |
double scaleFactor = 1; | |
while (Math.Abs(value * scaleFactor - (double)mantissa) > 0) | |
{ | |
exponent -= 1; | |
scaleFactor *= 10; | |
mantissa = (BigInteger)(value * scaleFactor); | |
} | |
return new BigDecimal(mantissa, exponent); | |
} | |
public static implicit operator BigDecimal(decimal value) | |
{ | |
var mantissa = (BigInteger)value; | |
var exponent = 0; | |
decimal scaleFactor = 1; | |
while ((decimal)mantissa != value * scaleFactor) | |
{ | |
exponent -= 1; | |
scaleFactor *= 10; | |
mantissa = (BigInteger)(value * scaleFactor); | |
} | |
return new BigDecimal(mantissa, exponent); | |
} | |
public static explicit operator double(BigDecimal value) | |
{ | |
return (double)value.Mantissa * Math.Pow(10, value.Exponent); | |
} | |
public static explicit operator float(BigDecimal value) | |
{ | |
return Convert.ToSingle((double)value); | |
} | |
public static explicit operator decimal(BigDecimal value) | |
{ | |
return (decimal)value.Mantissa * (decimal)Math.Pow(10, value.Exponent); | |
} | |
public static explicit operator int(BigDecimal value) | |
{ | |
return (int)(value.Mantissa * BigInteger.Pow(10, value.Exponent)); | |
} | |
public static explicit operator uint(BigDecimal value) | |
{ | |
return (uint)(value.Mantissa * BigInteger.Pow(10, value.Exponent)); | |
} | |
#endregion | |
#region Operators | |
public static BigDecimal operator +(BigDecimal value) | |
{ | |
return value; | |
} | |
public static BigDecimal operator -(BigDecimal value) | |
{ | |
value.Mantissa *= -1; | |
return value; | |
} | |
public static BigDecimal operator ++(BigDecimal value) | |
{ | |
return value + 1; | |
} | |
public static BigDecimal operator --(BigDecimal value) | |
{ | |
return value - 1; | |
} | |
public static BigDecimal operator +(BigDecimal left, BigDecimal right) | |
{ | |
return Add(left, right); | |
} | |
public static BigDecimal operator -(BigDecimal left, BigDecimal right) | |
{ | |
return Add(left, -right); | |
} | |
private static BigDecimal Add(BigDecimal left, BigDecimal right) | |
{ | |
return left.Exponent > right.Exponent | |
? new BigDecimal(AlignExponent(left, right) + right.Mantissa, right.Exponent) | |
: new BigDecimal(AlignExponent(right, left) + left.Mantissa, left.Exponent); | |
} | |
public static BigDecimal operator *(BigDecimal left, BigDecimal right) | |
{ | |
return new BigDecimal(left.Mantissa * right.Mantissa, left.Exponent + right.Exponent); | |
} | |
public static BigDecimal operator /(BigDecimal dividend, BigDecimal divisor) | |
{ | |
var exponentChange = Precision - (NumberOfDigits(dividend.Mantissa) - NumberOfDigits(divisor.Mantissa)); | |
if (exponentChange < 0) | |
{ | |
exponentChange = 0; | |
} | |
dividend.Mantissa *= BigInteger.Pow(10, exponentChange); | |
return new BigDecimal(dividend.Mantissa / divisor.Mantissa, dividend.Exponent - divisor.Exponent - exponentChange); | |
} | |
public static BigDecimal operator %(BigDecimal left, BigDecimal right) | |
{ | |
return left - right * (left / right).Floor(); | |
} | |
public static bool operator ==(BigDecimal left, BigDecimal right) | |
{ | |
return left.Exponent == right.Exponent && left.Mantissa == right.Mantissa; | |
} | |
public static bool operator !=(BigDecimal left, BigDecimal right) | |
{ | |
return left.Exponent != right.Exponent || left.Mantissa != right.Mantissa; | |
} | |
public static bool operator <(BigDecimal left, BigDecimal right) | |
{ | |
return left.Exponent > right.Exponent ? AlignExponent(left, right) < right.Mantissa : left.Mantissa < AlignExponent(right, left); | |
} | |
public static bool operator >(BigDecimal left, BigDecimal right) | |
{ | |
return left.Exponent > right.Exponent ? AlignExponent(left, right) > right.Mantissa : left.Mantissa > AlignExponent(right, left); | |
} | |
public static bool operator <=(BigDecimal left, BigDecimal right) | |
{ | |
return left.Exponent > right.Exponent ? AlignExponent(left, right) <= right.Mantissa : left.Mantissa <= AlignExponent(right, left); | |
} | |
public static bool operator >=(BigDecimal left, BigDecimal right) | |
{ | |
return left.Exponent > right.Exponent ? AlignExponent(left, right) >= right.Mantissa : left.Mantissa >= AlignExponent(right, left); | |
} | |
/// <summary> | |
/// Returns the mantissa of value, aligned to the exponent of reference. | |
/// Assumes the exponent of value is larger than of reference. | |
/// </summary> | |
private static BigInteger AlignExponent(BigDecimal value, BigDecimal reference) | |
{ | |
return value.Mantissa * BigInteger.Pow(10, value.Exponent - reference.Exponent); | |
} | |
#endregion | |
#region Additional mathematical functions | |
public static BigDecimal Exp(double exponent) | |
{ | |
var tmp = (BigDecimal)1; | |
while (Math.Abs(exponent) > 100) | |
{ | |
var diff = exponent > 0 ? 100 : -100; | |
tmp *= Math.Exp(diff); | |
exponent -= diff; | |
} | |
return tmp * Math.Exp(exponent); | |
} | |
public static BigDecimal Pow(double basis, double exponent) | |
{ | |
var tmp = (BigDecimal)1; | |
while (Math.Abs(exponent) > 100) | |
{ | |
var diff = exponent > 0 ? 100 : -100; | |
tmp *= Math.Pow(basis, diff); | |
exponent -= diff; | |
} | |
return tmp * Math.Pow(basis, exponent); | |
} | |
#endregion | |
public override string ToString() | |
{ | |
return string.Concat(Mantissa.ToString(), "E", Exponent); | |
} | |
public bool Equals(BigDecimal other) | |
{ | |
return other.Mantissa.Equals(Mantissa) && other.Exponent == Exponent; | |
} | |
public override bool Equals(object obj) | |
{ | |
if (ReferenceEquals(null, obj)) | |
{ | |
return false; | |
} | |
return obj is BigDecimal && Equals((BigDecimal) obj); | |
} | |
public override int GetHashCode() | |
{ | |
unchecked | |
{ | |
return (Mantissa.GetHashCode()*397) ^ Exponent; | |
} | |
} | |
public int CompareTo(object obj) | |
{ | |
if (ReferenceEquals(obj, null) || !(obj is BigDecimal)) | |
{ | |
throw new ArgumentException(); | |
} | |
return CompareTo((BigDecimal) obj); | |
} | |
public int CompareTo(BigDecimal other) | |
{ | |
return this < other ? -1 : (this > other ? 1 : 0); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment