96
\$\begingroup\$

In a simple business simulation game (built in Java + Slick2D), should a player's current amount of money be stored as a float or an int, or something else?

In my use-case, most transactions will use cents ($0.50, $1.20, etc.), and simple interest rate calculations will be involved.

I've seen people saying you should never use float for currency, as well as people saying you should never use int for currency. I feel like I should use int and round any necessary percentage calculations. What should I use?

\$\endgroup\$
4
  • 2
    \$\begingroup\$ This question was discussed on StackOverflow. Seems like BigDecimal is probably the way to go. stackoverflow.com/questions/285680/… \$\endgroup\$
    – Ade Miller
    Commented Dec 21, 2012 at 19:17
  • 2
    \$\begingroup\$ Does Java not have a Currency type like Delphi's, which uses scaled fixed-point math to give you decimal math without the precision problems inherent to floating-point? \$\endgroup\$ Commented Dec 22, 2012 at 0:57
  • 1
    \$\begingroup\$ It's game. Accounting isn't going to lynch you for a rounded off penny and thus the normal reasons against using floating point for currency don't matter. \$\endgroup\$ Commented Dec 22, 2012 at 17:33
  • \$\begingroup\$ @MasonWheeler: Java has BigDecimal for these kind of problems. \$\endgroup\$ Commented Dec 22, 2012 at 23:33

12 Answers 12

90
\$\begingroup\$

You can use int, and consider everything in cents. $1.20 is just 120 cents. At display, you put the decimal in where it belongs.

Interest calculations would just be either truncated or rounded up. So

newAmt = round( 120 cents * 1.04 ) = round( 124.8 ) = 125 cents

This way you don't have messy decimals always sticking around. You could get rich by adding the unaccounted for money (due to round-downs) into your own bank account

\$\endgroup\$
5
  • 3
    \$\begingroup\$ If you use round there is no real reason to cast down to int, is there? \$\endgroup\$ Commented Dec 21, 2012 at 18:45
  • 20
    \$\begingroup\$ +1. Floating point numbers screw up equality comparisons, they are harder to format properly, and introduce funny rounding errors over time. It's better to do all that stuff yourself so you don't get complaints about it later. It really isn't a lot of work. \$\endgroup\$ Commented Dec 22, 2012 at 0:18
  • \$\begingroup\$ +1. Sounds like a good way to go for my game. I'll deal with rounding the ints myself and stay away from all the float problems. \$\endgroup\$ Commented Dec 22, 2012 at 16:14
  • \$\begingroup\$ Just a disclaimer: I changed the correct answer to this one because it ended up being what I used. It's much simpler and in the context of such a simple game, the unaccounted decimals don't really matter. \$\endgroup\$ Commented Apr 8, 2013 at 11:32
  • 3
    \$\begingroup\$ However, use Basis Points not Cents (where 100 Basis Points = 1 cent) with a factor of 10,000 instead of 100. This is required by GAAP for all Financial, Banking or Accounting applications. \$\endgroup\$ Commented Apr 8, 2013 at 22:57
66
\$\begingroup\$

Okay, I'll jump in.

My advice: it's a game. Take it easy and use double.

Here is my rationale:

  • float does have a precision issue that appears when adding units to millions, so though it might be benign, I would avoid that type. double only starts getting problems around the quintillons (a billion billions).
  • Since you are going to have interest rates, you will need theoretical infinite precision anyway: with 4% interest rates, $100 will become $104, then $108.16, then $112.4864, etc. This makes int and long useless because you don't know where to stop with the decimals.
  • BigDecimal will give you arbitrary precision but will become painfully slow, unless you clamp the precision at some time. What are the rounding rules? How do you choose where to stop? Is it worth having more precision bits than double? I believe not.

The reason fixed point arithmetic is used in financial applications is because they are deterministic. The rounding rules are perfectly defined, sometimes by the law, and must be strictly applied, but rounding still happens at some point. Any argument in favour of a given type based on precision is likely bogus. All types have precision issues with the kind of computations you are going to do.

Practical examples

I see quite a few comments claiming things about rounding or precision that I disagree with. Here are a few additional examples to illustrate what I mean.

Storing: if your base unit is the cent, you'll probably want to round to the nearest cent when storing values:

