6
Pickman
6y

Today my boss sent me something that smelled fishy to me. While he was trying to simulate Excel's rounding he faced what was to him unexpected behaviour and he claimed that one constructor of the BigDecimal class was "wrong".
It took me a moment why this was happening to him and I identified two issues in his code.
I found one fo the issues funny and I would like to present you a challenge. Can you find a number that disproves his claim?
It's Java if anyone was wondering.

double d = 102.15250;

BigDecimal db = new BigDecimal(d)
.setScale(3, BigDecimal.ROUND_HALF_EVEN);

BigDecimal db2 = new BigDecimal(String.format("%f",d))
.setScale(3, BigDecimal.ROUND_HALF_EVEN);

BigDecimal db3 = BigDecimal.valueOf(d)
.setScale(3, BigDecimal.ROUND_HALF_EVEN);

System.out.println(db); // WRONG! 102.153
System.out.println(db2); // RIGHT! 102.152
System.out.println(db3); // RIGHT! 102.152

P.s. of course the code itself is just a simple check, it's not how he usually writes code.
P.p.s. it's all about the numerical representation types.

Comments
  • 0
    Does the BidDezimal consstructor only take floats?
  • 0
    No, of course. It's just the BigDecimal class, you can find the APIs online.
  • 2
    It's not the constructor being wrong, it's the one who uses it that does it wrong. That's all that needs to be said.
    Otherwise, RTFM or consider accuracy issues within floating-point numerical datatypes:

    "The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding."
  • 1
    @filthyranter this is the root of the problem. Well done. But how to explain its manifestation? Why do the other two prints exhibit the correct behaviour? Do they do that always (they don't)? How do we find a number that shows this?

    Also notice how the author of this code was actually lazy and used BigDecimal to round the numbers instead if writing his own function.
  • 0
    @Pickman The issue is that the new BigDecimal() constructor takes the exact value of the double, so the technically "more correct" way.
    The other two methods use the string representation.
    So if there's a difference, the first and the latter two will be different (but of course, that is not always the case)
    The rounding using .setScale just more visibly shows that effect, tbh.
  • 2
    This is like 0 != 0m in c#
  • 1
    @filthyranter not quite, the second two cast the double to float and then use the string representation of that float. This means that is equivalent to using the first constructor (well its version that takes a float) by casting the double to float. What's funny is that this does not prevent "unexpected" behaviour.

    In fact if that double would be a float there would not be this "error".
  • 0
    @gruff yes, I guess it is.
Add Comment