72

I was trying to make my own class for currencies using longs, but apparently I should use BigDecimal instead. Could someone help me get started? What would be the best way to use BigDecimals for dollar currencies, like making it at least but no more than 2 decimal places for the cents, etc. The API for BigDecimal is huge, and I don't know which methods to use. Also, BigDecimal has better precision, but isn't that all lost if it passes through a double? if I do new BigDecimal(24.99), how will it be different than using a double? Or should I use the constructor that uses a String instead?

mk12
  • 24,644
  • 29
  • 92
  • 132
  • 1
    Everything looks fine, but for one final thing - when you use setScale() on a BigDecimal instance, especially if you will use it for addition later, provide the rounding factor of n+1, where n is the number of significant digits. For instance, set a rounding factor of 3, for the following addition 0.043 + 0.043 will yield 0.09 instead of 0.08 (if you chose 2 significant digits for rounding/storage). – Vineet Reynolds Sep 01 '09 at 22:19
  • In the previous example, if you chose 2 significant digits, and performed rounding (as an intermediate operation) before addition, you will get 0.04 + 0.04 = 0.08 – Vineet Reynolds Sep 01 '09 at 22:21
  • Should my variable that stores the percentage to be removed from the price be a BigDecimal also? – mk12 Sep 02 '09 at 00:00

9 Answers9

85

Here are a few hints:

  1. Use BigDecimal for computations if you need the precision that it offers (Money values often need this).
  2. Use the NumberFormat class for display. This class will take care of localization issues for amounts in different currencies. However, it will take in only primitives; therefore, if you can accept the small change in accuracy due to transformation to a double, you could use this class.
  3. When using the NumberFormat class, use the scale() method on the BigDecimal instance to set the precision and the rounding method.

PS: In case you were wondering, BigDecimal is always better than double, when you have to represent money values in Java.

PPS:

Creating BigDecimal instances

This is fairly simple since BigDecimal provides constructors to take in primitive values, and String objects. You could use those, preferably the one taking the String object. For example,

BigDecimal modelVal = new BigDecimal("24.455");
BigDecimal displayVal = modelVal.setScale(2, RoundingMode.HALF_EVEN);

Displaying BigDecimal instances

You could use the setMinimumFractionDigits and setMaximumFractionDigits method calls to restrict the amount of data being displayed.

NumberFormat usdCostFormat = NumberFormat.getCurrencyInstance(Locale.US);
usdCostFormat.setMinimumFractionDigits( 1 );
usdCostFormat.setMaximumFractionDigits( 2 );
System.out.println( usdCostFormat.format(displayVal.doubleValue()) );
Community
  • 1
  • 1