void SetValue(double v) { m_value = round(v * 100.0) / 100.0; }

You will get absolutely no rounding problems when adopting this method that you wouldn't also have with an integer type.

Retrieving: all computations can be done directly on the double values, with no conversions:

double value = data.GetValue();
value = value / 3.0 * 12.0;
[...]
data.SetValue(value);

Note that the above code does not work if you replace double with int64_t: there will be an implicit conversion to double, then truncation to int64_t, with a possible loss of information.data.GetValue()

Comparing: comparisons are the one thing to get right with floating-point types. I suggest using a comparison method such as this one:

/* Are values equal to a tenth of a cent? */
bool AreCurrencyValuesEqual(double a, double b) { return abs(a - b) < 0.001; }

Rounding fairness

Suppose you have $9.99 in the account with 4% interest. How much should the player earn?With integer rounding you get $0.03; with floating-point rounding you get $0.04. I believe the latter is more fair.

\$\endgroup\$
19
  • 4
    \$\begingroup\$ +1. The only clarification I'd like to add, is just as the financial applications do conform to specific predefined requirements, so does the rounding algorithm. If the game is a casino one, it conforms to similar standards and then double is not an option. \$\endgroup\$ Commented Dec 21, 2012 at 15:07
  • 2
    \$\begingroup\$ You still have to think about rounding rules to format the UI. Because there's always a fraction of players who will try to treat a game as a spreadsheet if you don't want to deal with people jumping up and down screaming when they find your backend isn't using the same precision as your UI resulting in the 'wrong' results being displayed your best option to keep them quiet is to use the same internal and external representation which means using a fixed point format. Unless performance becomes an issue BigDecimal is the best choice for doing this. \$\endgroup\$ Commented Dec 21, 2012 at 19:22
  • 11
    \$\begingroup\$ I disagree with this answer. Rounding issues do appear, and if they go down, and if you're not using any additional rounding, $1.00 may suddenly become $0.99. But most importantly, arbitrary precision decimals being slow is (almost) completely irrelevant. We're almost in 2013, and unless you're doing large factorizations, trig stuff or logarithms over several millions of numbers with thousands of digits, you will never even notice a dent in performance. Anyways, simple is always best, so my recommendation is to store all numbers as cents. That way they're all int_64ts. \$\endgroup\$ Commented Dec 22, 2012 at 3:13
  • 8
    \$\begingroup\$ @Sam last time I checked my bank account, my balance did not end in .0000000000000001. Real currency systems must work with non-divisible units, and that's correctly represented with integers. If you must do currency divisions correctly (which are actually not that common at all in the real financial world), you have to do them so money is not lost. For example 10/3 = 3+3+4 or 10/3 = 3+3+3 (+1). For all other operations integers work perfectly, unlike floating point which can get rounding problems in any operation. \$\endgroup\$ Commented Dec 22, 2012 at 15:35
  • 2
    \$\begingroup\$ @SamHocevar 999*4/100. Where not stipulated by regulation assume all rounding will be done in the banks favor. \$\endgroup\$ Commented Dec 22, 2012 at 17:00
21
\$\begingroup\$

Floating point types in Java (float, double) are not good representation for currencies because of one main reason - there is a machine error in rounding. Even if a simple calculation returns a whole number - like 12.0/2 (6.0), the floating point might wrongly round it (due tho the specific representation of these types in memory) as 6.0000000000001 or 5.999999999999998 or similar. This is a result of the specific machine rounding that occurs in the processor and it is unique to the computer that calculated it. Usually, it is rarely an issue to operate with these values, since the error is quite negligent, but its a pain to display that to the user.

A possible solution to this would be to use a custom implementations of floating point data type, like BigDecimal. It supports better calculation mechanisms which at least isolate the rounding errors not to be machine specific, but are slower in terms of performance.

If you need high productivity, you'd better stick to the simple types. In case you operate with important financial data, and each cent is important (like a Forex application, or some casino game) then I'd recommend you to use Long or long. Long would allow you to handle large amounts and good precision. Just assume you need, lets say, 4 digits after the decimal point, all you need is to multiply the amount by 10000. Having experience in developing on-line casino games, I've seen Long to be often used to represent the money in cents. In Forex applications, the precision is more important, so you'll need a greater multiplier - still, whole numbers are free of machine rounding issues (of course manual rounding like in 3/2 you should handle yourself).

