/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2007-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */


package scala
package math

import scala.language.implicitConversions

import java.math.{ MathContext, BigDecimal => BigDec }
import scala.collection.immutable.NumericRange

/**
 *  @author  Stephane Micheloud
 *  @author  Rex Kerr
 *  @version 1.1
 *  @since 2.7
 */
object BigDecimal {
  private final val maximumHashScale = 4934           // Quit maintaining hash identity with BigInt beyond this scale
  private final val hashCodeNotComputed = 0x5D50690F  // Magic value (happens to be "BigDecimal" old MurmurHash3 value)
  private final val deci2binary = 3.3219280948873626  // Ratio of log(10) to log(2)
  private val minCached = -512
  private val maxCached = 512
  val defaultMathContext = MathContext.DECIMAL128

  /** Cache only for defaultMathContext using BigDecimals in a small range. */
  private lazy val cache = new Array[BigDecimal](maxCached - minCached + 1)

  object RoundingMode extends Enumeration {
    // Annoying boilerplate to ensure consistency with java.math.RoundingMode
    import java.math.{RoundingMode => RM}
    type RoundingMode = Value
    val UP          = Value(RM.UP.ordinal)
    val DOWN        = Value(RM.DOWN.ordinal)
    val CEILING     = Value(RM.CEILING.ordinal)
    val FLOOR       = Value(RM.FLOOR.ordinal)
    val HALF_UP     = Value(RM.HALF_UP.ordinal)
    val HALF_DOWN   = Value(RM.HALF_DOWN.ordinal)
    val HALF_EVEN   = Value(RM.HALF_EVEN.ordinal)
    val UNNECESSARY = Value(RM.UNNECESSARY.ordinal)
  }

  /** Constructs a `BigDecimal` using the decimal text representation of `Double` value `d`, rounding if necessary. */
  def decimal(d: Double, mc: MathContext): BigDecimal =
    new BigDecimal(new BigDec(java.lang.Double.toString(d), mc), mc)

  /** Constructs a `BigDecimal` using the decimal text representation of `Double` value `d`. */
  def decimal(d: Double): BigDecimal = decimal(d, defaultMathContext)

  /** Constructs a `BigDecimal` using the decimal text representation of `Float` value `f`, rounding if necessary.
   *  Note that `BigDecimal.decimal(0.1f) != 0.1f` since equality agrees with the `Double` representation, and
   *  `0.1 != 0.1f`.
   */
  def decimal(f: Float, mc: MathContext): BigDecimal =
    new BigDecimal(new BigDec(java.lang.Float.toString(f), mc), mc)

  /** Constructs a `BigDecimal` using the decimal text representation of `Float` value `f`.
   *  Note that `BigDecimal.decimal(0.1f) != 0.1f` since equality agrees with the `Double` representation, and
   *  `0.1 != 0.1f`.
   */
  def decimal(f: Float): BigDecimal = decimal(f, defaultMathContext)

  // This exists solely to avoid conversion from Int/Long to Float, screwing everything up.
  /** Constructs a `BigDecimal` from a `Long`, rounding if necessary.  This is identical to `BigDecimal(l, mc)`. */
  def decimal(l: Long, mc: MathContext): BigDecimal = apply(l, mc)

  // This exists solely to avoid conversion from Int/Long to Float, screwing everything up.
  /** Constructs a `BigDecimal` from a `Long`.  This is identical to `BigDecimal(l)`. */
  def decimal(l: Long): BigDecimal = apply(l)

  /** Constructs a `BigDecimal` using a `java.math.BigDecimal`, rounding if necessary. */
  def decimal(bd: BigDec, mc: MathContext): BigDecimal = new BigDecimal(bd.round(mc), mc)

  /** Constructs a `BigDecimal` by expanding the binary fraction
   *  contained by `Double` value `d` into a decimal representation,
   *  rounding if necessary.  When a `Float` is converted to a
   *  `Double`, the binary fraction is preserved, so this method
   *  also works for converted `Float`s.
   */
  def binary(d: Double, mc: MathContext): BigDecimal = new BigDecimal(new BigDec(d, mc), mc)

  /** Constructs a `BigDecimal` by expanding the binary fraction
   *  contained by `Double` value `d` into a decimal representation.
   *  Note: this also works correctly on converted `Float`s.
   */
  def binary(d: Double): BigDecimal = binary(d, defaultMathContext)

