11 Numeric Types
PostgreSQL offers three families of numeric types: integers, exact decimals, and floating-point. Each has its place. Integers are fast and compact for counters and IDs. numeric (also spelled decimal) gives you exact arithmetic — essential for money. Floats (real, double precision) are fast and small but can produce tiny rounding errors and should not be used for currency.
What you’ll learn
- Integer family:
smallint,integer,bigint - Exact decimals:
numeric(p, s)and unconstrainednumeric - Floating-point:
real,double precision - How precision and scale work
- When each type is the right choice
Example
integer is your default integer type and is what 1 parses as. Switch to bigint when you might exceed two billion (rows in a large table, milliseconds since epoch, etc.). smallint is rarely worth it — the storage savings are tiny and values outside its range raise an error.
numeric(p, s) enforces up to p total digits with s of them after the decimal point. numeric(10, 2) stores values from -99,999,999.99 to 99,999,999.99. Unconstrained numeric (no p, s) stores exact decimal values within PostgreSQL’s implementation limits — slower than fixed integers, but exact. Use it for currency, lab measurements, anything where rounding errors are unacceptable.
real (4 bytes, ~6 significant digits) and double precision (8 bytes, ~15 significant digits) are IEEE 754 floats. They are fast and small, but 0.1 + 0.2 is not exactly 0.3. Use them for physical measurements, ML features, statistical aggregations where you can tolerate the imprecision.
Run the example
To run:
Common pitfalls
- Storing money in
double precision— eventually the cent-rounding catches you. Usenumeric(p, 2)(ornumericwithout bounds) and let the database handle exact arithmetic. - Choosing
integerfor an ID column on a large table — you can hit the 2.1-billion limit. Usebigintby default for primary keys you expect to grow. - Casting strings with non-numeric characters:
'$5'::numericerrors. Strip first, then cast. - Dividing integers and being surprised:
7 / 2is3, not3.5. Cast:7::numeric / 2.
pg_typeof(expression) returns the inferred type — useful when you are uncertain. For example, SELECT pg_typeof(1 / 2); returns integer, while SELECT pg_typeof(1.0 / 2); returns numeric.
Create a small table with an integer id and a numeric(10, 2) price column, then insert and inspect: CREATE TEMP TABLE p(id integer, price numeric(10,2)); INSERT INTO p VALUES (1, 9.99), (2, 1234.5); SELECT * FROM p;. Then watch what happens with SELECT 0.1::real + 0.2::real;.
Downloads
SQL file: numeric-types.sql · All lesson files · Setup guide