An acceptable option would be to use the standard floating point types - Float and Double, if performance is more important than accuracy to hundredths of the cent. Then, on your display logic, all you need is to use a predefined formatting, so that the ugliness of potential machine rounding does not get to the user.

\$\endgroup\$
8
  • 4
    \$\begingroup\$ +1 A detail: "Just assume you need, lets say, 4 digits after the decimal point, all you need is to multiply the amount by 1000." -> This is called fixed point. \$\endgroup\$ Commented Dec 21, 2012 at 14:30
  • 1
    \$\begingroup\$ @Sam, the numbers were provided for the example stake. Still, I've personally seen weird results with such simple numbers, but it is not a frequent scenario. When floating points come to play, this is probably more likely to occur, but more unlikely to spot \$\endgroup\$ Commented Dec 21, 2012 at 14:39
  • 2
    \$\begingroup\$ @Ivaylo Slavov, I agree with you. In my answer i just made my opinion. I love to discuss and have the willingness to take the right thing. Also thanks for your opinion. :) \$\endgroup\$ Commented Dec 21, 2012 at 14:56
  • 1
    \$\begingroup\$ @SamHocevar: That reasoning doesn't work in all cases. See: ideone.com/nI2ZOK \$\endgroup\$
    – Samaursa
    Commented Dec 23, 2012 at 3:58
  • 1
    \$\begingroup\$ @SamHocevar: That is not guaranteed either. Numbers within the range that are still whole numbers start accumulating error on the first division: ideone.com/AKbR7i \$\endgroup\$
    – Samaursa
    Commented Dec 23, 2012 at 12:02
17
\$\begingroup\$

For small scale game and where process speed, memory is important issue (due to precision or work with math co-processor can make painfully slow), there double is enough.

But for large scale games (for example, social games) and where process speed, memory is not limited, there BigDecimal is better. Because here,

  • int or long for monetary calculations.
  • floats and doubles cannot accurately represent most base 10 real numbers.

Resources:

From https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency

Because floats and doubles cannot accurately represent most base 10 real numbers.

This is how an IEEE-754 floating-point number works: it dedicates a bit for the sign, a few bits to store an exponent, and the rest for the actual fraction. This leads to numbers being represented in a form similar to 1.45 * 10^4; except that instead of the base being 10, it's two.

All real decimal numbers can be seen, in fact, as exact fractions of a power of ten. For instance, 10.45 really is 1045 / 10^2. And just as some fractions can't be represented exactly as a fraction of a power of ten (1 / 3 comes to mind), some of them can't be represented exactly as a fraction of a power of two, either. As a simple example, you simply cannot store 0.1 inside a floating-point variable. You'll get the nearest representable value, which is something like 0.0999999999999999996, and software will round it to 0.1 when displaying it.

However, as you perform more additions, subtractions, multiplications and divisions on inexact numbers, you'll lose more and more precision as the tiny errors add up. This makes floats and doubles inadequate for dealing with money, where perfect accuracy is required.

From Bloch, J., Effective Java, 2nd ed, Item 48:

The float and double types are particularly ill-suited for 

monetary calculations because it is impossible to represent 0.1 (or any other negative power of ten) as a float or double exactly.

For example, suppose you have $1.03 and you spend 42c. How much money do you have left?

System.out.println(1.03 - .42);

prints out 0.6100000000000001.

The right way to solve this problem is to use BigDecimal, 

int or long for monetary calculations.

Also have a look at