  /** Constructs a `BigDecimal` from a `java.math.BigDecimal`.  The
   *  precision is the default for `BigDecimal` or enough to represent
   *  the `java.math.BigDecimal` exactly, whichever is greater.
   */
  def exact(repr: BigDec): BigDecimal = {
    val mc =
      if (repr.precision <= defaultMathContext.getPrecision) defaultMathContext
      else new MathContext(repr.precision, java.math.RoundingMode.HALF_EVEN)
    new BigDecimal(repr, mc)
  }

  /** Constructs a `BigDecimal` by fully expanding the binary fraction
   *  contained by `Double` value `d`, adjusting the precision as
   *  necessary.  Note: this works correctly on converted `Float`s also.
   */
  def exact(d: Double): BigDecimal = exact(new BigDec(d))

  /** Constructs a `BigDecimal` that exactly represents a `BigInt`.
   */
  def exact(bi: BigInt): BigDecimal = exact(new BigDec(bi.bigInteger))

  /** Constructs a `BigDecimal` that exactly represents a `Long`.  Note that
   *  all creation methods for `BigDecimal` that do not take a `MathContext`
   *  represent a `Long`; this is equivalent to `apply`, `valueOf`, etc..
   */
  def exact(l: Long): BigDecimal = apply(l)

  /** Constructs a `BigDecimal` that exactly represents the number
   *  specified in a `String`.
   */
  def exact(s: String): BigDecimal = exact(new BigDec(s))

  /** Constructs a `BigDecimal` that exactly represents the number
   *  specified in base 10 in a character array.
   */
 def exact(cs: Array[Char]): BigDecimal = exact(new BigDec(cs))


  /** Constructs a `BigDecimal` using the java BigDecimal static
   *  valueOf constructor.  Equivalent to `BigDecimal.decimal`.
   *
   *  @param  d the specified double value
   *  @return the constructed `BigDecimal`
   */
  def valueOf(d: Double): BigDecimal = apply(BigDec valueOf d)

  /** Constructs a `BigDecimal` using the java BigDecimal static
   *  valueOf constructor, specifying a `MathContext` that is
   *  used for computations but isn't used for rounding.  Use
   *  `BigDecimal.decimal` to use `MathContext` for rounding,
   *  or `BigDecimal(java.math.BigDecimal.valueOf(d), mc)` for
   *  no rounding.
   *
   *  @param  d the specified double value
   *  @param  mc the `MathContext` used for future computations
   *  @return the constructed `BigDecimal`
   */
  @deprecated("MathContext is not applied to Doubles in valueOf. Use BigDecimal.decimal to use rounding, or java.math.BigDecimal.valueOf to avoid it.", "2.11.0")
  def valueOf(d: Double, mc: MathContext): BigDecimal = apply(BigDec valueOf d, mc)

  /** Constructs a `BigDecimal` using the java BigDecimal static
   *  valueOf constructor.
   *
   *  @param  x the specified `Long` value
   *  @return the constructed `BigDecimal`
   */
  def valueOf(x: Long): BigDecimal = apply(x)

  /** Constructs a `BigDecimal` using the java BigDecimal static
   *  valueOf constructor.  This is unlikely to do what you want;
   *  use `valueOf(f.toDouble)` or `decimal(f)` instead.
   */
  @deprecated("Float arguments to valueOf may not do what you wish. Use decimal or valueOf(f.toDouble).", "2.11.0")
  def valueOf(f: Float): BigDecimal = valueOf(f.toDouble)

