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?

1
  • Thanks for mentioning .setScale as a better way to round than the mysteriously inscrutable .round(MatchContext...) bs. Commented Sep 23, 2021 at 0:21

6 Answers 6

114

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
}
Sign up to request clarification or add additional context in comments.

6 Comments

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)
@JoshuaGoldberg didn't you mean BigDecimal.valueOf("0.745"); instead of BigDecimal.valueOf(1.745); ??? ... maybe not, there is no such BigDecimal.valueOf(String) :-/
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)."
@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]"
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").
|
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:

Comments

10

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

Comments

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.

Comments

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.

1 Comment

You can get unexpected result. For example: Math.round(265.335 * 100) / 100.0 != 265.34 See comments to stackoverflow.com/a/153753/2664193
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



Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.