В статье рассматриваются особенности типов данных для хранения вещественных чисел в PostgreSQL.

Типы данных PostgreSQL для работы с вещественными числами:

1) float4, синоним real, синоним float(1..24)

2) float8, синоним float, синоним double precision, синоним float(25..53)

3) numeric, синоним decimal (десятичные числа). Диапазон для этого типа значительный: 131072 цифр до точки и 16383 цифр после точки. Но если при определении типа указать numeric(точность, масштаб), то максимальные значения точности и масштаба 1000. numeric можно объявить с отрицательным масштабом: значения могут округляться до десятков, сотен, тысяч.

Во всех этих типах данных кроме чисел и null могут храниться значения Infinity, -Infinity, NaN.

Поля типов float4 и float8 имеют фиксированную ширину, а такие типы данных не могут вытесняться в TOSAT-таблицу. Поля переменной ширины (numeric) могут. Если длина строки превышает примерно 2000 байт, то часть полей будет вытеснена в TOAST.

При вычислениях нужно учитывать правила округления значений и точность вычислений (approx или exact). float4 обеспечивает точность 6 разрядов (значащих чисел в десятичной системе счисления), float8 обеспечивает точность 15 разрядов. Последний разряд округляется:

select 12345678901234567890123456789.1234567890123456789::float4::numeric;
            numeric
-------------------------------
       12345700000000000000000000000
(1 row)
select 12345678901234567890123456789.1234567890123456789::float8::numeric;
            numeric
-------------------------------
       12345678901234600000000000000
(1 row)

На картинке красным цветом выделены шестой и пятнадцатый разряды, которые были округлены. В том же первом примере на картике видно, что разряды больше шестого и пятнадцатого были заменены нулями, что значит что точность не сохраняется.

Недостаток типов float4 и float8 данных в том, что добавление к большому числу маленького числа эквивалентно добавлению нуля:

select (12345678901234567890123456789.1234567890123456789::float8 + 123456789::float8)::numeric;
            numeric
-------------------------------
12345678901234600000000000000
(1 row)

В примере добавление значения 123456789::float8 эквивалентнно добавлению нуля.

При добавлении к numeric точность сохраняется и разряды не теряются:

select 1234567890123456789.123456789::numeric + 0.00000000000000000000123456789::numeric as numeric;
                      numeric                      
---------------------------------------------------
 1234567890123456789.12345678900000000000123456789
(1 row)

Использование float может привести к плохо диагностируемым ошибкам. Например, столбец хранит дальность полёта самолёта, при тестировании на маленькие расстояния самолёт приземляется с точностью до миллиметра, а при полёте на большие расстояния с точностью до километра. Сравнения значений могут быть проблематичны:

select 1.1::numeric::float8, 
       1.1::numeric::float4, 
       1.1::numeric::float8-1.1::numeric::float4 compare;
 float8 | float4 |         compare         
--------+--------+-------------------------
    1.1 |    1.1 | -2.3841857821338408e-08
(1 row)

При округлении float8 учитывается шестнадцатый разряд:

округление float8
округление float8

Параметр конфигурации extra_float_digits

Параметром extra_float_digits можно уменьшить число цифр в текстовом представлении чисел float8, float4 и геометрических типов. Диапазон значений от -15 до 3 включительно

значения 1,2,3 эквивалентны. Параметр влияет только на отображение, на вычисления и приведения к типу numeric не влияет

Влияет на текстовое представление float8, float4 и геометрических типов. Значение по умолчанию 1. Значения параметра extra_float_digits  1,2,3 эквивалентны:

show extra_float_digits;
1
select 1234567890.123456789::float8, 1.123456789::float4;
      float8               |      float4       
---------------------------+--------------------
       1234567890.1234567  |      1.1234568
(1 row)
set extra_float_digits = 3;
select 1234567890.123456789::float8, 1.123456789::float4;
      float8               |      float4       
---------------------------+--------------------
       1234567890.1234567  |      1.1234568
(1 row)

Значение ноль и отрицательные значения убирают из вывода разряды с округлением:

