1

when using "chained" double generators with jqwik I get a scale error message java.util.concurrent.ExecutionException: net.jqwik.api.JqwikException: Decimal value -1.6099999999999999 cannot be represented with scale 4..

Can you provide me with some details on how to set this scale and the meaning of this parameter ?

Here is the generator function I use :

    @Provide("close doubles")
    Arbitrary<Tuple.Tuple2<Double,Double>> closeDoubles(@ForAll() Double aDouble) {
        return Arbitraries.doubles()
                .between(aDouble-2.5, aDouble+2.5)
                .withSpecialValue(aDouble)
                .ofScale(4)
                .map(num ->Tuple.of(aDouble,num));
    }

It is then combined to form a business object instance.

My ultimate goal is to generate 2 doubles that are "close" to each other (here the distance is 2.5).

Antonin
  • 879
  • 2
  • 10
  • 27
  • Also, I just began using jqwik, I must apologize in advance if I am asking obvious questions or trying to resolve a trivial issue. – Antonin Jun 24 '22 at 08:17

1 Answers1

1

The problem you encounter is due to rounding errors of double numbers and the fact that jqwik is strict with allowing only upper and lower boundaries that comply with the specified scale.

I see several options to get around that, one is to use BigDecimals for generation and map them to double afterwards. This may look like overhead but actually it is not because that's what jqwik is doing anyway under the hood. This could look like:

@Provide
Arbitrary<Tuple.Tuple2<Double, Double>> closeDoubles(@ForAll @Scale(4) BigDecimal aBigDecimal) {
    BigDecimal twoPointFive = new BigDecimal("2.5");
    return Arbitraries.bigDecimals().between(aBigDecimal.subtract(twoPointFive), aBigDecimal.add(twoPointFive))
                      .ofScale(4)
                      .map(num -> Tuple.of(aBigDecimal.doubleValue(), num.doubleValue()));
}

Mind that the original number should also use the same scale as the target numbers, otherwise it will have a default scale of 2.

Personally, I'd prefer to generate a number and the delta, which has improved shrinking behaviour and will create a tuple with identical numbers more often:

@Provide
Arbitrary<Tuple.Tuple2<Double, Double>> closeDoubles2(@ForAll @Scale(4) BigDecimal aBigDecimal) {
    BigDecimal twoPointFive = new BigDecimal("2.5");
    return Arbitraries.bigDecimals().between(twoPointFive.negate(), twoPointFive)
                      .ofScale(4)
                      .map(num -> Tuple.of(aBigDecimal.doubleValue(), aBigDecimal.add(num).doubleValue()));
}
johanneslink
  • 4,877
  • 1
  • 20
  • 37
  • Thanks a lot @johanneslink, very nice and responsive support of users of your library. your solution with the delta seems more elegant indeed. Now tackling the next issue : combining generated values in a business object. – Antonin Jun 24 '22 at 08:58
  • 1
    Luckily, there're two whole chapters about that in the user guide: https://jqwik.net/docs/current/user-guide.html#combining-arbitraries and https://jqwik.net/docs/current/user-guide.html#combining-arbitraries-with-builders – johanneslink Jun 24 '22 at 09:51
  • Yep I did it with success. My first attempt was using "decoupled" Arbitraries, each using a Combiner. And a final map + filter to create a pair of objects. Unfortunately the filter predicate was two often false : one generator would be "stuck" on a value and the other one would try to generate many values that could not pass the filter predicate. Did combine all Arbitraries and generate the tuple directly : the parameters all evolve at each generation and the filter predicate lets enough value pass by. (Maybe one of the worst comment I ever wrote in terms of clarity) – Antonin Jun 24 '22 at 10:00