22

I'm writing a Java program that mines currency exchange data. The data can have multiple digits in the decimal such as "0.973047". After some research I was able to find out BigDecimal is the right data type for Java, but which data type should I be using for PostgreSQL?

bluish
  • 26,356
  • 27
  • 122
  • 180
Arya
  • 8,473
  • 27
  • 105
  • 175

3 Answers3

39

NUMERIC/DECIMAL

As Joachim Isaksson said, you want to use NUMERIC/DECIMAL type, as an arbitrary precision type.

Two important points about NUMERIC/DECIMAL:

  • Read the doc carefully to learn that you should specify the scale to avoid the default scale of 0, meaning integer values where the decimal fraction gets lopped off. While this is one of the places where Postgres strays from standard SQL (giving you any scale up to the implementation limit). So failing to specify the scale is a poor choice.
  • The SQL types NUMERIC & DECIMAL are close but not identical according to the SQL Standard. In SQL:92, your specified precision for NUMERIC is respected, whereas for DECIMAL the database server is allowed to add additional precision beyond what you specified. Here again Postgres strays a bit from the standard, with both NUMERIC & DECIMAL documented as equivalent.

Terms:

  • Precision is total number of digits in a number.
  • Scale is the number of digits to the right of the decimal point (the decimal fraction).
  • ( Precision - Scale ) = Number of digits to the left of decimal point (integer portion).

Be clear on your project's specs for precision and scale:

  • Big
    The precision must be big enough to handle larger numbers that might be needed in the future. Meaning… Perhaps your app today works in amounts of thousands of USD but in the future must perform roll-up reports that end up in the millions.
  • Small
    For some accounting purposes, you may need to store a fraction of a the smallest currency amount. Meaning… More than 3 or 4 decimal places rather than the 2 needed for a penny in USD.

Avoid MONEY type

Postgres offers a MONEY type as well. That may sound right, but probably not best for most purposes. One downside is that with MONEY the scale is set by a database-wide configuration setting based on locale. So that setting can vary dangerously easily when you switch servers or make other changes. Furthermore, you cannot control that setting for specific columns, while you can set the scale on each column of NUMERIC type. Lastly, MONEY is not standard SQL as shown in this list of standard SQL data types. Postgres includes MONEY for the convenience of folks porting data from other database systems.

Move the Decimal Point

Another alternative employed by some is moving the decimal point, and just store in large integer data type.

For example, If storing USD dollars to the penny, multiple any given fractional number by 100, cast to an integer type, and proceed. For example, $123.45 becomes the integer 12,345.

The benefit to this approach is faster execution times. Operations such as sum are very fast when performed on integers. Another benefit to integers is less memory usage.

I find this approach annoying, confusing, and risky. Annoying because computers should be working for us, not against us. Risky because some programmer or user may neglect to multiply/divide to convert back to fractional number, giving incorrect results. If working in a system without good support for accurate fractional numbers, this approach might be an acceptable workaround.

I don't see any advantage to moving the decimal point when we have DECIMAL/NUMERIC in SQL and BigDecimal in Java.

Rounding & NaN

In your app’s programming, as well as any calculations made on the Postgres server-side, be very careful and aware of rounding and truncation in the decimal fraction. And test for inadvertent NaNs popping up.

In both sides, app and Postgres, always avoid floating point data types for money work. Floating point is designed for performance speed, but at the cost of accuracy. Calculations may result in seemingly crazy extra digits in the decimal fraction. Not good for financial/money or other purposes where accuracy matters.

BigDecimal

Yes, in Java, you want BigDecimal as your arbitrary precision type. BigDecimal is slower and uses more memory, but will accurately store your money amounts. SQL NUMERIC/DECIMAL should map to BigDecimal as discussed here and on StackOverflow.

BigDecimal is one of the best things about Java. I don’t know of any other platform with a similar class, especially one so well-implemented and well-honed with major enhancements and fixes made over the years.

Using BigDecimal is definitely slower than using Java’s floating-point types, float & double. But in real-world apps I doubt your money calculations are going to be any bottleneck. And besides, which do you or your customers want: the fastest money calculations, or accurate money calculations?

I have always thought of BigDecimal as the biggest sleeper feature in Java, the most important advantage to using the Java platform over so many other platforms lacking such sophisticated support for fractional numbers.

Similar question: Best Data Type For Currency

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Another problem with using `Money`, if it's set based off the server's locale, is it makes dealing with multiple currencies difficult. Or, if the server clock has been set to UTC/GMT.... – Clockwork-Muse Aug 11 '13 at 23:02
  • One downside to using `numeric` versus `integer` (and "moving the decimal point") is mentioned in [the PostgreSQL documentation (version 9.1)](http://www.postgresql.org/docs/9.1/interactive/datatype-numeric.html#DATATYPE-NUMERIC-TABLE) – "arithmetic on numeric values is very slow compared to the integer types, or to the floating-point types". – Kenny Evitt Mar 29 '14 at 22:13
  • 4
    @KennyEvitt Yes, that is the whole point to floating-point (see what I did there?)… Speed. Floating-point trades away accuracy to get faster performance. With modern computers doing a few *billion* calculations per second though, you should think twice before prematurely optimizing by hacking your data types. Use a proper fit in your data types until you know for sure (measurably, empirically) that you have a problem. – Basil Bourque Dec 23 '14 at 08:22
  • Hi @basil-bourque. You say, "Read the doc carefully to learn that you should specify the scale to avoid the default scale of 0." It looks like scale is 0 only when precision is specified without scale. If neither are specified it "creates a column in which numeric values of any precision and scale can be stored, up to the implementation limit on precision. A column of this kind will not coerce input values to any particular scale." That seems like very developer-friendly behavior to me--it seems like a good choice. It might be worth adjusting your answer to reflect that. – Mark Doliner Jan 07 '20 at 18:40
5

To get as good (and exact) precision as possible, you can use NUMERIC (or its alias DECIMAL), which has high precision and allows you to decide the precision you require;

NUMERIC

User-specified precision, exact up to 131072 digits before the decimal point; up to 16383 digits after the decimal point

Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294
  • Good answer, except that DECIMAL is not an alias for NUMERIC. While very similar, the SQL spec allows a database implementation to store data with more precision than you specified with a DECIMAL column. With a NUMERIC column, the spec requires the database implementation to respect your specified precision. – Basil Bourque Mar 30 '14 at 18:49
  • Further note: SQL spec requires both data types to respect the scale you specify. And to see the spec for yourself, see draft of SQL:92 spec, section 6.1 rules 17 and 18. – Basil Bourque Mar 30 '14 at 18:58
  • In the Postgres implementation: `The types decimal and numeric are equivalent. Both types are part of the SQL standard.` as stated [here](http://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL) – Genma Nov 30 '15 at 11:57
  • 1
    @Genma As I said and showed, the types are *not* equivalent in the SQL standard and in other databases. This is one of the few places that Postgres diverges from the Standard and not in a good way. So treating these two types as equivalent is unwise. See [my Answer](http://stackoverflow.com/a/18170030/642706) for the details. – Basil Bourque May 23 '16 at 22:00
2

Generally, money shouldn't be stored as floating-point. The best approach is usually to store the amount of money as an integer of the smallest allowable size (for example, one U.S. cent) and format it for input and display. This is essentially what a fixed-precision DECIMAL column does in SQL, but if you transfer it back into Java, you still run the risk of losing precision (e.g., what happens if you split exactly half of the last allowable digit)?

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152