Vineet Reynolds
  • 72,899
  • 16
  • 143
  • 173
  • 3
    Agree especially on number 2. Keep the view (formatting for the display) separate from the model (BigDecimal). You can always write a custom java.text.Format to handle your specific data type. – Steve Kuo Sep 01 '09 at 00:33
  • Don't you mean DecimalFormat, not NumberFormat? – mk12 Sep 01 '09 at 01:22
  • @Mk12, you can use either. DecimalFormat is to be used when you want more control over the formatting. It provides the applyPattern() method that NumberFormat does not. – Vineet Reynolds Sep 01 '09 at 01:38
  • Oh, I guess NumberFormat works too. Just used getCurrencyInstance() and than setRoundMode to HALF_EVEN. – mk12 Sep 01 '09 at 01:38
  • Yes, it is getCurrencyInstance(Locale locale) that will help in localization. – Vineet Reynolds Sep 01 '09 at 01:41
  • Whoops!! Corrected a mistake regarding the usage of setScale(). The example is good to use now. – Vineet Reynolds Sep 01 '09 at 02:20
  • 8
    -1 for using the BigDecimal(double) constructor .. even though the documentation says that this constructor is 'unpredictable' – Ryan Fernandes Sep 01 '09 at 03:18
  • Do I really need two BigDecimals for each value like that? And can I not use the NumberFormat to do the HALF_EVEN rounding instead of the BigDecimal? And does this "scale" thing only do something when you call scale on the BigDecimal? And when would I do that? – mk12 Sep 01 '09 at 12:03
  • Well, you could use the BigDecimal primarily in the model (for computation and storage), in which case you don't need to use the setScale() method. And then use the NumberFormat methods for display of data. – Vineet Reynolds Sep 01 '09 at 13:15
  • But what's the point of the BigDecimal precision, if you display it with NumberFormat, converting to a double, doesn't that get rid of all the accuracy? And shouldn't the rounding be done before every calculation, what's the point of it if it is only rounded for display, never on the actual model? And doesn't scale return an int? – mk12 Sep 01 '09 at 20:03
  • Hmm. I think I'm starting to understand, BigDecimal has more precision, but when you display it, you need just 2 decimal places, and to get to there it needs to be properly rounded (e.g. NumberFormat), but In between calculations it keeps all the precision of the extra decimal places to get an accurate final result, right? And when you convert it to a double for NumberFormat, the loss of precision is only for that one value, not for all the calculations. Do I understand it right? Also, you said you need to use doubleValue, but for me just passing the BigDecimal worked fine. – mk12 Sep 01 '09 at 20:09
  • And would using number format, but also doing setScale be alright, like not that this application I'm making actually matters, or deals with real money, but if it did, once the final price was calculated for the purchase of something, say, would I then do bigDecimalInstance.scale() (having done setScale(2, RoudingMode.HALF_EVEN) at the start) to round off the value of the actual model to then charge the user or something? – mk12 Sep 01 '09 at 20:14
  • Ok I just realized setScale creates a new one, not an old one, so I need to do bigD = bigD.setScale(...), and also realized that scale just returns the scale and I don't need to do that, but if the scale is set, it will always be rounded. So should I do setScale at the end when I have finished calculations, or at the start so all values are rounded? – mk12 Sep 01 '09 at 20:30
  • Sorry for long comments, but my remaining question is, should I just use NumberFormat and set its roundingmode to half_even, for display, and then if I need the model data, to do setScale at the end of calculations, or should I just use the NumberFormat for the currency number formatting, and setScale the BigDecimal at the beginning? – mk12 Sep 01 '09 at 20:52
  • 1
    Let me rephrase your question as "When should I round a BigDecimal value?" That would depend on how you use the numbers - addition and subtraction require lesser number of decimal values to be stored, whereas multiplication and division would require more. In fact, you need 1 extra decimal place for addition and subtraction (to handle rounding for overflow). Unit costs should not be rounded, and neither should conversion units. All in all, it depends on what you are storing in the BigDecimal instance. If you need to retain precision throughout operations, perform rounding at the end. – Vineet Reynolds Sep 01 '09 at 20:57
  • 1
    Ideally, you should round at the end of the operation, and store that value in the database (usually for auditing in the financial services world), so that the value displayed and the value stored in the database remains the same. So you have your answer there, use Number format just for the currency formatting, and use setScale at the beginning (if necessary) and at the end (before storage). Removal of the rounding operations will enable verification of mathematical correctness of the computation. – Vineet Reynolds Sep 01 '09 at 21:01
  • By the way, there is no harm in raising these as separate questions on SO. It is beneficial since you would find more 'eyes' to watch a separate question, than the comments section of an answer. – Vineet Reynolds Sep 01 '09 at 21:03
  • Ok I'll do that next time. So If I use setScale at the beginning, that means I will always only have 2 decimal places, so for best precision I guess I'll just setScale at the end, or when I actually need to store a value, and it won't affect the current one because it creates a new one. But wait, what If I just need to display a value? Than would I have so use setScale for that too? Wouldn't it be simpler to making the NumberFormat round too? – mk12 Sep 01 '09 at 21:17
  • Because there's operations that I just want to display and others I also want to store.. – mk12 Sep 01 '09 at 21:20
  • It is better to define a consistent way of performing rounding operations. So you could use BigDecimal values throughout the model. In the view, you can use NumberFormat. – Vineet Reynolds Sep 01 '09 at 21:23
  • Which do you actually prefer though, rounding with the NumberFormat for display and with setScale for storage, or just setScale for all of it, and NumberFormat for display (just for the currency part)? – mk12 Sep 01 '09 at 22:14
  • I normally prefer setScale for most of the operations. Usually the result of a computation is something like profit before tax, or something of that variant. Performing a setScale() before I deduct/subtract tax, makes for easier verification as well. If I leave setScale() towards the end, to be used just before storage, I end up having a lot of precision (usually unnecessary precision). – Vineet Reynolds Sep 01 '09 at 22:32
  • The thumb rule would be the second one that you've listed - setScale for all (actually most), and NumberFormat for display. – Vineet Reynolds Sep 01 '09 at 22:33
  • So round when necessary, also round when precision is unnecessary, round with setScale, if there will be *no* more calculations, round to 2 decimal spaces, if there will be more, to 3 decimal spaces. Am I right? – mk12 Sep 01 '09 at 23:01
  • Yes, unless you intend to multiply or divide. In that case retain precision. – Vineet Reynolds Sep 02 '09 at 22:20