  /** Constructs a `BigDecimal` using the java BigDecimal static
   *  valueOf constructor.  This is unlikely to do what you want;
   *  use `valueOf(f.toDouble)` or `decimal(f)` instead.
   */
  @deprecated("Float arguments to valueOf may not do what you wish. Use decimal or valueOf(f.toDouble).", "2.11.0")
  def valueOf(f: Float, mc: MathContext): BigDecimal = valueOf(f.toDouble, mc)


  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified `Integer` value.
   *
   *  @param i the specified integer value
   *  @return  the constructed `BigDecimal`
   */
  def apply(i: Int): BigDecimal = apply(i, defaultMathContext)

  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified `Integer` value, rounding if necessary.
   *
   *  @param i the specified integer value
   *  @param mc the precision and rounding mode for creation of this value and future operations on it
   *  @return  the constructed `BigDecimal`
   */
  def apply(i: Int, mc: MathContext): BigDecimal =
    if (mc == defaultMathContext && minCached <= i && i <= maxCached) {
      val offset = i - minCached
      var n = cache(offset)
      if (n eq null) { n = new BigDecimal(BigDec.valueOf(i.toLong), mc); cache(offset) = n }
      n
    }
    else apply(i.toLong, mc)

  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified long value.
   *
   *  @param l the specified long value
   *  @return  the constructed `BigDecimal`
   */
  def apply(l: Long): BigDecimal =
    if (minCached <= l && l <= maxCached) apply(l.toInt)
    else new BigDecimal(BigDec.valueOf(l), defaultMathContext)

  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified long value, but rounded if necessary.
   *
   *  @param l the specified long value
   *  @param mc the precision and rounding mode for creation of this value and future operations on it
   *  @return  the constructed `BigDecimal`
   */
  def apply(l: Long, mc: MathContext): BigDecimal =
    new BigDecimal(new BigDec(l, mc), mc)

  /** Constructs a `BigDecimal` whose unscaled value is equal to that
   *  of the specified long value.
   *
   *  @param  unscaledVal the value
   *  @param  scale       the scale
   *  @return the constructed `BigDecimal`
   */
  def apply(unscaledVal: Long, scale: Int): BigDecimal =
    apply(BigInt(unscaledVal), scale)

  /** Constructs a `BigDecimal` whose unscaled value is equal to that
   *  of the specified long value, but rounded if necessary.
   *
   *  @param  unscaledVal the value
   *  @param  scale       the scale
   *  @param mc the precision and rounding mode for creation of this value and future operations on it
   *  @return the constructed `BigDecimal`
   */
  def apply(unscaledVal: Long, scale: Int, mc: MathContext): BigDecimal =
    apply(BigInt(unscaledVal), scale, mc)

  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified double value.  Equivalent to `BigDecimal.decimal`.
   *
   *  @param d the specified `Double` value
   *  @return  the constructed `BigDecimal`
   */
  def apply(d: Double): BigDecimal = decimal(d, defaultMathContext)

  // note we don't use the static valueOf because it doesn't let us supply
  // a MathContext, but we should be duplicating its logic, modulo caching.
  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified double value, but rounded if necessary.  Equivalent to
   *  `BigDecimal.decimal`.
   *
   *  @param d the specified `Double` value
   *  @param mc the precision and rounding mode for creation of this value and future operations on it
   *  @return  the constructed `BigDecimal`
   */
  def apply(d: Double, mc: MathContext): BigDecimal = decimal(d, mc)

  @deprecated("The default conversion from Float may not do what you want. Use BigDecimal.decimal for a String representation, or explicitly convert the Float with .toDouble.", "2.11.0")
  def apply(x: Float): BigDecimal = apply(x.toDouble)

  @deprecated("The default conversion from Float may not do what you want. Use BigDecimal.decimal for a String representation, or explicitly convert the Float with .toDouble.", "2.11.0")
  def apply(x: Float, mc: MathContext): BigDecimal = apply(x.toDouble, mc)

  /** Translates a character array representation of a `BigDecimal`
   *  into a `BigDecimal`.
   */
  def apply(x: Array[Char]): BigDecimal = exact(x)

  /** Translates a character array representation of a `BigDecimal`
   *  into a `BigDecimal`, rounding if necessary.
   */
  def apply(x: Array[Char], mc: MathContext): BigDecimal =
    new BigDecimal(new BigDec(x, mc), mc)

  /** Translates the decimal String representation of a `BigDecimal`
   *  into a `BigDecimal`.
   */
  def apply(x: String): BigDecimal = exact(x)

  /** Translates the decimal String representation of a `BigDecimal`
   *  into a `BigDecimal`, rounding if necessary.
   */
  def apply(x: String, mc: MathContext): BigDecimal =
    new BigDecimal(new BigDec(x, mc), mc)

  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified `BigInt` value.
   *
   *  @param x the specified `BigInt` value
   *  @return  the constructed `BigDecimal`
   */
  def apply(x: BigInt): BigDecimal = exact(x)