set extra_float_digits = 0;
select 1234567890.123456789::float8, 1.123456789::float4;
      float8               |      float4       
---------------------------+--------------------
       1234567890.12346    |      1.12346
(1 row)
set extra_float_digits = -1;
select 1234567890.123456789::float8, 1.123456789::float4;
      float8               |      float4       
---------------------------+--------------------
       1234567890.1235     |      1.1235
(1 row)
set extra_float_digits = -2;
select 1234567890.123456789::float8, 1.123456789::float4;
      float8               |      float4       
---------------------------+--------------------
       1234567890.123      |      1.123
(1 row)
set extra_float_digits = -5;
select 1234567890.123456789::float8, 1.123456789::float4;
      float8               |      float4       
---------------------------+--------------------
       1234567890          |      1
(1 row)

Параметр конфигурации extra_float_digits влияет только на представление (отображение, вывод). На вычисления и приведения к типу numeric не влияет:

select 1234567890.123456789::float8::numeric, 1.123456789::float4::numeric;
      float8               |      float4       
---------------------------+--------------------
       1234567890.12346    |               1.12346
(1 row)

Округление может убрать много разрядов:

reset extra_float_digits; 
select 234567890.199999989::float8::numeric, 1.19999999123::float4::numeric;
      float8               |      float4       
---------------------------+--------------------
       234567890.2         |              1.2
(1 row)

Хранение вещественных чисел

Тип numeric имеет переменную длину и для небольших чисел хранит данные компактнее, чем float8 : точность 15 "десятичных разрядов", цифр в десятичном виде, то есть цифр до и после точки в десятичном виде, то есть если разрядов не хватает, то убираются десятичные и потом целочисленные цифры и заменяются нулями.

Цветом выделены поля трёх типов
Цветом выделены поля трёх типов

Функция проверки размера поля также показывает занимаемое полями место:

select pg_column_size(c1), pg_column_size(c2), pg_column_size(c3) from t5;
 pg_column_size | pg_column_size | pg_column_size 
----------------+----------------+----------------
              8 |              4 |              5
              8 |              4 |             13
              8 |              4 |              5
              8 |              4 |              7

Все три типа данных поддерживают значения Infinity NaN -Infinity. Пример:

truncate t5;
insert into t5 values ('Infinity', 'Infinity', 'Infinity');
insert into t5 values ('NaN', 'NaN', 'NaN');
select * from t5;
    c1    |    c2    |    c3    
----------+----------+----------
 Infinity | Infinity | Infinity
      NaN |      NaN |      NaN
(2 rows)
select lp_off, lp_len, t_hoff, t_data from heap_page_items(get_raw_page('t5','main',0)) order by lp_off;
 lp_off | lp_len | t_hoff |              t_data              
--------+--------+--------+----------------------------------
   8096 |     39 |     24 | \x000000000000f87f0000c07f0700c0
   8136 |     39 |     24 | \x000000000000f07f0000807f0700d0
(2 rows)

Разрядность результата деления numeric

Разрядность результата деления двух чисел типа numeric:

1) не менее 16 значащих цифр, то есть не хуже, чем float8

2) не меньше, чем разрядность любого из входных параметров.

Для вычисления квадратного корня и других операций с потерей точности действует аналогичное правило. Для операторов сложения, вычитания, умножения и других потери точности нет.

Пример:

insert into t5 values (1,1, 1.000000000000000000000000000000000001/3); 
select lp, lp_off, lp_len, t_hoff, t_data from heap_page_items(get_raw_page('t5','main',0)) 
order by lp desc limit 1;
 lp | lp_off | lp_len | t_hoff |                  t_data 
----+--------+--------+--------+-----------------------------------
  5 |   7912 |     57 |     24 | \x000000000000f03f0000803f2b7f92050d050d050d050d050d050d050d050d060d 
(5 rows)
select * from t5 order by ctid desc limit 1;
 c1 | c2 |                   c3                   
----+----+---------------------------------------
  1 |  1 | 0.333333333333333333333333333333333334 
разрядность деления чисел в PostgteSQL
разрядность деления чисел в PostgteSQL