\$\endgroup\$
6
  • \$\begingroup\$ I only disagree with the part that long is suitable for small calculations. In my real life experience, it has proven enough to handle good precision and large amounts of money. Yes, it might be a little cumbersome to use but it beats BigDecimal in two crucial points - 1) it is faster (BigDecimal uses software rounding, which is way slower that the built-in CPU rounding used in float/double) and 2) BigDecimal is harder to persist in a database without losing precision - not all databases support such 'custom' type. Long is well known and widely supported on all major databases. \$\endgroup\$ Commented Dec 21, 2012 at 14:46
  • \$\begingroup\$ @Ivaylo Slavov, Sorry for my mistake. I have updated my answer. \$\endgroup\$ Commented Dec 21, 2012 at 14:49
  • \$\begingroup\$ No apology needed to give a different approach for the same problem :) \$\endgroup\$ Commented Dec 21, 2012 at 14:54
  • 2
    \$\begingroup\$ @Ivaylo Slavov, I agree with you. In my answer i just made my opinion. I love to discuss and have the willingness to take the right thing. Also thanks for your opinion. :) \$\endgroup\$ Commented Dec 21, 2012 at 14:57
  • \$\begingroup\$ it is the purpose of this site to clarify the issues and help the OP make the right decision, depending on the specific scenario. All we can do is help speed up the process :) \$\endgroup\$ Commented Dec 21, 2012 at 15:01
13
\$\begingroup\$

You want to store your currency in long and calculate your currency in double, at least as a backup. You want all transactions to take place as long.

The reason you want to store your currency in long is that you don't want to lose any currency.

Let's suppose you use a double, and you have no money. Someone gives you three dimes, and then takes them back.

You:       0.1+0.1+0.1-0.1-0.1-0.1 = 2.7755575615628914E-17

Well, that's not so cool. Maybe someone with $10 wants to give their fortune away by first giving you three dimes, and then giving $9.70 to someone else.

Them: 10.0-0.1-0.1-0.1-9.7 = 1.7763568394002505E-15

And then you give them the dimes back:

Them: ...+0.1+0.1+0.1 = 0.3000000000000018

This is just broken.

Now, let's use a long, and we'll keep track of tenths of cents (so 1 = $0.001). Let's give everyone on the planet one billion, one hundred and twelve million, seventy five thousand, one hundred and forty three dollars:

Us: 7000000000L*1112075143000L = 1 894 569 218 048

Um, wait, we can give everyone over a billion dollars, and only spend a little over two? Overflow is a disaster here.

So, whenever you're calculating an amount of money to transfer, use double and Math.round it to get a long. Then fix up balances (add and subtract both accounts) using long.

Your economy won't leak, and it will scale up to a quadrillion dollars.

There are more tricky issues--for example, what do you do if you make twenty payments?*--but this should get you started.

* You calculate what one payment is, round to long; then multiply by 20.0 and check that it's in range; if so, you multiply the payment by 20L to get the amount deducted from your balance. In general, all transactions must be handled as long, so you really need to sum up all the individual transactions; you can multiply as a shortcut, but you need to make sure you don't add rounding errors and that you don't overflow, which means you need to check with double before doing the real calculation with long.

\$\endgroup\$
8
\$\begingroup\$

I'd go as far as to say that any value which may be displayed to the user should almost always be an integer. Money is only the most prominent example of this. Dealing 225 damage four times to a monster with 900 HP and finding that it still has 1 HP left will subtract from the experience just as much as finding that you are an invisible fraction of a penny short of affording something.

On the more technical side I think it's worth noting that one doesn't have to revert to floats to do advanced stuff like interest. As long as you have got enough headroom in your chosen integer type a multiplication and a division will stand in for a multiplication by a decimal number, for instance to add 4%, rounded down:

number=(number*104)/100

To add 4%, rounded by standard conventions:

number=(number*104+50)/100

No float point inaccuracy here, the rounding always split exactly on the .5 mark.

Edit, the real question:

Seeing how the debate has gone I start to think that outlining what the question is all about may be more useful than a simple int/float answer. At the core of the question it is not about data types, it's about taking control of the details of a program.

Using an integer to represent a non-integer value forces the programmer to deal with the implementation details. "What precision to use?" and "What way to round?" are questions that must be answered explicitly.

A float on the other hand does not force the programmer to worry, it already does pretty much what one would expect. But since a float is not infinite precision, some rounding will take place, and that rounding is pretty unpredictable.

What happens in one use floats and want to take control of the rounding? It turns out to be almost impossible. The only way to make a float truly predictable is to use only values that can be represented in whole 2^ns. But that construction makes floats quite hard to use.

Thus the answer to the simple question is: If you want to take control, use integers, if not, use floats.

But the question that is being debated is just another form of the question: Do you want to take control?

\$\endgroup\$
6
\$\begingroup\$