  /** Constructs a `BigDecimal` whose value is equal to that of the
   *  specified `BigInt` value, rounding if necessary.
   *
   *  @param x  the specified `BigInt` value
   *  @param mc the precision and rounding mode for creation of this value and future operations on it
   *  @return   the constructed `BigDecimal`
   */
  def apply(x: BigInt, mc: MathContext): BigDecimal =
    new BigDecimal(new BigDec(x.bigInteger, mc), mc)

  /** Constructs a `BigDecimal` whose unscaled value is equal to that
   *  of the specified `BigInt` value.
   *
   *  @param unscaledVal the specified `BigInt` value
   *  @param scale       the scale
   *  @return  the constructed `BigDecimal`
   */
  def apply(unscaledVal: BigInt, scale: Int): BigDecimal =
    exact(new BigDec(unscaledVal.bigInteger, scale))

  /** Constructs a `BigDecimal` whose unscaled value is equal to that
   *  of the specified `BigInt` value.
   *
   *  @param unscaledVal the specified `BigInt` value
   *  @param scale       the scale
   *  @param mc          the precision and rounding mode for creation of this value and future operations on it
   *  @return  the constructed `BigDecimal`
   */
  def apply(unscaledVal: BigInt, scale: Int, mc: MathContext): BigDecimal =
    new BigDecimal(new BigDec(unscaledVal.bigInteger, scale, mc), mc)

  /** Constructs a `BigDecimal` from a `java.math.BigDecimal`. */
  def apply(bd: BigDec): BigDecimal = apply(bd, defaultMathContext)

  @deprecated("This method appears to round a java.math.BigDecimal but actually doesn't. Use new BigDecimal(bd, mc) instead for no rounding, or BigDecimal.decimal(bd, mc) for rounding.", "2.11.0")
  def apply(bd: BigDec, mc: MathContext): BigDecimal = new BigDecimal(bd, mc)

  /** Implicit conversion from `Int` to `BigDecimal`. */
  implicit def int2bigDecimal(i: Int): BigDecimal = apply(i)

  /** Implicit conversion from `Long` to `BigDecimal`. */
  implicit def long2bigDecimal(l: Long): BigDecimal = apply(l)

  /** Implicit conversion from `Double` to `BigDecimal`. */
  implicit def double2bigDecimal(d: Double): BigDecimal = decimal(d)

  /** Implicit conversion from `java.math.BigDecimal` to `scala.BigDecimal`. */
  implicit def javaBigDecimal2bigDecimal(x: BigDec): BigDecimal = apply(x)
}

/**
 *  `BigDecimal` represents decimal floating-point numbers of arbitrary precision.
 *  By default, the precision approximately matches that of IEEE 128-bit floating
 *  point numbers (34 decimal digits, `HALF_EVEN` rounding mode).  Within the range
 *  of IEEE binary128 numbers, `BigDecimal` will agree with `BigInt` for both
 *  equality and hash codes (and will agree with primitive types as well).  Beyond
 *  that range--numbers with more than 4934 digits when written out in full--the
 *  `hashCode` of `BigInt` and `BigDecimal` is allowed to diverge due to difficulty
 *  in efficiently computing both the decimal representation in `BigDecimal` and the
 *  binary representation in `BigInt`.
 *
 *  When creating a `BigDecimal` from a `Double` or `Float`, care must be taken as
 *  the binary fraction representation of `Double` and `Float` does not easily
 *  convert into a decimal representation.  Three explicit schemes are available
 *  for conversion.  `BigDecimal.decimal` will convert the floating-point number
 *  to a decimal text representation, and build a `BigDecimal` based on that.
 *  `BigDecimal.binary` will expand the binary fraction to the requested or default
 *  precision.  `BigDecimal.exact` will expand the binary fraction to the
 *  full number of digits, thus producing the exact decimal value corresponding to
 *  the binary fraction of that floating-point number.  `BigDecimal` equality
 *  matches the decimal expansion of `Double`: `BigDecimal.decimal(0.1) == 0.1`.
 *  Note that since `0.1f != 0.1`, the same is not true for `Float`.  Instead,
 *  `0.1f == BigDecimal.decimal((0.1f).toDouble)`.
 *
 *  To test whether a `BigDecimal` number can be converted to a `Double` or
 *  `Float` and then back without loss of information by using one of these
 *  methods, test with `isDecimalDouble`, `isBinaryDouble`, or `isExactDouble`
 *  or the corresponding `Float` versions.  Note that `BigInt`'s `isValidDouble`
 *  will agree with `isExactDouble`, not the `isDecimalDouble` used by default.
 *
 *  `BigDecimal` uses the decimal representation of binary floating-point numbers
 *  to determine equality and hash codes.  This yields different answers than
 *  conversion between `Long` and `Double` values, where the exact form is used.
 *  As always, since floating-point is a lossy representation, it is advisable to
 *  take care when assuming identity will be maintained across multiple conversions.
 *
 *  `BigDecimal` maintains a `MathContext` that determines the rounding that
 *  is applied to certain calculations.  In most cases, the value of the
 *  `BigDecimal` is also rounded to the precision specified by the `MathContext`.
 *  To create a `BigDecimal` with a different precision than its `MathContext`,
 *  use `new BigDecimal(new java.math.BigDecimal(...), mc)`.  Rounding will
 *  be applied on those mathematical operations that can dramatically change the
 *  number of digits in a full representation, namely multiplication, division,
 *  and powers.  The left-hand argument's `MathContext` always determines the
 *  degree of rounding, if any, and is the one propagated through arithmetic
 *  operations that do not apply rounding themselves.
 *
 *  @author  Stephane Micheloud
 *  @author  Rex Kerr
 *  @version 1.1
 */
