Storing money in a decimal column - what precision and scale?
Asked Answered
K

11

218

I'm using a decimal column to store money values on a database, and today I was wondering what precision and scale to use.

Since supposedly char columns of a fixed width are more efficient, I was thinking the same could be true for decimal columns. Is it?

And what precision and scale should I use? I was thinking precision 24/8. Is that overkill, not enough or ok?


This is what I've decided to do:

  • Store the conversion rates (when applicable) in the transaction table itself, as a float
  • Store the currency in the account table
  • The transaction amount will be a DECIMAL(19,4)
  • All calculations using a conversion rate will be handled by my application so I keep control of rounding issues

I don't think a float for the conversion rate is an issue, since it's mostly for reference, and I'll be casting it to a decimal anyway.

Thank you all for your valuable input.

Karyolysis answered 22/10, 2008 at 4:8 Comment(3)
Ask yourself the question: Is it really necessary to store the data in decimal form? Cant I store the data as Cents/Pennies -> integers ?Graphology
DECIMAL(19, 4) is a popular choice check this also check here World Currency Formats to decide how many decimal places to use , hope helps.Lifesaver
This is an extremely important and common question, and the question should be reopened.Shani
J
233

If you are looking for a one-size-fits-all, I'd suggest DECIMAL(19, 4) is a popular choice (a quick Google bears this out). I think this originates from the old VBA/Access/Jet Currency data type, being the first fixed point decimal type in the language; Decimal only came in 'version 1.0' style (i.e. not fully implemented) in VB6/VBA6/Jet 4.0.

The rule of thumb for storage of fixed point decimal values is to store at least one more decimal place than you actually require to allow for rounding. One of the reasons for mapping the old Currency type in the front end to DECIMAL(19, 4) type in the back end was that Currency exhibited bankers' rounding by nature, whereas DECIMAL(p, s) rounded by truncation.

An extra decimal place in storage for DECIMAL allows a custom rounding algorithm to be implemented rather than taking the vendor's default (and bankers' rounding is alarming, to say the least, for a designer expecting all values ending in .5 to round away from zero).

Yes, DECIMAL(24, 8) sounds like overkill to me. Most currencies are quoted to four or five decimal places. I know of situations where a decimal scale of 8 (or more) is required but this is where a 'normal' monetary amount (say four decimal places) has been pro rata'd, implying the decimal precision should be reduced accordingly (also consider a floating point type in such circumstances). And no one has that much money nowadays to require a decimal precision of 24 :)

However, rather than a one-size-fits-all approach, some research may be in order. Ask your designer or domain expert about accounting rules which may be applicable: GAAP, EU, etc. I vaguely recall some EU intra-state transfers with explicit rules for rounding to five decimal places, therefore using DECIMAL(p, 6) for storage. Accountants generally seem to favour four decimal places.


PS Avoid SQL Server's MONEY data type because it has serious issues with accuracy when rounding, among other considerations such as portability etc. See Aaron Bertrand's blog.


Microsoft and language designers chose banker's rounding because hardware designers chose it [citation?]. It is enshrined in the Institute of Electrical and Electronics Engineers (IEEE) standards, for example. And hardware designers chose it because mathematicians prefer it. See Wikipedia; to paraphrase: The 1906 edition of Probability and Theory of Errors called this 'the computer's rule' ("computers" meaning humans who perform computations).

Jamey answered 22/10, 2008 at 8:26 Comment(9)
I think the problem is implicitly casting the money type to a useful type in decimal(). such that some of the standard library will work with decimal() but money will never implicitly cast to it.Stand
See this answer and this page for more on why language designers choose Banker's rounding.Palocz
onedaywhen: Banker's rounding is not due to Microsoft. @NickChammas: nor is it a language designer's invention. Microsoft and language designers basically chose it because hardware designers chose it; it is enshrined in the Institute of Electrical and Electronics Engineers (IEEE) standards, for example. And hardware designers chose it because mathematicians prefer it. See en.wikipedia.org/wiki/Rounding#History; to paraphrase: The 1906 edition of Probability and Theory of Errors called this 'the computer's rule' ("computers" meaning humans who perform computations).Virginium
@phoog: I've corrected and added your piece... but with your level of rep, you could have done this yourself! Except you missed a citation (unless the Wikipedia article is the source, in which case please edit to make this clear). Thanks.Jamey
@Jamey why is DECIMAL(19, 4) more popular than DECIMAL(19, 2)? Most world currencies are only two decimal places.Grishilde
And no one has that much money nowadays to require a decimal precision of 24 A counterexample: 1ZWL=10^25ZWD. And from Wikipedia: By late 2008, inflation had risen so high that automated teller machines for one major bank gave a "data overflow error" and stopped customers' attempt to withdraw money with so many zeros. source ;)Auld
@zypA13510: yeah, my assertion is so ten years ago! But this is my third most voted for answer and accounts for a fair chunk of change as regards my SO rep so I'm quids in :)Jamey
Yeah, this is a good answer but now it is not true anymore. If you are making software for cryptocurrency than bitcoin's satoshi is ten-millionth of a bitcoin so you need DECIMAL (19, 8). If you use Ethereum you need DECIMAL (38, 18).Isoleucine
Maybe consider storing more decimals if really need it for instance in currency exchange.Mortification
A
126