Типы данных Oracle Database NUMBER, FLOAT, DOUBLE соответствуют типу numeric в PostgreSQL, что стоит учитывать при миграции с Oracle на Postgres. Типы данных, соответствующие float4 и float8, появились только в версиии Oracle Database 10g и называются BINARY_FLOAT и BINARY_DOUBLE. Опции, в том числе, Oracle Spatial Data Option (обработка пространственных данных, геометрия, «инженерные расчёты») используют NUMBER. В коммерческих приложениях важна точность вычислений.

Заключение

 В статье рассмотрены особенности работы с типами данных в PostgreSQL. Для обработки десятичных чисел можно использовать numeric, а не float4 и float8, которые используют двоичные вычисления (binary). Если в столбцах таблиц хранятся небольшие числа, то поля numeric используют меньше места, чем типы фиксированной длины. Точность вычислений при использовании numeric не меньше 16 разрядов, то есть не хуже, чем у float8. Точности float4 (real) может быть недостаточно: всего 6 десятичных разрядов. Все три типа данных поддерживают значения Infinity NaN -Infinity.

Комментарии (5)


  1. acDev
    03.07.2025 04:55

    Недостаток типов float4 и float8 данных в том, что добавление к большому числу маленького числа эквивалентно добавлению нуля

    Что за ересь то? У вас в примере изначально указано число, которое не может хранится во float8 без потери точности. Сложение тут вообще не причём.

    Эта статья уровня школьных рефератов. Да ещё 4 плюсика уже есть... за что?


    1. OlegIct Автор
      03.07.2025 04:55

      причём и оба числа хранятся. Добавил в статью более наглядный пример:

      select 1.1::numeric::float8, 
             1.1::numeric::float4, 
             1.1::numeric::float8-1.1::numeric::float4 compare;
       float8 | float4 |         compare         
      --------+--------+-------------------------
          1.1 |    1.1 | -2.3841857821338408e-08


  1. AntonLarinLive
    03.07.2025 04:55

    Мда, очередная графомания от неофитов. Вы даже не упомянули основополагающий стандарт IEEE 754, где расписаны детали хранения и выполнения операций над числами с плавающей запятой. Стесняюсь спросить, читали ли вы его? Если читали, то почему не упомянули?

    Для обработки десятичных чисел можно использовать numeric, а не float4 и float8. Преимущество numeric в том, что обычно, в столбцах хранятся небольшие числа и поля numeric используют меньше места, чем типы фиксированной длины.

    Абсолютно неверная логика. Выбор между numeric и float базируется в первую очередь не размером хранения, а предметной областью. Если вы работает с деньгами или подобными данными, где требуется гарантированная точность N знаков после запятой и недопустимы неконтролируемые потери при выполнении арифметических операций, то вам в 99.9% нужен numeric. В остальных случаях: инженерные расчёты, показания датчиков и т.п. - можно использовать float. Но и выбор между float4 и float8 в большинстве случаев - это не выбор по занимаемому месту, а выбор допустимой относительной погрешности.


    1. OlegIct Автор
      03.07.2025 04:55

      нет смысла упоминать. Добавил в конец абзац про типы Oracle: в бизнес-приложениях float не используется - неточности недопустимы. По большей части, статья опубликована для исследования предпочтений. Последний месяц качественные статьи в хабе PostgreSQL получают в 2,5 раза меньше зеленых стрелочек, вероятно, отпуска. Статья на тему типов данных набрала неестественно много стрелочек. Я предположил - неужели типы данных так интересны, тема банальна, может хаб Программирование дало такой эффект. Взял часть из темы - про вещественные числа. Ровно через сутки в тех же хабах разместил эту статью, которая немного более технична и качественна. Статья набрала столько же стрелочек и закладок, как примерно аналогичная по полезности про fillfactor. Оказалось, что высокий уровень стрелочек действительно неестественный. С качеством статей лучше коррелирует число закладок деленное на число просмотров. Спрошу не стесняясь :) какова ваша оценка двух статей про типы данных и fillfactor?


    1. Q3_Results
      03.07.2025 04:55

      Инженерные расчеты влияют на деньги, поэтому и там надо использовать DECIMAL