final class BigDecimal(val bigDecimal: BigDec, val mc: MathContext)
extends ScalaNumber with ScalaNumericConversions with Serializable with Ordered[BigDecimal] {
  def this(bigDecimal: BigDec) = this(bigDecimal, BigDecimal.defaultMathContext)
  import BigDecimal.RoundingMode._
  import BigDecimal.{decimal, binary, exact}

  if (bigDecimal eq null) throw new IllegalArgumentException("null value for BigDecimal")
  if (mc eq null) throw new IllegalArgumentException("null MathContext for BigDecimal")

  // There was an implicit to cut down on the wrapper noise for BigDec -> BigDecimal.
  // However, this may mask introduction of surprising behavior (e.g. lack of rounding
  // where one might expect it).  Wrappers should be applied explicitly with an
  // eye to correctness.

  // Sane hash code computation (which is surprisingly hard).
  // Note--not lazy val because we can't afford the extra space.
  private final var computedHashCode: Int = BigDecimal.hashCodeNotComputed
  private final def computeHashCode(): Unit = {
    computedHashCode =
      if (isWhole && (precision - scale) < BigDecimal.maximumHashScale) toBigInt.hashCode
      else if (isDecimalDouble) doubleValue.##
      else {
        val temp = bigDecimal.stripTrailingZeros
        scala.util.hashing.MurmurHash3.mixLast( temp.scaleByPowerOfTen(temp.scale).toBigInteger.hashCode, temp.scale )
      }
  }

  /** Returns the hash code for this BigDecimal.
   *  Note that this does not merely use the underlying java object's
   *  `hashCode` because we compare `BigDecimal`s with `compareTo`
   *  which deems 2 == 2.00, whereas in java these are unequal
   *  with unequal `hashCode`s.  These hash codes agree with `BigInt`
   *  for whole numbers up ~4934 digits (the range of IEEE 128 bit floating
   *  point).  Beyond this, hash codes will disagree; this prevents the
   *  explicit representation of the `BigInt` form for `BigDecimal` values
   *  with large exponents.
   */
  override def hashCode(): Int = {
    if (computedHashCode == BigDecimal.hashCodeNotComputed) computeHashCode
    computedHashCode
  }

  /** Compares this BigDecimal with the specified value for equality.  Where `Float` and `Double`
   *  disagree, `BigDecimal` will agree with the `Double` value
   */
  override def equals (that: Any): Boolean = that match {
    case that: BigDecimal     => this equals that
    case that: BigInt         =>
      that.bitLength > (precision-scale-2)*BigDecimal.deci2binary &&
      this.toBigIntExact.exists(that equals _)
    case that: Double         =>
      !that.isInfinity && {
        val d = toDouble
        !d.isInfinity && d == that && equals(decimal(d))
      }
    case that: Float          =>
      !that.isInfinity && {
        val f = toFloat
        !f.isInfinity && f == that && equals(decimal(f.toDouble))
      }
    case _                    => isValidLong && unifiedPrimitiveEquals(that)
  }
  override def isValidByte  = noArithmeticException(toByteExact)
  override def isValidShort = noArithmeticException(toShortExact)
  override def isValidChar  = isValidInt && toIntExact >= Char.MinValue && toIntExact <= Char.MaxValue
  override def isValidInt   = noArithmeticException(toIntExact)
  def isValidLong  = noArithmeticException(toLongExact)
  /** Tests whether the value is a valid Float.  "Valid" has several distinct meanings, however.  Use
    * `isExactFloat`, `isBinaryFloat`, or `isDecimalFloat`, depending on the intended meaning.
    * By default, `decimal` creation is used, so `isDecimalFloat` is probably what you want.
    */
  @deprecated("What constitutes validity is unclear. Use `isExactFloat`, `isBinaryFloat`, or `isDecimalFloat` instead.", "2.11.0")
  def isValidFloat = {
    val f = toFloat
    !f.isInfinity && bigDecimal.compareTo(new BigDec(f.toDouble)) == 0
  }
  /** Tests whether the value is a valid Double.  "Valid" has several distinct meanings, however.  Use
    * `isExactDouble`, `isBinaryDouble`, or `isDecimalDouble`, depending on the intended meaning.
    * By default, `decimal` creation is used, so `isDecimalDouble` is probably what you want.
    */
  @deprecated("Validity has distinct meanings. Use `isExactDouble`, `isBinaryDouble`, or `isDecimalDouble` instead.", "2.11.0")
  def isValidDouble = {
    val d = toDouble
    !d.isInfinity && bigDecimal.compareTo(new BigDec(d)) == 0
  }

  /** Tests whether this `BigDecimal` holds the decimal representation of a `Double`. */
  def isDecimalDouble = {
    val d = toDouble
    !d.isInfinity && equals(decimal(d))
  }

  /** Tests whether this `BigDecimal` holds the decimal representation of a `Float`. */
  def isDecimalFloat = {
    val f = toFloat
    !f.isInfinity && equals(decimal(f))
  }

  /** Tests whether this `BigDecimal` holds, to within precision, the binary representation of a `Double`. */
  def isBinaryDouble = {
    val d = toDouble
    !d.isInfinity && equals(binary(d,mc))
  }

  /** Tests whether this `BigDecimal` holds, to within precision, the binary representation of a `Float`. */
  def isBinaryFloat = {
    val f = toFloat
    !f.isInfinity && equals(binary(f,mc))
  }

  /** Tests whether this `BigDecimal` holds the exact expansion of a `Double`'s binary fractional form into base 10. */
  def isExactDouble = {
    val d = toDouble
    !d.isInfinity && equals(exact(d))
  }

  /** Tests whether this `BigDecimal` holds the exact expansion of a `Float`'s binary fractional form into base 10. */
  def isExactFloat = {
    val f = toFloat
    !f.isInfinity && equals(exact(f.toDouble))
  }


  private def noArithmeticException(body: => Unit): Boolean = {
    try   { body ; true }
    catch { case _: ArithmeticException => false }
  }

  def isWhole() = scale <= 0 || bigDecimal.stripTrailingZeros.scale <= 0

  def underlying = bigDecimal


  /** Compares this BigDecimal with the specified BigDecimal for equality.
   */
  def equals (that: BigDecimal): Boolean = compare(that) == 0

  /** Compares this BigDecimal with the specified BigDecimal
   */
  def compare (that: BigDecimal): Int = this.bigDecimal compareTo that.bigDecimal

  /** Addition of BigDecimals
   */
  def +  (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal add that.bigDecimal, mc)

  /** Subtraction of BigDecimals
   */
  def -  (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal subtract that.bigDecimal, mc)

  /** Multiplication of BigDecimals
   */
  def *  (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal.multiply(that.bigDecimal, mc), mc)

  /** Division of BigDecimals
   */
  def /  (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal.divide(that.bigDecimal, mc), mc)

  /** Division and Remainder - returns tuple containing the result of
   *  divideToIntegralValue and the remainder.  The computation is exact: no rounding is applied.
   */
  def /% (that: BigDecimal): (BigDecimal, BigDecimal) =
    this.bigDecimal.divideAndRemainder(that.bigDecimal) match {
      case Array(q, r)  => (new BigDecimal(q, mc), new BigDecimal(r, mc))
    }

  /** Divide to Integral value.
   */
  def quot (that: BigDecimal): BigDecimal =
    new BigDecimal(this.bigDecimal divideToIntegralValue that.bigDecimal, mc)

  /** Returns the minimum of this and that, or this if the two are equal
   */
  def min (that: BigDecimal): BigDecimal = (this compare that) match {
    case x if x <= 0 => this
    case _           => that
  }

  /** Returns the maximum of this and that, or this if the two are equal
   */
  def max (that: BigDecimal): BigDecimal = (this compare that) match {
    case x if x >= 0 => this
    case _           => that
  }

  /** Remainder after dividing this by that.
   */
  def remainder (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal remainder that.bigDecimal, mc)

  /** Remainder after dividing this by that.
   */
  def % (that: BigDecimal): BigDecimal = this remainder that

  /** Returns a BigDecimal whose value is this ** n.
   */
  def pow (n: Int): BigDecimal = new BigDecimal(this.bigDecimal.pow(n, mc), mc)

  /** Returns a BigDecimal whose value is the negation of this BigDecimal
   */
  def unary_- : BigDecimal = new BigDecimal(this.bigDecimal.negate(), mc)

  /** Returns the absolute value of this BigDecimal
   */
  def abs: BigDecimal = if (signum < 0) unary_- else this

  /** Returns the sign of this BigDecimal;
   *   -1 if it is less than 0,
   *   +1 if it is greater than 0,
   *   0  if it is equal to 0.
   */
  def signum: Int = this.bigDecimal.signum()

  /** Returns the precision of this `BigDecimal`.
   */
  def precision: Int = this.bigDecimal.precision()

  /** Returns a BigDecimal rounded according to the supplied MathContext settings, but
   *  preserving its own MathContext for future operations.
   */
  def round(mc: MathContext): BigDecimal = {
    val r = this.bigDecimal round mc
    if (r eq bigDecimal) this else new BigDecimal(r, this.mc)
  }

  /** Returns a `BigDecimal` rounded according to its own `MathContext` */
  def rounded: BigDecimal = {
    val r = bigDecimal round mc
    if (r eq bigDecimal) this else new BigDecimal(r, mc)
  }

  /** Returns the scale of this `BigDecimal`.
   */
  def scale: Int = this.bigDecimal.scale()

  /** Returns the size of an ulp, a unit in the last place, of this BigDecimal.
   */
  def ulp: BigDecimal = new BigDecimal(this.bigDecimal.ulp, mc)

  /** Returns a new BigDecimal based on the supplied MathContext, rounded as needed.
   */
  def apply(mc: MathContext): BigDecimal = new BigDecimal(this.bigDecimal round mc, mc)

  /** Returns a `BigDecimal` whose scale is the specified value, and whose value is
   *  numerically equal to this BigDecimal's.
   */
  def setScale(scale: Int): BigDecimal =
    if (this.scale == scale) this
    else new BigDecimal(this.bigDecimal setScale scale, mc)

  def setScale(scale: Int, mode: RoundingMode): BigDecimal =
    if (this.scale == scale) this
    else new BigDecimal(this.bigDecimal.setScale(scale, mode.id), mc)

  /** Converts this BigDecimal to a Byte.
   *  If the BigDecimal is too big to fit in a Byte, only the low-order 8 bits are returned.
   *  Note that this conversion can lose information about the overall magnitude of the
   *  BigDecimal value as well as return a result with the opposite sign.
   */
  override def byteValue   = intValue.toByte

  /** Converts this BigDecimal to a Short.
   *  If the BigDecimal is too big to fit in a Short, only the low-order 16 bits are returned.
   *  Note that this conversion can lose information about the overall magnitude of the
   *  BigDecimal value as well as return a result with the opposite sign.
   */
  override def shortValue  = intValue.toShort

  /** Converts this BigDecimal to a Char.
   *  If the BigDecimal is too big to fit in a Char, only the low-order 16 bits are returned.
   *  Note that this conversion can lose information about the overall magnitude of the
   *  BigDecimal value and that it always returns a positive result.
   */
  def charValue   = intValue.toChar

  /** Converts this BigDecimal to an Int.
   *  If the BigDecimal is too big to fit in an Int, only the low-order 32 bits
   *  are returned. Note that this conversion can lose information about the
   *  overall magnitude of the BigDecimal value as well as return a result with
   *  the opposite sign.
   */
  def intValue    = this.bigDecimal.intValue

  /** Converts this BigDecimal to a Long.
   *  If the BigDecimal is too big to fit in a Long, only the low-order 64 bits
   *  are returned. Note that this conversion can lose information about the
   *  overall magnitude of the BigDecimal value as well as return a result with
   *  the opposite sign.
   */
  def longValue   = this.bigDecimal.longValue

  /** Converts this BigDecimal to a Float.
   *  if this BigDecimal has too great a magnitude to represent as a float,
   *  it will be converted to `Float.NEGATIVE_INFINITY` or
   *  `Float.POSITIVE_INFINITY` as appropriate.
   */
  def floatValue  = this.bigDecimal.floatValue

  /** Converts this BigDecimal to a Double.
   *  if this BigDecimal has too great a magnitude to represent as a double,
   *  it will be converted to `Double.NEGATIVE_INFINITY` or
   *  `Double.POSITIVE_INFINITY` as appropriate.
   */
  def doubleValue = this.bigDecimal.doubleValue

  /** Converts this `BigDecimal` to a [[scala.Byte]], checking for lost information.
    * If this `BigDecimal` has a nonzero fractional part, or is out of the possible
    * range for a [[scala.Byte]] result, then a `java.lang.ArithmeticException` is
    * thrown.
    */
  def toByteExact = bigDecimal.byteValueExact

  /** Converts this `BigDecimal` to a [[scala.Short]], checking for lost information.
    * If this `BigDecimal` has a nonzero fractional part, or is out of the possible
    * range for a [[scala.Short]] result, then a `java.lang.ArithmeticException` is
    * thrown.
    */
  def toShortExact = bigDecimal.shortValueExact

  /** Converts this `BigDecimal` to a [[scala.Int]], checking for lost information.
    * If this `BigDecimal` has a nonzero fractional part, or is out of the possible
    * range for an [[scala.Int]] result, then a `java.lang.ArithmeticException` is
    * thrown.
    */
  def toIntExact = bigDecimal.intValueExact

  /** Converts this `BigDecimal` to a [[scala.Long]], checking for lost information.
    * If this `BigDecimal` has a nonzero fractional part, or is out of the possible
    * range for a [[scala.Long]] result, then a `java.lang.ArithmeticException` is
    * thrown.
    */
  def toLongExact = bigDecimal.longValueExact

  /** Creates a partially constructed NumericRange[BigDecimal] in range
   *  `[start;end)`, where start is the target BigDecimal.  The step
   *  must be supplied via the "by" method of the returned object in order
   *  to receive the fully constructed range.  For example:
   * {{{
   * val partial = BigDecimal(1.0) to 2.0       // not usable yet
   * val range = partial by 0.01                // now a NumericRange
   * val range2 = BigDecimal(0) to 1.0 by 0.01  // all at once of course is fine too
   * }}}
   *
   *  @param end    the end value of the range (exclusive)
   *  @return       the partially constructed NumericRange
   */
  def until(end: BigDecimal): Range.Partial[BigDecimal, NumericRange.Exclusive[BigDecimal]] =
    new Range.Partial(until(end, _))

  /** Same as the one-argument `until`, but creates the range immediately. */
  def until(end: BigDecimal, step: BigDecimal) = Range.BigDecimal(this, end, step)

  /** Like `until`, but inclusive of the end value. */
  def to(end: BigDecimal): Range.Partial[BigDecimal, NumericRange.Inclusive[BigDecimal]] =
    new Range.Partial(to(end, _))

  /** Like `until`, but inclusive of the end value. */
  def to(end: BigDecimal, step: BigDecimal) = Range.BigDecimal.inclusive(this, end, step)

  /** Converts this `BigDecimal` to a scala.BigInt.
   */
  def toBigInt(): BigInt = new BigInt(this.bigDecimal.toBigInteger())

  /** Converts this `BigDecimal` to a scala.BigInt if it
   *  can be done losslessly, returning Some(BigInt) or None.
   */
  def toBigIntExact(): Option[BigInt] =
    if (isWhole()) {
      try Some(new BigInt(this.bigDecimal.toBigIntegerExact()))
      catch { case _: ArithmeticException => None }
    }
    else None

  /** Returns the decimal String representation of this BigDecimal.
   */
  override def toString(): String = this.bigDecimal.toString()

}