We recently implemented a system that needs to handle values in multiple currencies and convert between them, and figured out a few things the hard way.

NEVER USE FLOATING POINT NUMBERS FOR MONEY

Floating point arithmetic introduces inaccuracies that may not be noticed until they've screwed something up. All values should be stored as either integers or fixed-decimal types, and if you choose to use a fixed-decimal type then make sure you understand exactly what that type does under the hood (ie, does it internally use an integer or floating point type).

When you do need to do calculations or conversions:

  1. Convert values to floating point
  2. Calculate new value
  3. Round the number and convert it back to an integer

When converting a floating point number back to an integer in step 3, don't just cast it - use a math function to round it first. This will usually be round, though in special cases it could be floor or ceil. Know the difference and choose carefully.

Store the type of a number alongside the value

This may not be as important for you if you're only handling one currency, but it was important for us in handling multiple currencies. We used the 3-character code for a currency, such as USD, GBP, JPY, EUR, etc.

Depending on the situation, it may also be helpful to store:

  • Whether the number is before or after tax (and what the tax rate was)
  • Whether the number is the result of a conversion (and what it was converted from)

Know the accuracy bounds of the numbers you're dealing with

For real values, you want to be as precise as the smallest unit of the currency. This means you have no values smaller than a cent, a penny, a yen, a fen, etc. Don't store values with higher accuracy than that for no reason.

Internally, you may choose to deal with smaller values, in which case that's a different type of currency value. Make sure your code knows which is which and doesn't get them mixed up. Avoid using floating point values even here.


Adding all those rules together, we decided on the following rules. In running code, currencies are stored using an integer for the smallest unit.

class Currency {
   String code;       //  eg "USD"
   int value;         //  eg 2500
   boolean converted;
}

class Price {
   Currency grossValue;
   Currency netValue;
   Tax taxRate;
}

In the database, the values are stored as a string in the following format:

USD:2500

That stores the value of $25.00. We were able to do that only because the code that deals with currencies doesn't need to be within the database layer itself, so all values can be converted into memory first. Other situations will no doubt lend themselves to other solutions.


And in case I didn't make it clear earlier, don't use float!

Abert answered 22/10, 2008 at 12:47 Comment(11)
Never say never: sometimes money amounts are pro rata'd and need to add up again later. Example: dividing total dividend (relatively small) dibided by number of shares in issue (relatively small) to give net per share. Sometimes float rounds better :)Jamey
I stand by my never. The floating point spec has inaccuracies that will add up the more calculations you do. If you need to store values smaller than a cent or penny, then define the accuracy level you do need and stick to it. Don't use float. Seriously. It's a bad idea.Abert
This answer also lines up with the javascript best practices outlined by Douglas Crockford in his "Crockford on JavaScript series", where he recommends to do all currency calculations in PENNIES to avoid machine error in rounding. So if you're working with currencies in javascript it makes a lot of sense to store the value this way.Sela
@Jamey Those net per share cases, you could sum the pro-rata'd amounts and compare to the original total and devise a strategy for dealing with the remainder (when using decimal/integer types).Drus
For Mysql, I recommend storing the integer (2500) as a bigint if you intend on sorting by the amount. And don't waste your time with PHP 32bit when dealing with big integers, upgrade to 64bit or Node.JS ;)Holocaust
Further good reading on why you don't use floating point is given in Martin Fowler's book, Patterns of Enterprise Application Architecture (2002) much of this book still extremely current.Laciniate
why not use float if you are rounding up or down anyway ? you ARE using float !, how is rounding improving the accuracy here ? wouldn't it be more correct to say, never use the float value when displaying or transfering the sum, but use it for calculations ?Eucken
Because, as explained, invisible errors can accumulate if you keep those floating point values around at all. They don't look like an error, they look fine, but they're quietly getting ready to surprise you with a very bad day.Abert
I think it is worth to go through the counter argument, nicely put together in that HN comment: news.ycombinator.com/item?id=15811730, shortly: if you are not using floats, you need to create your own implementation that might be worst than that what programming language float offers.Concernment
"I used an int64 to represent currencies with a resolution of up to 10^-6... So my "genius" younger self decided that my Money class would store both an integer value and a 'scale' factor..." The commenter basically reinvented floating point calculation? The mistake made in that article is of not picking the precision correctly. I explicitly said you should use int at the scale of the smallest unit, ie the cent or penny, except briefly during calculations.Abert
Of course, some domains require you to calculate fractions of a penny (cent, yen etc), but these are generally quite contained. You need to flatten those to whole pennies before you can deal with them in the real world.Abert
M
5

