How to round 0.745 to 0.75 using BigDecimal.ROUND_HALF_UP?

135,351

Solution 1

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
}

Solution 2

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:

Solution 3

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

Solution 4

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.

Solution 5

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.

Share:
135,351
aliplane
Author by

aliplane

Updated on July 09, 2022

Comments

  • aliplane
    aliplane almost 2 years

    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?

  • Joshua Goldberg
    Joshua Goldberg almost 9 years
    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)
  • Julien
    Julien about 8 years
    @JoshuaGoldberg didn't you mean BigDecimal.valueOf("0.745"); instead of BigDecimal.valueOf(1.745); ??? ... maybe not, there is no such BigDecimal.valueOf(String) :-/
  • Joshua Goldberg
    Joshua Goldberg about 8 years
    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)."
  • mshutov
    mshutov almost 8 years
    You can get unexpected result. For example: Math.round(265.335 * 100) / 100.0 != 265.34 See comments to stackoverflow.com/a/153753/2664193
  • Sidney de Moraes
    Sidney de Moraes almost 5 years
    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.
  • Sean
    Sean over 2 years
    @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]"
  • Joshua Goldberg
    Joshua Goldberg over 2 years
    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").