typelevel / typelevel/scalacheck
arbitrary[BigDecimal] generates numbers that cause NumberFormatExceptions when serialized and deserialized
Nobody has claimed this yet.
- Dominant language
- Scala
- Stars
- 2k
- Forks
- 393
- Avg merge
- 6h 42m
- Merged PRs (30d)
- 4
Description
I had arbitrary[BigDecimal] generate the following: "0E+2147483648" in version 1.11.3. That scale is bigger than Int.MaxValue, so when you serialize the BigDecimal as a String and then try to reconstruct it with that String you will get a NumberFormatException. I understand why this is, but it's troublesome.
scala> BigDecimal(0, Int.MinValue)
res6: scala.math.BigDecimal = 0E+2147483648
According to the Javadocs, when you use this constructor, the value of the BigDecimal is going to be unscaledValue * (10^(-1 * scale)). So Int.MinValue actually becomes the maximum scale, and thanks to two's complement, this actually results in impossible 32-bit integer scale values.
A quick fix for this case is just to choose between Int.MinValue + limit + 1, but I think there's more than meets the eye to this issue. For instance, when I ran this generator several times to see what was going on by using this print call: println(s"unscaledVal is $unscaledVal, scale is $scale, limit is $limit, mc is $mc") I found that it'll make other problematic numbers too:
unscaledVal is -28334198897217871282176, scale is -2147483640, limit is 7, mc is precision=16 roundingMode=HALF_EVEN
So what's wrong with this?
scala> val orig = BigDecimal(BigInt("-28334198897217871282176"), -2147483640, UNLIMITED)
orig: scala.math.BigDecimal = -2.8334198897217871282176E+2147483662
scala> BigDecimal(orig.toString)
java.lang.NumberFormatException
at java.math.BigDecimal.<init>(BigDecimal.java:554)
at java.math.BigDecimal.<init>(BigDecimal.java:383)
at java.math.BigDecimal.<init>(BigDecimal.java:806)
at scala.math.BigDecimal$.exact(BigDecimal.scala:125)
at scala.math.BigDecimal$.apply(BigDecimal.scala:283)
... 33 elided
I realize we can blame java.math.BigDecimal for its inconsistent constructors, but, it's still problematic here. Should there be a generator provided that doesn't suffer from this gotcha? Here is one that works for me:
lazy val genBigDecimal: Gen[BigDecimal] = for {
unscaledVal <- arbitrary[BigInt]
scale <- Gen.chooseNum(Int.MinValue + unscaledVal.abs.toString.length, Int.MaxValue)
} yield BigDecimal(unscaledVal, scale)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the arbitrary[BigDecimal] generator and reproduce the provided Int.MinValue and large-negative-scale examples. Compare generated values' String serialization with reconstruction via BigDecimal(String); done means the generator no longer produces values that fail this round trip, while preserving its intended coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100