When handling money in MySQL, use DECIMAL(13,2) if you know the precision of your money values or use DOUBLE if you just want a quick good-enough approximate value. So if your application needs to handle money values up to a trillion dollars (or euros or pounds), then this should work:

DECIMAL(13, 2)

Or, if you need to comply with GAAP then use:

DECIMAL(13, 4)
Mistral answered 25/5, 2014 at 13:34 Comment(2)
Can you link to the specific portion of the GAAP guidelines instead of the contents of a 2500 page document? Thanks.Melanoma
@ReactingToAngularVues it seems the page changed. SorryMistral
L
2

The money datatype on SQL Server has four digits after the decimal.

From SQL Server 2000 Books Online:

Monetary data represents positive or negative amounts of money. In Microsoft® SQL Server™ 2000, monetary data is stored using the money and smallmoney data types. Monetary data can be stored to an accuracy of four decimal places. Use the money data type to store values in the range from -922,337,203,685,477.5808 through +922,337,203,685,477.5807 (requires 8 bytes to store a value). Use the smallmoney data type to store values in the range from -214,748.3648 through 214,748.3647 (requires 4 bytes to store a value). If a greater number of decimal places are required, use the decimal data type instead.

Langan answered 22/10, 2008 at 4:10 Comment(0)
S
2

4 decimal places would give you the accuracy to store the world's smallest currency sub-units. You can take it down further if you need micropayment (nanopayment?!) accuracy.

I too prefer DECIMAL to DBMS-specific money types, you're safer keeping that kind of logic in the application IMO. Another approach along the same lines is simply to use a [long] integer, with formatting into ¤unit.subunit for human readability (¤ = currency symbol) done at the application level.

Suttles answered 22/10, 2008 at 8:56 Comment(0)
T
1

If you were using IBM Informix Dynamic Server, you would have a MONEY type which is a minor variant on the DECIMAL or NUMERIC type. It is always a fixed-point type (whereas DECIMAL can be a floating point type). You can specify a scale from 1 to 32, and a precision from 0 to 32 (defaulting to a scale of 16 and a precision of 2). So, depending on what you need to store, you might use DECIMAL(16,2) - still big enough to hold the US Federal Deficit, to the nearest cent - or you might use a smaller range, or more decimal places.

Theriault answered 22/10, 2008 at 6:5 Comment(0)
B
1

Sometimes you will need to go to less than a cent and there are international currencies that use very large demoniations. For example, you might charge your customers 0.088 cents per transaction. In my Oracle database the columns are defined as NUMBER(20,4)

Bible answered 22/10, 2008 at 6:11 Comment(0)
A
1

If you're going to be doing any sort of arithmetic operations in the DB (multiplying out billing rates and so on), you'll probably want a lot more precision than people here are suggesting, for the same reasons that you'd never want to use anything less than a double-precision floating point value in application code.

Audiphone answered 22/10, 2008 at 11:34 Comment(4)
That was what I was thinking, but in terms of currency rates (i.e., converting Zimbawe dollars to USD). I'll perform some experiments on the databases that I'm using (psql, sqlite) to see how they handle rounding on very small decimals.Karyolysis
Also, don't floats have accuracy problems in some dbms/languages?Karyolysis
floats have accuracy problems in ALL languages.Abert
The most common recommendation these days is to use arbitrary precision (think BigDecimal), but for a long time it was double-precision (think double instead of float). Also, arbitrary precision has significant performance penalties in some cases. Testing is definitely the right approach.Audiphone
P
0

I would think that for a large part your or your client's requirements should dictate what precision and scale to use. For example, for the e-commerce website I am working on that deals with money in GBP only, I have been required to keep it to Decimal( 6, 2 ).

Pennoncel answered 22/10, 2008 at 6:16 Comment(0)
R
0

A late answer here, but I've used

DECIMAL(13,2)

which I'm right in thinking should allow upto 99,999,999,999.99.

Rosco answered 24/8, 2015 at 12:54 Comment(0)
D
0

In Venezuela, we have a country with hyper-inflation and we even started using a different currency. We have the USD, COP and VES currencies in this country. With the need to convert currencies, having 2 decimals was not enough. 1 USD is 40 VES. If we convert 100.15 VES to USD, we are left with 2.50375 USD.

A lot of operations got impacted by this, so my answer would be: if you don't intend to manage multiple currencies, it's ok to go with Decimal(x, 2) If you intend to manage several currencies, you should take into consideration the conversion operations which will require more decimals. This depends on your conversions order.

If your conversions are in the order of 10, add an extra decimal (Decimal(x, 3) and so on.

So in our case, we have a conversion of 1 USD to 1000 COP, so we added 4 extra decimals (Decimal(x, 6))

Daphinedaphna answered 26/10, 2023 at 15:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.