5

Consider this code:

import java.math.BigDecimal;
import java.math.RoundingMode;

public class RoundingTests {

    public static void main(String[] args) {
        BigDecimal bd1 = new BigDecimal(265.345d);
        BigDecimal bd2 = new BigDecimal(265.335d);

        System.out.println("Setting scale 265.345: " + bd1.setScale(2, RoundingMode.HALF_EVEN));
        System.out.println("Setting scale 265.335: " + bd2.setScale(2, RoundingMode.HALF_EVEN));
    }
}

The output is:

Setting scale 265.345: 265.35
Setting scale 265.335: 265.33

Now this is exactly the opposite of what I was expecting. With RoundingMode.HALF_EVEN (also called Bankers rounding) I was expecting both these values to become the value 265.34

Note that I am not using the BigDecimal.round method on purpose because it also does not what I need. If I add this code (and import java.math.MathContext) :

System.out.println("Rounding 265.345: " + bd1.round(new MathContext(2, RoundingMode.HALF_EVEN)));
System.out.println("Rounding 265.335: " + bd2.round(new MathContext(2, RoundingMode.HALF_EVEN)));

Then the output is:

Rounding 265.345: 2.7E+2
Rounding 265.335: 2.7E+2

This is expected and also explained in BigDecimal setScale and round, but means it's basically useless for my purpose.

Can someone explain the issue with setScale here?

Update: So it was just another floating point problem, with no easy way to fix it instead of using BigDecimals from the start.

Community
  • 1
  • 1
Sebastiaan van den Broek
  • 4,861
  • 5
  • 34
  • 61
  • 4
    The problem is the floating-point constants. Try it with quoted strings instead of floating-point constants as constructor arguments. – user207421 Nov 08 '13 at 12:53
  • Ugh indeed... that is annoying. In my actual application they are not literals but double variables, but I guess the same applies. So it might just be a coincidence that it's doing the opposite of what I am expecting, for other values it might accidentally work correctly. It's weird that something so simple becomes so hard. I really don't want to convert things to a String and back again but it seems to be the only working solution. – Sebastiaan van den Broek Nov 08 '13 at 12:58
  • [Floating point](http://floating-point-gui.de/) will always be a pain to work with to represent monetary values. – Jonathan Drapeau Nov 08 '13 at 13:01
  • Where are you getting those values and are they really just 3 digits? – Jonathan Drapeau Nov 08 '13 at 13:04
  • They could be coming from many places. In this case they really are just 3 digits because this is a testcase I made myself. It's a java.lang.Double with that exact value. But we are developing a generic application development platform so it could mean anything. I will update my answer with my actual solution. – Sebastiaan van den Broek Nov 08 '13 at 13:10
  • The problem is that the actual FP values aren't what you think they are. Converting to and from strings won't help. – user207421 Nov 08 '13 at 13:13
  • What would you suggest to do in this case EJP? I had a look at http://stackoverflow.com/questions/153724/how-to-round-a-number-to-n-decimal-places-in-java/ but solutions there also include String conversions OR they don't take into account the problem I just had. You also have a reply there but it only shows the problem. You seem to say the only solution is to work with BigDecimal from the start, right? – Sebastiaan van den Broek Nov 08 '13 at 13:14
  • That would be the best option indeed, `BigDecimal` from the start. If that's possible to do, go with that. – Jonathan Drapeau Nov 08 '13 at 13:25
  • Unfortunately it's not, at least not in the short term. I know about the problems regarding floating point arithmetics. I just did not realize they also came into play for things like rounding numbers. I will try to go towards BigDecimals in the long run, but for now I hope the String conversion will do for most cases. – Sebastiaan van den Broek Nov 08 '13 at 13:27
  • Actually I decided against this and will just go with the problems of floating point arithmetic here rather than trying to patch things. – Sebastiaan van den Broek Nov 08 '13 at 13:38

1 Answers1

1

You might want to change your code to use BigDecimal.valueOf() instead of new BigDecimal().

The code

  Double dValue = 265.345d;
  Double dValue2 = 265.335d;
  BigDecimal value = BigDecimal.valueOf(dValue);
  BigDecimal bd1 = new BigDecimal(265.345d);
  BigDecimal bd2 = new BigDecimal(265.335d);
  BigDecimal value2 = BigDecimal.valueOf(dValue2);
  System.out.println("BigDecimal.valueOf(dValue);");
  System.out.println(value.toPlainString());
  System.out.println(String.valueOf(dValue));
  value = value.setScale(2, BigDecimal.ROUND_HALF_EVEN);
  System.out.println(value);
  System.out.println("BigDecimal.valueOf(dValue2);");
  System.out.println(value2.toPlainString());
  System.out.println(String.valueOf(dValue2));
  value2 = value2.setScale(2, BigDecimal.ROUND_HALF_EVEN);
  System.out.println(value2);
  System.out.println("BigDecimal bd1 = new BigDecimal(265.345d);");
  System.out.println(bd1.setScale(2, BigDecimal.ROUND_HALF_EVEN));
  System.out.println("BigDecimal bd2 = new BigDecimal(265.335d);");
  System.out.println(bd2.setScale(2, BigDecimal.ROUND_HALF_EVEN));

outputs :

BigDecimal.valueOf(dValue);
265.345
265.345
265.34
BigDecimal.valueOf(dValue2);
265.335
265.335
265.34
BigDecimal bd1 = new BigDecimal(265.345d);
265.35
BigDecimal value2 = BigDecimal.valueOf(dValue2);
265.33
Jonathan Drapeau
  • 2,578
  • 2
  • 24
  • 32
  • Thanks, that works better than my way without having to use string values of things. However, the actual implementation seems to use the string representation of the double value as well. From the Javadoc at http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html : `valueOf(double val) Translates a double into a BigDecimal, using the double's canonical string representation provided by the Double.toString(double) method.`. So basically this does not solve an issue with floating point numbers either. – Sebastiaan van den Broek Nov 08 '13 at 14:08
  • The shown output is not the output of the code above. There is no "BigDecimal bd2 = new BigDecimal(265.335d);" for example. – mrg Jan 31 '20 at 09:19