40

I would recommend a little research on Money Pattern. Martin Fowler in his book Analysis pattern has covered this in more detail.

public class Money {

    private static final Currency USD = Currency.getInstance("USD");
    private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_EVEN;

    private final BigDecimal amount;
    private final Currency currency;   

    public static Money dollars(BigDecimal amount) {
        return new Money(amount, USD);
    }

    Money(BigDecimal amount, Currency currency) {
        this(amount, currency, DEFAULT_ROUNDING);
    }

    Money(BigDecimal amount, Currency currency, RoundingMode rounding) {
        this.currency = currency;      
        this.amount = amount.setScale(currency.getDefaultFractionDigits(), rounding);
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public Currency getCurrency() {
        return currency;
    }

    @Override
    public String toString() {
        return getCurrency().getSymbol() + " " + getAmount();
    }

    public String toString(Locale locale) {
        return getCurrency().getSymbol(locale) + " " + getAmount();
    }   
}

Coming to the usage:

You would represent all monies using Money object as opposed to BigDecimal. Representing money as big decimal will mean that you will have the to format the money every where you display it. Just imagine if the display standard changes. You will have to make the edits all over the place. Instead using the Money pattern you centralize the formatting of money to a single location.

Money price = Money.dollars(38.28);
System.out.println(price);
dzezzz
  • 965
  • 7
  • 16
Brad
  • 401
  • 4
  • 3
  • 4
    One problem with this- not all currencies default to the symbol before the amount, or having a space inbetween. That's things that should be part of the fomatting. – Gabe Sechan May 23 '16 at 17:15
  • That's right @GabeSechan - this might interest you https://drivy.engineering/multi-currency-java/ – Mick Oct 22 '19 at 11:41
9

Or, wait for JSR-354. Java Money and Currency API coming soon!

Victor Grazi
  • 13,263
  • 13
  • 53
  • 85
1

1) If you are limited to the double precision, one reason to use BigDecimals is to realize operations with the BigDecimals created from the doubles.

2) The BigDecimal consists of an arbitrary precision integer unscaled value and a non-negative 32-bit integer scale, while the double wraps a value of the primitive type double in an object. An object of type Double contains a single field whose type is double

3) It should make no difference

You should have no difficulties with the $ and precision. One way to do it is using System.out.printf

Bhesh Gurung
  • 48,464
  • 20
  • 87
  • 139
Diego Dias
  • 19,409
  • 5
  • 30
  • 35
1

Use BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP) when you want to round up to the 2 decimal points for cents. Be aware of rounding off error when you do calculations though. You need to be consistent when you will be doing the rounding of money value. Either do the rounding right at the end just once after all calculations are done, or apply rounding to each value before doing any calculations. Which one to use would depend on your business requirement, but generally, I think doing rounding right at the end seems to make a better sense to me.

Use a String when you construct BigDecimal for money value. If you use double, it will have a trailing floating point values at the end. This is due to computer architecture regarding how double/float values are represented in binary format.

Bhesh Gurung
  • 48,464
  • 20
  • 87
  • 139
tim_wonil
  • 12,402
  • 5
  • 25
  • 41
1

Primitive numeric types are useful for storing single values in memory. But when dealing with calculation using double and float types, there is a problems with the rounding.It happens because memory representation doesn't map exactly to the value. For example, a double value is supposed to take 64 bits but Java doesn't use all 64 bits.It only stores what it thinks the important parts of the number. So you can arrive to the wrong values when you adding values together of the float or double type.

