56

I tried the following,

   double doubleVal = 1.745;
   double doubleVal1 = 0.745;
   BigDecimal bdTest = new BigDecimal(  doubleVal);
   BigDecimal bdTest1 = new BigDecimal(  doubleVal1 );
   bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
   bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
   System.out.println("bdTest:"+bdTest); //1.75
   System.out.println("bdTest1:"+bdTest1);//0.74    problemmmm ????????????  

but got weird results. Why?

slugonamission
  • 9,562
  • 1
  • 34
  • 41
aliplane
  • 875
  • 3
  • 8
  • 9
  • Thanks for mentioning `.setScale` as a better way to round than the mysteriously inscrutable `.round(MatchContext...)` bs. – MarkHu Sep 23 '21 at 00:21

6 Answers6

111

Never construct BigDecimals from floats or doubles. Construct them from ints or strings. floats and doubles loose precision.

This code works as expected (I just changed the type from double to String):

public static void main(String[] args) {
  String doubleVal = "1.745";
  String doubleVal1 = "0.745";
  BigDecimal bdTest = new BigDecimal(  doubleVal);
  BigDecimal bdTest1 = new BigDecimal(  doubleVal1 );
  bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
  bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
  System.out.println("bdTest:"+bdTest); //1.75
  System.out.println("bdTest1:"+bdTest1);//0.75, no problem
}
Augusto
  • 28,839
  • 5
  • 58
  • 88
  • 12
    This also works fine without going to strings if you use `BigDecimal.valueOf(double)`, the static factory method which is preferred over the constructor (noted in the javadocs.) Using `BigDecimal bdTest = BigDecimal.valueOf(1.745); BigDecimal bdTest1 = BigDecimal.valueOf(0.745);` gives the same result (0.75) – Joshua Goldberg May 29 '15 at 21:34
  • @JoshuaGoldberg didn't you mean `BigDecimal.valueOf("0.745");` instead of `BigDecimal.valueOf(1.745);` ??? ... maybe not, there is no such `BigDecimal.valueOf(String)` :-/ – Julien Mar 16 '16 at 09:35
  • Yes, the key to the comment is the lack of quotes. From the javadoc of `BigDec.valueOf(double)`: _"Note: This is generally the preferred way to convert a double (or float) into a BigDecimal, as the value returned is equal to that resulting from constructing a BigDecimal from the result of using Double.toString(double)."_ – Joshua Goldberg Mar 16 '16 at 21:01
  • What if my `BigDecimal` is imported with `@ConfigurationProperties` from Spring? I can't control how it's constructed from `application.yml` which supposely already provides the value as a `String` and still ends with the same rounding issue. – Sidney de Moraes Jul 09 '19 at 17:13
  • 3
    @JoshuaGoldberg: `BigDecimal.valueOf(double)` is _not_ the preferred way of constructing a BigDecimal. It is the preferred way of converting a double into a BigDecimal. The string constructor is the preferred way of constructing a BigDecimal. Per the docs: "it is generally recommended that the String constructor be used in preference to [the double constructor]" – Sean Aug 26 '21 at 17:20
  • 1
    `valueOf(double)` and `new BigDecimal(String)` have similar comments indicating they are "the preferred way.to convert a float or double..." My read, given the comments on both constructors about "the unpredictability of `new BigDecimal(double)`" is that either of those first two are good, and the caveat is to generally avoid the latter (despite the confusing "the"). – Joshua Goldberg Aug 26 '21 at 22:02
13
double doubleVal = 1.745;
double doubleVal1 = 0.745;
System.out.println(new BigDecimal(doubleVal));
System.out.println(new BigDecimal(doubleVal1));

outputs:

1.74500000000000010658141036401502788066864013671875
0.74499999999999999555910790149937383830547332763671875

Which shows the real value of the two doubles and explains the result you get. As pointed out by others, don't use the double constructor (apart from the specific case where you want to see the actual value of a double).

More about double precision:

Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783
10

Use BigDecimal.valueOf(double d) instead of new BigDecimal(double d). The last one has precision errors by float and double.

admoca60
  • 143
  • 1
  • 7
3

This will maybe give you a hint on what went wrong.

import java.math.BigDecimal;

public class Main {
    public static void main(String[] args) {
        BigDecimal bdTest = new BigDecimal(0.745);
        BigDecimal bdTest1 = new BigDecimal("0.745");
        bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
        bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
        System.out.println("bdTest:" + bdTest); // prints "bdTest:0.74"
        System.out.println("bdTest1:" + bdTest1); // prints "bdTest:0.75"
    }
}

The problem is, that your input (a double x=0.745;) can not represent 0.745 exactly. It actually saves a value slightly lower. For BigDecimals, this is already below 0.745, so it rounds down...

Try not to use the BigDecimal(double/float) constructors.

brimborium
  • 9,362
  • 9
  • 48
  • 76
2

For your interest, to do the same with double

double doubleVal = 1.745;
double doubleVal2 = 0.745;
doubleVal = Math.round(doubleVal * 100 + 0.005) / 100.0;
doubleVal2 = Math.round(doubleVal2 * 100 + 0.005) / 100.0;
System.out.println("bdTest: " + doubleVal); //1.75
System.out.println("bdTest1: " + doubleVal2);//0.75

or just

double doubleVal = 1.745;
double doubleVal2 = 0.745;
System.out.printf("bdTest: %.2f%n",  doubleVal);
System.out.printf("bdTest1: %.2f%n",  doubleVal2);

both print

bdTest: 1.75
bdTest1: 0.75

I prefer to keep code as simple as possible. ;)

As @mshutov notes, you need to add a little more to ensure that a half value always rounds up. This is because numbers like 265.335 are a little less than they appear.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 2
    You can get unexpected result. For example: Math.round(265.335 * 100) / 100.0 != 265.34 See comments to http://stackoverflow.com/a/153753/2664193 – mshutov Aug 03 '16 at 12:07
0

various option are available such as:

 Double d= 123.12;
BigDecimal b = new BigDecimal(d, MathContext.DECIMAL64); // b = 123.1200000
b = b.setScale(2, BigDecimal.ROUND_HALF_UP);  // b = 123.12

BigDecimal b1 =new BigDecimal(collectionFileData.getAmount(), MathContext.DECIMAL64).setScale(2, BigDecimal.ROUND_HALF_UP)  // b1= 123.12

 d = (double) Math.round(d * 100) / 100;
BigDecimal b2 = new BigDecimal(d.toString());  // b2= 123.12



shubham kumar
  • 271
  • 2
  • 6