package scala.math
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.Test
import java.math.{BigDecimal => BD, MathContext => MC}
/* Tests various maps by making sure they all agree on the same answers. */
@RunWith(classOf[JUnit4])
class BigDecimalTest {
// Motivated by SI-6173: BigDecimal#isWhole implementation is very heap intensive
@Test
def isWholeTest() {
val wholes = List(
BigDecimal(1),
BigDecimal(10L),
BigDecimal(14.000),
BigDecimal(new BD("19127981892347012385719827340123471923850195")),
BigDecimal("1e1000000000"),
BigDecimal(14.1928857191985e22),
BigDecimal(14.12519823759817, new MC(2))
)
val fracs = List(
BigDecimal(0.1),
BigDecimal(new BD("1.000000000000000000000000000000000001")),
BigDecimal(new BD("275712375971892375127591745810580123751.99999")),
BigDecimal("14.19238571927581e6"),
BigDecimal("912834718237510238591285")/2
)
assert(wholes.forall(_.isWhole) && fracs.forall(! _.isWhole))
}
// Motivated by SI-6699: BigDecimal.isValidDouble behaves unexpectedly
@Test
def isValidDoubleTest() {
val valids = List(
BigDecimal(1),
BigDecimal(19571.125),
BigDecimal.decimal(0.1),
BigDecimal(1e15)
)
val invalids = List(
BigDecimal(new BD("1.0000000000000000000000000000000000000000001")),
BigDecimal("10e1000000"),
BigDecimal("10e-1000000")
)
assert(
valids.forall(_.isDecimalDouble) &&
invalids.forall(! _.isDecimalDouble)
)
}
// Motivated by SI-6173: BigDecimal#isWhole implementation is very heap intensive
@Test
def doesNotExplodeTest() {
val troublemaker = BigDecimal("1e1000000000")
val reasonable = BigDecimal("1e1000")
val reasonableInt = reasonable.toBigInt
assert(
reasonable.hashCode == reasonableInt.hashCode &&
reasonable == reasonableInt &&
reasonableInt == reasonable &&
troublemaker.hashCode != reasonable.hashCode &&
!(troublemaker == reasonableInt) &&
!(reasonableInt == troublemaker)
)
}
// Motivated by SI-6456: scala.math.BigDecimal should not accept a null value
@Test
def refusesNullTest() {
def isIAE[A](a: => A) = try { a; false } catch { case iae: IllegalArgumentException => true }
def isNPE[A](a: => A) = try { a; false } catch { case npe: NullPointerException => true }
assert(
isIAE(new BigDecimal(null: BD, new MC(2))) &&
isIAE(new BigDecimal(new BD("5.7"), null: MC)) &&
isNPE(BigDecimal(null: BigInt)) &&
isNPE(BigDecimal(null: String)) &&
isNPE(BigDecimal(null: Array[Char]))
)
}
// Motivated by SI-6153: BigDecimal.hashCode() has high collision rate
@Test
def hashCodesAgreeTest() {
val bi: BigInt = 100000
val bd: BigDecimal = 100000
val l: Long = 100000
val d: Double = 100000
assert(
d.## == l.## &&
l.## == bd.## &&
bd.## == bi.## &&
(bd pow 4).hashCode == (bi pow 4).hashCode &&
BigDecimal("1e150000").hashCode != BigDecimal("1e150000").toBigInt.hashCode
)
}
// Motivated by noticing BigDecimal(0.1f) != BigDecimal(0.1)
@Test
def consistentTenthsTest() {
def tenths = List[Any](
BigDecimal("0.1"),
0.1,
BigDecimal.decimal(0.1f),
BigDecimal.decimal(0.1),
BigDecimal(0.1),
BigDecimal(BigInt(1), 1),
BigDecimal(new BD("0.1")),
BigDecimal(1L, 1),
BigDecimal(1) / BigDecimal(10),
BigDecimal(10).pow(-1)
)
for (a <- tenths; b <- tenths) assert(a == b, s"$a != $b but both should be 0.1")
}
// Motivated by noticing BigDecimal(123456789, mc6) != BigDecimal(123456789L, mc6)
// where mc6 is a MathContext that rounds to six digits
@Test
def consistentRoundingTest() {
val mc6 = new MC(6)
val sameRounding = List(
List(
123457000,
123457000L,
123457e3,
BigDecimal(123456789, mc6),
BigDecimal(123456789L, mc6),
BigDecimal(123456789d, mc6),
BigDecimal("123456789", mc6),
BigDecimal(Array('1','2','3','4','5','6','7','8','9'), mc6),
BigDecimal(BigInt(123456789), mc6),
BigDecimal(BigInt(1234567890), 1, mc6),
BigDecimal.decimal(123456789, mc6),
BigDecimal.decimal(123456789d, mc6),
BigDecimal.decimal(new BD("123456789"), mc6)
),
List(
123456789,
123456789L,
123456789d,
new BigDecimal(new BD("123456789"), mc6),
new BigDecimal(new BD("123456789")),
BigDecimal(123456789),
BigDecimal(123456789L),
BigDecimal(123456789d),
BigDecimal("123456789"),
BigDecimal(Array('1','2','3','4','5','6','7','8','9')),
BigDecimal(BigInt(123456789)),
BigDecimal(BigInt(1234567890), 1),
BigDecimal.decimal(123456789),
BigDecimal.decimal(123456789d),
BigDecimal.valueOf(123456789d, mc6)
)
)
sameRounding.map(_.zipWithIndex).foreach{ case xs =>
for ((a,i) <- xs; (b,j) <- xs) {
assert(a == b, s"$a != $b (#$i != #$j) but should be the same")
assert(a.## == b.##, s"Hash code mismatch in equal BigDecimals: #$i != #$j")
}
}
val List(xs, ys) = sameRounding.map(_.zipWithIndex)
for ((a,i) <- xs; (b,j) <- ys) assert(a != b, s"$a == $b (#$i == #$j) but should be different")
}
// This was unexpectedly truncated in 2.10
@Test
def noPrematureRoundingTest() {
val text = "9791375983750284059237954823745923845928547807345082378340572986452364"
val same = List[Any](
BigInt(text), BigDecimal(text), BigDecimal(new BD(text))
)
for (a <- same; b <- same) assert(a == b, s"$a != $b but should be the same")
}
// Tests attempts to make sane the representation of IEEE binary32 and binary64
// (i.e. Float and Double) with Scala's text-is-King BigDecimal policy
@Test
def churnRepresentationTest() {
val rn = new scala.util.Random(42)
for (i <- 1 to 1000) {
val d = rn.nextDouble
assert({
BigDecimal.decimal(d).isDecimalDouble &&
BigDecimal.binary(d).isBinaryDouble &&
BigDecimal.exact(d).isExactDouble
}, s"At least one wrong BigDecimal representation for $d")
}
for (i <- 1 to 1000) {
val f = rn.nextFloat
assert({
BigDecimal.decimal(f).isDecimalFloat &&
BigDecimal.binary(f).isBinaryFloat &&
BigDecimal.exact(f).isExactFloat
}, s"At least one wrong BigDecimal representation for $f")
}
for (i <- 1 to 1000) {
val ndig = 15+rn.nextInt(5)
val s = Array.fill(ndig)((rn.nextInt(10)+'0').toChar).mkString
val bi = BigInt(s)
val l = bi.toLong
val d = bi.toDouble
val bd = BigDecimal(bi)
val bd2 = BigDecimal.decimal(d)
assert(!bi.isValidLong || bi == l, s"Should be invalid or equal: $bi $l")
assert(!bi.isValidDouble || bi == d, s"Should be invalid or equal: $bi $d")
assert(bd == bi, s"Should be equal $bi $bd")
assert(bd.## == bi.##, s"Hash codes for $bi, $bd should be equal")
assert(bd == bd2 || bd2 != BigDecimal.exact(d) || !bi.isValidDouble,
s"$bd != $bd2 should only be when inexact or invalid")
assert(d == bd2 && bd2 == d, s"$d != $bd2 but they should equal")
}
val different = List(
BigDecimal.decimal(0.1),
BigDecimal.binary(0.1),
BigDecimal.binary(0.1, new MC(25)),
BigDecimal.exact(0.1),
BigDecimal.exact(0.1f),
BigDecimal.decimal((0.1f).toDouble)
)
for (a <- different; b <- different if (a ne b))
assert(a != b, "BigDecimal representations of Double mistakenly conflated")
}
// Make sure hash code agrees with decimal representation of Double
@Test
def test_SI8970() {
assert((0.1).## == BigDecimal(0.1).##)
}
// Motivated by the problem of MathContext lost
@Test
def testMathContext() {
def testPrecision() {
val p = 1000
val n = BigDecimal("1.1", MC.UNLIMITED).pow(p)
// BigDecimal(x: Float, mc: MC), which may not do what you want, is deprecated
assert(BigDecimal(1.1f, MC.UNLIMITED).pow(p) == BigDecimal(java.lang.Double.toString(1.1f.toDouble), MC.UNLIMITED).pow(p))
assert(BigDecimal(1.1d, MC.UNLIMITED).pow(p) == n)
assert(BigDecimal(new BD("1.1"), MC.UNLIMITED).pow(p) == n)
assert(BigDecimal.decimal(1.1f, MC.UNLIMITED).pow(p) == n)
assert(BigDecimal.decimal(1.1d, MC.UNLIMITED).pow(p) == n)
assert(BigDecimal.decimal(new BD("1.1"), MC.UNLIMITED).pow(p) == n)
assert((BigDecimal(11, MC.UNLIMITED) / 10).pow(p) == n)
assert((BigDecimal.decimal(11, MC.UNLIMITED) / 10).pow(p) == n)
}
def testRounded() {
// the default rounding mode is HALF_UP
assert((BigDecimal(1.23f, new MC(3)) + BigDecimal("0.005")).rounded == BigDecimal("1.24")) // deprecated api
assert((BigDecimal(1.23d, new MC(3)) + BigDecimal("0.005")).rounded == BigDecimal("1.24"))
assert((BigDecimal.decimal(1.23f, new MC(3)) + BigDecimal("0.005")).rounded == BigDecimal("1.24"))
assert((BigDecimal.decimal(1.23d, new MC(3)) + BigDecimal("0.005")).rounded == BigDecimal("1.24"))
}
testPrecision()
testRounded()
}
@Test
def testIsComparable() {
assert(BigDecimal(0.1).isInstanceOf[java.lang.Comparable[_]])
}
}