-2

I have the following bit of code:

AtomicReference<BigDecimal> totalAmount = new AtomicReference<>(new BigDecimal(BigInteger.ZERO, new MathContext(3)));
List<BigDecimal> list = new ArrayList<BigDecimal>();
list.add(new BigDecimal(8024));
list.add(new BigDecimal(8024));


list.forEach(value -> {


    totalAmount.set(totalAmount.get().add(value, new MathContext(3)));
});

Basically when i run the above code sum of total amount should be 16048 but instead it is `1.60E+4. Please advice what i am missing here to get the correct amount?

user1999453
  • 1,297
  • 4
  • 29
  • 65
  • Is an `AtomicReference` really necessary to obtain the effect? – Michael Piefel Dec 30 '20 at 19:40
  • because of the functional for loop i have to use AtomicReference – user1999453 Dec 30 '20 at 19:41
  • Is the functional for-loop really necessary to obtain the effect? – Michael Piefel Dec 30 '20 at 19:42
  • nothing "functional" about that loop, it relies on side effects. see https://stackoverflow.com/questions/22635945/adding-up-bigdecimals-using-streams – Nathan Hughes Dec 30 '20 at 19:43
  • no i thought it would be cleaner code, please advice – user1999453 Dec 30 '20 at 19:43
  • 1
    What do you believe [`new MathContext(3)`](https://docs.oracle.com/javase/8/docs/api/java/math/MathContext.html#MathContext-int-) means, and why do you believe that? --- If means that result should have a **precision of 3**, i.e. only 3 digits of precision. `8024` reduced to 3 digits of precision is `8020`!!! – Andreas Dec 30 '20 at 19:44
  • 1
    @user1999453 *Advice on loop:* Use a normal loop: `for (BigDecimal value : list) { totalAmount = totalAmount.add(value); }` where the variable is defined as `BigDecimal totalAmount = BigDecimal.ZERO;` – Andreas Dec 30 '20 at 19:45
  • Thnks will use the normal for loop without the precision – user1999453 Dec 30 '20 at 19:47
  • 1
    My point was: `new BigDecimal(BigInteger.ZERO, new MathContext(3)).add(new BigDecimal(8024), new MathContext(3)).add(new BigDecimal(8024), new MathContext(3))` will yield exactly the same result. Your question obfuscates the real problem by showing unnecessay code. Please trim down your code to the absolute minimum. – Michael Piefel Dec 30 '20 at 19:53
  • So no threading is involved in this task? Edit your Question to say so, one way or the other. The `AtomicReference` suggest multi-threading which opens a whole can of worms. If you are *not* threading, the solution is much simpler. – Basil Bourque Dec 30 '20 at 21:15

2 Answers2

2

You seem to be missing two things:

  1. For the value: using new MathContext(3) when adding causes the value to be rounded to three digits. First you add 8024, and the sum, 8024, is rounded to 8020. Everything beyond the first three digits is set to 0, rounding up or down. Then you add 8024 again, and the sum, 16044, is rounded to 16000.
  2. For the output format: When you print a BigDecimal, you are (implicitly or explicitly) calling its toString method. This method very often produces the scientific notation that you got. The exact rules for when it does are in the documentation, see the link at the bottom. If you want to be sure to control the format of your BigDecimal, use a DecimalFormat.
  3. Not related to your unwanted output in this example: You are misusing AtomicReference when in your addition first doing a get() and then a set(). In a multi-threaded environment (which is where AtomicReference makes any sense) a different thread may do a set() or other operation between those two calls, but the result of that would get lost when you do set(). Instead you should use the accumulateAndGet (or getAndAccumulate) method of AtomicReference.

I also don’t understand why you were doing things in such a complicated way. This simpler way gives you what you ask for:

    List<BigDecimal> list = List.of(new BigDecimal("8024"), new BigDecimal("8024"));
    
    BigDecimal totalAmount = BigDecimal.ZERO;
    for (BigDecimal value : list) {
        totalAmount = totalAmount.add(value);
    }
    System.out.println(totalAmount);

Output is:

16048

It’s similar to the code in the other answer.

Edit: As a matter of good habit I am passing string literals to new BigDecimal(). While passing an integer literal such as 8024 poses no problem in itself, passing a double literal, for example 8024.1, often implies an inaccuracy. Passing a string will always give you a BigDecimsl holding the exact same value as the string.

Documentation links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 1
    Perhaps add one further note: Passing integer literals here works, but may be a bad habit as passing float/double literals may fail by introducing [floating-point inaccuracy](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). Better to pass inputs to `BigDecimal` as strings. – Basil Bourque Dec 30 '20 at 21:27
1

BigDecimal and its add() method will produce the desired result. There is no need to use lambda expressions here. Below code demonstrates the same. This too, is clean code.

public static void main(String[] args) {
        
        List<BigDecimal> list = new ArrayList<BigDecimal>();
        
        list.add(new BigDecimal(8024));
        list.add(new BigDecimal(8024));
        
        BigDecimal total = BigDecimal.ZERO;
        
        for(BigDecimal element : list)
            total = total.add(element);
        
        System.out.print("Sum : " + total);
}

See this code run live at IdeOne.com.

Sum : 16048

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Damodar Hegde
  • 386
  • 1
  • 8