32

At work, we found a problem when trying to divide a large number by 1000. This number came from the database.

Say I have this method:

private static BigDecimal divideBy1000(BigDecimal dividendo) {
    if (dividendo == null) return null;

    return dividendo.divide(BigDecimal.valueOf(1000), RoundingMode.HALF_UP);
}

When I make the following call

divideBy1000(new BigDecimal("176100000"))

I receive the expected value of 176100. But if I try the line below

divideBy1000(new BigDecimal("1761e+5"))

I receive the value 200000. Why this occurs? Both numbers are the same with different representation and the latest is what I receive from database. I understand that, somehow, the JVM is dividing the number 1761 by 1000, rounding up and filling with 0's at the end.

What is the best way to avoid this kind of behavior? Keep in mind that the original number is not controlled by me.

Raphael do Vale
  • 931
  • 2
  • 11
  • 28
  • Why do you get a BigDecimal from the database as a String rather than as a BigDecimal in the first place? http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html#getBigDecimal%28int%29 – JB Nizet Oct 19 '14 at 16:56
  • 1
    I don't. That was my way to simplify the example here. The original code uses getBigDecimal method. – Raphael do Vale Oct 19 '14 at 17:12
  • 5
    Then where in your real code do you have the problem of transforming the string "1761e+5" to a BigDecimal? What is the *actual* problem in your *actual* code? – JB Nizet Oct 19 '14 at 17:13

8 Answers8

25

As specified in javadoc, a BigDecimal is defined by an integer value and a scale.

The value of the number represented by the BigDecimal is therefore (unscaledValue × 10^(-scale)).

So BigDecimal("1761e+5") has scale -5 and BigDecimal(176100000) has scale 0.

The division of the two BigDecimal is done using the -5 and 0 scales respectively because the scales are not specified when dividing. The divide documentation explains why the results are different.

divide

public BigDecimal divide(BigDecimal divisor)

Returns a BigDecimal whose value is (this / divisor), and whose preferred scale is (this.scale() - divisor.scale()); if the exact quotient cannot be represented (because it has a non-terminating decimal expansion) an ArithmeticException is thrown.

Parameters:

divisor - value by which this BigDecimal is to be divided.

Returns:

this / divisor

Throws:

ArithmeticException — if the exact quotient does not have a terminating decimal expansion

Since:

1.5

If you specify a scale when dividing, e.g. dividendo.divide(BigDecimal.valueOf(1000), 0, RoundingMode.HALF_UP) you will get the same result.

Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
dcernahoschi
  • 14,968
  • 5
  • 37
  • 59
7

The expressions new BigDecimal("176100000") and new BigDecimal("1761e+5") are not equal. BigDecimal keeps track of both value, and precision.

BigDecimal("176100000") has 9 digits of precision and is represented internally as the BigInteger("176100000"), multiplied by 1. BigDecimal("1761e+5") has 4 digits of precision and is represented internally as the BigInteger("1761"), multiplied by 100000.

When you a divide a BigDecimal by a value, the result respects the digits of precision, resulting in different outputs for seemingly equal values.

Joshua
  • 2,431
  • 15
  • 23
  • That's not true. Use `compareTo` to see that they compare equivalently. Further, [Wolfram Alpha](http://www.wolframalpha.com/input/?i=176100000+%3D%3D+1761e%2B5) supports their equality. – Makoto Oct 19 '14 at 17:05
  • 6
    @Makoto: `compareTo` and `equals` are not the same operation for `BigDecimal`. – Oliver Charlesworth Oct 19 '14 at 17:10
  • I understand that. But how can I avoid this behavior as the number with less precision came from the database? Which is the best way to divide a number that came from the database? – Raphael do Vale Oct 19 '14 at 17:14
5

for your division with BigDecimal.

dividendo.divide(divisor,2,RoundingMode.CEILING)//00.00 nothing for up and nothing for down

in this operation have a precision for two decimals.

leandro lion
  • 51
  • 1
  • 3
2

To avoid this kind of problems in Java when dividing by powers of 10 you have a much efficient and precise approach:

dividendo.movePointLeft(3)
Carlos Verdes
  • 3,037
  • 22
  • 20
0

Yeah, that's kind of issue what you're experimenting. If I may, in a situation where you only have exponental numbers, you should cast them and then use your method. See what I suggest is this bit of code down there:

long  longValue = Double.valueOf("1761e+5").longValue();
BigDecimal value= new BigDecimal(longValue);

Use it in a method which would convert those string into a new BigDecimal and return this BigDecimal value. Then you can use those returned values with divideBy1000.That should clear any issue you're having.