Please see a short clip https://youtu.be/EXxUSz9x7BM

Gregory Nozik
  • 3,004
  • 3
  • 30
  • 46
0

There is an extensive example of how to do this on javapractices.com. See in particular the Money class, which is meant to make monetary calculations simpler than using BigDecimal directly.

The design of this Money class is intended to make expressions more natural. For example:

if ( amount.lt(hundred) ) {
 cost = amount.times(price); 
}

The WEB4J tool has a similar class, called Decimal, which is a bit more polished than the Money class.

Bhesh Gurung
  • 48,464
  • 20
  • 87
  • 139
John O
  • 1,585
  • 11
  • 21
0
NumberFormat.getNumberInstance(java.util.Locale.US).format(num);
Heisenberg
  • 5,026
  • 2
  • 26
  • 37
0

I would be radical. No BigDecimal.

Here is a great article https://lemnik.wordpress.com/2011/03/25/bigdecimal-and-your-money/

Ideas from here.

import java.math.BigDecimal;

public class Main {

    public static void main(String[] args) {
        testConstructors();
        testEqualsAndCompare();
        testArithmetic();
    }

    private static void testEqualsAndCompare() {
        final BigDecimal zero = new BigDecimal("0.0");
        final BigDecimal zerozero = new BigDecimal("0.00");

        boolean zerosAreEqual = zero.equals(zerozero);
        boolean zerosAreEqual2 = zerozero.equals(zero);

        System.out.println("zerosAreEqual: " + zerosAreEqual + " " + zerosAreEqual2);

        int zerosCompare = zero.compareTo(zerozero);
        int zerosCompare2 = zerozero.compareTo(zero);
        System.out.println("zerosCompare: " + zerosCompare + " " + zerosCompare2);
    }

    private static void testArithmetic() {
        try {
            BigDecimal value = new BigDecimal(1);
            value = value.divide(new BigDecimal(3));
            System.out.println(value);
        } catch (ArithmeticException e) {
            System.out.println("Failed to devide. " + e.getMessage());
        }
    }

    private static void testConstructors() {
        double doubleValue = 35.7;
        BigDecimal fromDouble = new BigDecimal(doubleValue);
        BigDecimal fromString = new BigDecimal("35.7");

        boolean decimalsEqual = fromDouble.equals(fromString);
        boolean decimalsEqual2 = fromString.equals(fromDouble);

        System.out.println("From double: " + fromDouble);
        System.out.println("decimalsEqual: " + decimalsEqual + " " + decimalsEqual2);
    }
}

It prints

From double: 35.7000000000000028421709430404007434844970703125
decimalsEqual: false false
zerosAreEqual: false false
zerosCompare: 0 0
Failed to devide. Non-terminating decimal expansion; no exact representable decimal result.

How about storing BigDecimal into a database? Hell, it also stores as a double value??? At least, if I use mongoDb without any advanced configuration it will store BigDecimal.TEN as 1E1.

Possible solutions?

I came with one - use String to store BigDecimal in Java as a String into the database. You have validation, for example @NotNull, @Min(10), etc... Then you can use a trigger on update or save to check if current string is a number you need. There are no triggers for mongo though. Is there a built-in way for Mongodb trigger function calls?

There is one drawback I am having fun around - BigDecimal as String in Swagger defenition

I need to generate swagger, so our front-end team understands that I pass them a number presented as a String. DateTime for example presented as a String.

There is another cool solution I read in the article above... Use long to store precise numbers.

A standard long value can store the current value of the Unites States national debt (as cents, not dollars) 6477 times without any overflow. Whats more: it’s an integer type, not a floating point. This makes it easier and accurate to work with, and a guaranteed behavior.


Update

https://stackoverflow.com/a/27978223/4587961

Maybe in the future MongoDb will add support for BigDecimal. https://jira.mongodb.org/browse/SERVER-1393 3.3.8 seems to have this done.

It is an example of the second approach. Use scaling. http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigdecimals-with-morphia-for-mongodb.html

Yan Khonski
  • 9,178
  • 13
  • 52
  • 88
  • Maybe a lot of the BigDecimal problems mentioned would go away if you use it with an MathContext with precision set to 2. – Salix alba Apr 15 '21 at 14:00