Correct way to show money

5

I am starting to work on a system that manages money, for the amounts I use the BigDecimal data type, however I have encountered some problems when generating invoices for the final client, since the invoices only have 2 decimals, therefore:

If a product costs $ 1,004 and the customer buys 300 pieces of this product the actual cost will be $ 301.2 but on the invoice the customer will see the following:

amount: 300

unit cost: $ 1.00

total: $ 301.20

The client when seeing it will think that it is an error.

The way I show the data is as follows:

    BigDecimal total = new BigDecimal(getTotal());

    System.out.println(Math.round(total.doubleValue() * 100) / 100);

This generates 3 main concerns:

  • What should be done in such cases?
  • When it comes to money, is it correct to round or truncate? (If any of these is a correct solution)
  • Is there any type of data or library that is for exclusive use of money and that maybe it is better to use BigDecimal?

PS: The example above is just a simplified example to help understand the real problem.

    
asked by gibran alexis moreno zuñiga 05.08.2017 в 00:06
source

1 answer

5

To represent monetary values it is recommended to use:

  • BigDecimal - is the default recommended. It's a bit more inconvenient to use, but it has built-in rounding modes.

  • int

  • long

To decide which of the three to use, it is important to take into account the number of digits that the application will handle (more or less).

If number of digits:

  • <=9 : use BigDecimal , int , or long

  • <=18 : use BigDecimal or long

  • >18 : use only BigDecimal

Note that the class Currency encapsulates standard identifiers for the world's coins.

To represent monetary values, no it is recommended to use:

  • double

  • float

because they always carry small rounding differences .

Some things to take into account regarding BigDecimal :

  • The recommended constructor is BigDecimal (String) , not BigDecimal (double) - see javadoc (When using the constructor double the results can be unpredictable).

  • The BigDecimal objects are immutable: operations always return new objects and never modify the state of existing objects

  • The ROUND_HALF_EVEN style of Rounding introduces the minimum bias. It is also called bankers' rounding, or round-to-equal.

Code example:

Here is an example of currency calculation code using BigDecimal . The example is taken as is from your source . Perform basic operations: +, - , * , / between two values.

import java.math.BigDecimal;
import java.util.Currency;

/**
* Example of typical calculations with monetary values, implemented with
* <tt>BigDecimal</tt>.
*
* <P>This example is for a currency which has two decimal places.
*
* See
* http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html
*
* Note in particular how the <em>default</em> scale of the result of an
* operation is calculated from the scales of the two input numbers :
* <ul>
* <li> a + b : max[ scale(a), scale(b) ]
* <li> a - b : max[ scale(a), scale(b) ]
* <li> a * b : scale(a) + scale(b)
* <li> a / b : scale(a) - scale(b)
* </ul>
*/
public final class MoneyCalculation {

  /**
  * Simple test harness.
  *
  * Takes two numeric arguments, representing monetary values, in a form
  * which can be passed successfully to the <tt>BigDecimal(String)</tt>
  * constructor (<tt>25.00, 25.0, 25</tt>, etc).
  *
  * Note that the <tt>String</tt> constructor is preferred for
  * <tt>BigDecimal</tt>.
  */
  public static void main(String... aArgs){
    BigDecimal amountOne = new BigDecimal(aArgs[0]);
    BigDecimal amountTwo = new BigDecimal(aArgs[1]);
    MoneyCalculation calc = new MoneyCalculation(amountOne, amountTwo);
    calc.doCalculations();
  }

  public MoneyCalculation(BigDecimal aAmountOne, BigDecimal aAmountTwo){
    fAmountOne = rounded(aAmountOne);
    fAmountTwo = rounded(aAmountTwo);
  }

  public void doCalculations() {
    log("Amount One: " + fAmountOne);
    log("Amount Two: " + fAmountTwo);
    log("Sum : " + getSum());
    log("Difference : " + getDifference());
    log("Average : " + getAverage());
    log("5.25% of Amount One: " + getPercentage());
    log("Percent Change From Amount One to Two: " + getPercentageChange());
  }

  // PRIVATE

  private BigDecimal fAmountOne;
  private BigDecimal fAmountTwo;

  /**
  * Defined centrally, to allow for easy changes to the rounding mode.
  */
  private static int ROUNDING_MODE = BigDecimal.ROUND_HALF_EVEN;

  /**
  * Number of decimals to retain. Also referred to as "scale".
  */
  private static int DECIMALS = 2;
  //An alternate style for this value :
  //private static int DECIMAL_PLACES =
  //  Currency.getInstance("USD").getDefaultFractionDigits()
  //;

  private static int EXTRA_DECIMALS = 4;
  private static final BigDecimal TWO = new BigDecimal("2");
  private static BigDecimal HUNDRED = new BigDecimal("100");
  private static BigDecimal PERCENTAGE = new BigDecimal("5.25");

  private void log(String aText){
    System.out.println(aText);
  }

  private BigDecimal getSum(){
    return fAmountOne.add(fAmountTwo);
  }

  private BigDecimal getDifference(){
    return fAmountTwo.subtract(fAmountOne);
  }

  private BigDecimal getAverage(){
    return getSum().divide(TWO, ROUNDING_MODE);
  }

  private BigDecimal getPercentage(){
    BigDecimal result = fAmountOne.multiply(PERCENTAGE);
    result = result.divide(HUNDRED, ROUNDING_MODE);
    return rounded(result);
  }

  private BigDecimal getPercentageChange(){
    BigDecimal fractionalChange = getDifference().divide(
      fAmountOne, EXTRA_DECIMALS, ROUNDING_MODE
    );
    return rounded(fractionalChange.multiply(HUNDRED));
  }

  private BigDecimal rounded(BigDecimal aNumber){
    return aNumber.setScale(DECIMALS, ROUNDING_MODE);
  }
} 

For more details: Java Best Practices: Representing money

A class Money

For years the community of programmers missed a class Money that would help to handle situations like these.

Finally, the JDK 9 brings the API Money into the JSR 354 , to manage money and currency models.

See:

answered by 05.08.2017 / 04:35
source