If you have a lot of those, what you can do also in store those BigDecimal in a data structure like a list. Then use a foreach loop in which you apply divideBy1000 and each new value would be stored in a different list. Then you would just have to access this list to have your new set of values !

Hope it helps :)

Kevin Avignon
  • 2,853
  • 3
  • 19
  • 40
0

Try using round().

private static BigDecimal divideBy1000(BigDecimal dividendo) {
    if (dividendo == null) return null;

    return dividendo.divide(BigDecimal.valueOf(1000)).round(new MathContext(4, RoundingMode.HALF_UP));
}

 public static void main(String []args){
    BigDecimal bigD = new BigDecimal("1761e5");
    BigDecimal bigDr = divideBy1000(bigD);
    System.out.println(bigDr);
 }

The new MathContext(4, RoundingMode.HALF_UP)) line returns the division to 4 places.

This produces:

1.761E+5

Which is what you want. (:

Tetramputechture
  • 2,911
  • 2
  • 33
  • 48
0

Any time you are multiplying a BigDecimal by a power of 10, in this case you are multiplying by 10-3, you can use dividendo.scaleByPowerOfTen(power) which only modifies the scale of the BigDecimal object and side steps any rounding issues, or at least moves them to a later calculation.

The other answers here cover the more general case of dividing by any number.

meticoeus
  • 543
  • 4
  • 9
0

I want to quote basic concepts for BigDecimal:

A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale.

public class BigDecimal extends Number implements Comparable<BigDecimal> {
    // …
    private final BigInteger intVal;
    private final int scale;
}
  • That is, BigDecimal number is represented as unscaled integer value * 10^(-scale)
  • For example, 1.234 = 1234 * 10^(-3). So, precision is 4, scale is 3.
  • Please refer to basic concept in here.

For the former:

BigDecimal bd1 = new BigDecimal("176100000");
System.out.println(bd1.precision());  // 9
System.out.println(bd1.scale());      // 0

BigDecimal bd2 = BigDecimal.valueOf(1000);
System.out.println(bd2.precision());  // 4
System.out.println(bd2.scale());      // 0

System.out.println(bd1.divide(bd2));                        // 176100
System.out.println(bd1.divide(bd2, RoundingMode.HALF_UP));  // 176100

BigDecimal result = bd1.divide(bd2);
System.out.println(result.precision());  // 6
System.out.println(result.scale());      // 0
  1. The new BigDecimal("176100000")'s precision is 9 and scale is 0.
  2. The BigDecimal.valueOf(1000)'s precision is 4 and scale is 0.
  3. (176100000 * 10^0) / (1000 * 10^0) = 176100 * 10^0.
  4. With method public BigDecimal divide​(BigDecimal divisor, RoundingMode roundingMode), we have to use the dividend(new BigDecimal("176100000"))'s scale as a scale of returning BigDecimal. In this case, the scale is 0.

    Returns a BigDecimal whose value is (this / divisor), and whose scale is this.scale().

  5. As a result, we have BigDecimal number 176100 * 10^0 whose precision is 6 and scale is 0.
  6. The rounding is applied, but the result is integer already, so we just get 176100.

For the latter:

BigDecimal bd1 = new BigDecimal("1761e+5");
System.out.println(bd1.precision());  // 4
System.out.println(bd1.scale());      // -5

BigDecimal bd2 = BigDecimal.valueOf(1000);
System.out.println(bd2.precision());  // 4
System.out.println(bd2.scale());      // 0

System.out.println(bd1.divide(bd2));                        // 1.761E+5
System.out.println(bd1.divide(bd2, RoundingMode.HALF_UP));  // 2E+5

BigDecimal result1 = bd1.divide(bd2);
System.out.println(result1.precision());  // 4
System.out.println(result1.scale());      // -2

BigDecimal result2 = bd1.divide(bd2, RoundingMode.HALF_UP);
System.out.println(result2.precision());  // 1
System.out.println(result2.scale());      // -5
  1. The new BigDecimal("1761e+5")'s precision is 4 and scale is -5.
  2. The BigDecimal.valueOf(1000)'s precision is 4 and scale is 0.
  3. (1761 * 10^(-(-5))) / (1000 * 10^0) = 1.761 * 10^(-(-5))
  4. = 1761 * 10^(-(-2)) whose precision is 4 and scale is -2; prints "1.761E+5" using scientific notation of overriden toString.
  5. If we apply rounding, 1.761 * 10^(-(-5)) = 2 * 10^(-(-5)) whose precision is 1 and scale is -5; prints "2E+5" using scientific notation of overriden toString.

I am might be wrong. If you could catch my mistakes, please comment to this answer. I'll correct them.

rosshjb
  • 581
  • 1
  • 8
  • 26