views:

1584

answers:

5
+1  Q: 

java - BigDecimal

I was trying to make my own class for currencies using longs, but Apparently I should use BigDecimal (and then whenever I print it just add the $ sign before it). Could someone please get me 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?

EDIT: I decided to use BigDecimals, and then use:

private static final java.text.NumberFormat moneyt =
        java.text.NumberFormat.getCurrencyInstance();
{
    money.setRoundingMode(RoundingMode.HALF_EVEN);
}

and then whenever I display the BigDecimals, to use money.format(theBigDecimal). Is this alright? Should I have the BigDecimal rounding it too? Because then it doesn't get rounded before it does another operation.. if so, could you show me how? And how should I create the BigDecimals? new BigDecimal("24.99") ?

Well, after many comments to Vineet Reynolds (thanks for keeping coming back and answering), this is what I have decided. I use BigDecimals and a NumberFormat. Here is where I create the NumberFormat instance.

private static final NumberFormat money;
static {
    money = NumberFormat.getCurrencyInstance(Locale.CANADA);
    money.setRoundingMode(RoundingMode.HALF_EVEN);
}

Here is my BigDecimal:

private final BigDecimal price;

Whenever I want to display the price, or another BigDecimal that I got through calculations of price, I use:

money.format(price)

to get the String. Whenever I want to store the price, or a calculation from price, in a database or in a field or anywhere, I use (for a field):

myCalculatedResult = price.add(new BigDecimal("34.58")).setScale(2, RoundingMode.HALF_EVEN);

.. but I'm thinking now maybe I should not have the NumberFormat round, but when I want to display do this:

System.out.print(money.format(price.setScale(2, RoundingMode.HALF_EVEN);

That way to ensure the model and things displayed in the view are the same. I don't do:

price = price.setScale(2, RoundingMode.HALF_EVEN);

Because then it would always round to 2 decimal places and wouldn't be as precise in calculations.

So its all solved now, I guess. But is there any shortcut to typing calculatedResult.setScale(2, RoundingMode.HALF_EVEN) all the time? All I can think of is static importing HALF_EVEN...

EDIT: I've changed my mind a bit, I think if I store a value, I won't round it unless I have no more operations to do with it, e.g. if it was the final total that will be charged to someone. I will only round things at the end, or whenever necessary, and I will still use NumberFormat for the currency formatting, but since I always want rounding for display, I made a static method for display:

public static String moneyFormat(BigDecimal price) {
    return money.format(price.setScale(2, RoundingMode.HALF_EVEN));
}

So values stored in variables won't be rounded, and I'll use that method to display prices.

+1  A: 

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

Diego Dias
+3  A: 

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()) );
Vineet Reynolds
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
Don't you mean DecimalFormat, not NumberFormat?
Mk12
@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
Oh, I guess NumberFormat works too. Just used getCurrencyInstance() and than setRoundMode to HALF_EVEN.
Mk12
Yes, it is getCurrencyInstance(Locale locale) that will help in localization.
Vineet Reynolds
I edited my question, could you look at it?
Mk12
I've updated the answer. Hope that helps.
Vineet Reynolds
Whoops!! Corrected a mistake regarding the usage of setScale(). The example is good to use now.
Vineet Reynolds
-1 for using the BigDecimal(double) constructor .. even though the documentation says that this constructor is 'unpredictable'
Ryan Fernandes
Agreed. Updated the example.
Vineet Reynolds
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
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
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
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
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
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
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
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
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
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
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
Because there's operations that I just want to display and others I also want to store..
Mk12
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
....with rounding operations performed wherever required (missed that part)
Vineet Reynolds
Check my question, I'm editing it to show what I decided.
Mk12
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
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
The thumb rule would be the second one that you've listed - setScale for all (actually most), and NumberFormat for display.
Vineet Reynolds
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
Yes, unless you intend to multiply or divide. In that case retain precision.
Vineet Reynolds
A: 

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.

tim_wonil
Note that for financial apps, ROUND_HALF_EVEN is the most common rounding mode since it avoids bias.
Michael Borgwardt
Good point about avoiding bias. Didn't know it worked to achieve that.
Vineet Reynolds
@Michael: Thanks for the tip. I didn't know that. I always wondered how best deal with bias/cumulative errors. Learned something new today. :)
tim_wonil
A: 

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.

John O
+1  A: 

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 BigDecimal amount;
    private Currency currency;   

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

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

    Money(BigDecimal amount, Currency currency, RoundingMode rounding) {
        this.amount = amount;
        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);
Brad