Even if it's "only a game", I would use Money Pattern from Martin Fowler, backed up by a long.

Why?

Localization (L10n): Using that pattern you can easily localize your game currency. Think about the old tycoon games like "Transport Tycoon". They easily allow the player to change in-game currency (i.e From British Pound to US Dollar) to meet real world currency.

And

The long data type is a 64-bit signed two's complement integer. It has a minimum value of -9,223,372,036,854,775,808 and a maximum value of 9,223,372,036,854,775,807 (inclusive) (Java Tutorials)

That means you can store 9,000 times current M2 US Money Supply (~10,000 Billion Dollars). Giving you enough room to use any other world currency, probably, even those who had/has hyperinflation (If curious, see post-WWI German inflation, where 1 pound of bread was 3,000,000,000 marks)

Long is easy to persist, very fast and should give you enough room to do all interest calculations using only integer arithmetics, eBusiness answer gives an explanation on how to do that.

\$\endgroup\$
2
\$\begingroup\$

How much work do you want to put in to this? How important is accuracy? Do you care about tracking the fractional errors that occur with rounding and the imprecision of representing decimal numbers in a binary system?

Ultimately I tend toward spending a bit more time coding and implementing unit tests for "corner cases" and known problematic cases - so I would be thinking in terms of a modified BigInteger which encompasses arbitrarily large amounts and maintains the fractional bits with either a BigDecimal or a BigRational part (two BigIntegers, one for the denominator and another for the numerator) - and include code to keep the fractional part an actual fraction by (perhaps only periodically) adding any non-fractional part to the main BigInteger. Then I would internally keep track of everything in cents just to keep the fractional parts out of the GUI calculations.

Probably way to complex for a (simple) game - but good for a library published as open source! Just would need to figure out ways to keep the performance good when dealing with the fractional bits...

\$\endgroup\$
2
\$\begingroup\$

Certainly not float. With only 7 digits, you only have accuracy like:

12,345.67 123,456.7x

So you see, already with 10^5 dollars, you are losing track of pennies. double is usable for game purposes, not in real life because of accuracy concerns.

But long (and by this I mean 64 bit, or long long in C++) is not enough if you are going to be tracking transactions and summing them up. It's enough to maintain the net holdings of any company, but all the transactions for a year could overflow. That depends on how big your financial "world" is in the game.

\$\endgroup\$
0
\$\begingroup\$

Another solution I'll add here is to make a money class. The class would be something like (could even simply be a struct).

class Money
{
     string currencyType; //the type of currency example USD could be an enum as well
     bool isNegativeValue;
     unsigned long int wholeUnits;
     unsigned short int partialUnits;
}

This would allow you to represent the cents in this case as an integer and the whole dollars as an integer. Since you have a seperate flag for being negative, you can use unsigned integers for your values which doubles the possible amount (you could also write a number class that applies this idea to numbers to get REALLY big numbers). All you would need to do is overload math operators and you could use this like any old datatype. It takes up more memory, but it really expands the value limits.

This could further be expanded to include things like number of partial units per whole units so you could have currencies that subdivide on something other than 100 sub units, cents in this case, per dollar. You could have a 125 floopies make up one flooper for example.

Edit:

I'll expand the idea in that you could also have a look up table of sorts, that the money class can access that would provide an exchange rate for it's currency versus all other currencies. You could build this into your operator overloading functions. Therefore, if you automatically try to add 4 USD to 4 British pounds, it would automatically give you 6.6 pounds (as of writing).

\$\endgroup\$
0
\$\begingroup\$

As mentioned in one of the comments on your original question, this issue has been covered on StackExchange: https://stackoverflow.com/questions/8148684/what-is-the-best-data-type-to-use-for-money-in-java-app

Basically, floating point types should never be used to represent currencies, and like most languages Java has a more appropriate type. Oracle's own documentation on primitives suggests using BigDecimal.

\$\endgroup\$
-1
\$\begingroup\$

Use class it's the best way. You can hide implementation of your money you can switch double/long what ever you want at any time.

Example

class Money
{
    private long count;//Store here what ever you want in what type you want i suggest long or int
    //methods constructors etc.
    public String print()
    {
        return count/100.F; //convert this to string
    }
}

In your code use simply getter it is better way i think and you can store more useful methods.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .