Вопрос или проблема
Выполнение математической операции с real
операндом и numeric
операндом в PostgreSQL приводит к значению типа double precision
, и это меня удивляет. Почему результат не является real
?
Я подтвердил это поведение на PostgreSQL 13 и 16.
SELECT
pg_typeof(0::numeric + 0::numeric), -- numeric
pg_typeof(0::real + 0::real), -- real
pg_typeof(0::numeric + 0::real), -- double precision??
pg_typeof(0::real + 0::numeric); -- double precision??
Я прочитал значительные части документации PostgreSQL, наиболее актуально: 10.2. Преобразование типов: Операторы
И я запросил pg_catalog.pg_cast
(или psql
‘s \dC
), чтобы понять, какие соответствующие неявные преобразования существуют:
> SET search_path="pg_catalog";
> SELECT format_type(castsource, NULL) AS source,
format_type(casttarget, NULL) AS target
FROM pg_cast
WHERE castcontext="i" -- т.е. преобразования разрешены неявно
AND ARRAY[format_type(castsource, NULL), format_type(casttarget, NULL)]
<@ '{numeric,real,double precision}'::text[];
Результат:
source | target
---------+------------------
real | double precision
numeric | real
numeric | double precision
numeric | numeric
(4 rows)
Таким образом, как я понимаю, для 0::numeric + 0::real
, PostgreSQL должен (цитаты все из 10.2. Преобразование типов: Операторы):
- Выбрать операторы, которые будут рассматриваться из системного каталога
pg_operator
. Если было использовано имя оператора без указания схемы (что обычно), рассматриваются операторы с соответствующим именем и количеством аргументов, которые видны в текущем поисковом пути.
Я ожидаю, что начнется со всех операторов +
– каждый из них принимает пару одних и тех же числовых типов (real + real
, integer + integer
и т.д.)
- Проверить наличие оператора, принимающего точно данные типы аргументов.
На шаге 2 нет операторов, которые точно соответствуют real
и numeric
, поэтому я ожидаю, что здесь ничего не произойдет.
3a. Отклонить кандидатные операторы, для которых типы ввода не совпадают и не могут быть преобразованы (с использованием неявного преобразования) для совпадения.
Я ожидаю, что это должно отклонить большую часть кандидатов. Должны остаться ровно два: один для real
и один для double precision
, который, очевидно, в итоге используется. (Обратите внимание, что real
не может быть неявно преобразован в numeric
, поэтому оператор для numeric
не должен оставаться.)
3b. Если любой аргумент ввода имеет тип домена, следует рассматривать его как тип базового домена на всех последующих этапах.
Я ожидаю, что это ничего не сделает, так как здесь нет типов домена.
3c. Просмотреть всех кандидатов и оставить тех, у кого больше всего точных соответствий с типами ввода. Оставить всех кандидатов, если нет точных соответствий. Если остается только один кандидат, использовать его; иначе перейти к следующему шагу.
Здесь я ожидаю, что оператор для double precision
будет отклонен, потому что оператор для real
имеет одно точное соответствие, в то время как оператор для double precision
не имеет ни одного точного соответствия. И поскольку остается только один кандидат, он должен использовать оператор для real
, и я ожидаю, что результат будет real
. Но вместо этого результат будет значением double precision
.
Таким образом, мой вопрос в том, почему PostgreSQL выбирает оператор double precision
в этом случае, где ему нужно неявно преобразовать оба операнда вместо только одного из них? Документация неверна или же мое понимание неверно?
Ответ или решение
В вашем вопросе вы рассматриваете поведение PostgreSQL при выполнении операций с типами данных real
и numeric
. Давайте разберёмся, почему результатом таких операций является значение типа double precision
.
Разъяснение поведения PostgreSQL
-
Типы данных и их диапазоны:
- Тип
real
представляет собой число с плавающей запятой одинарной точности, которое может представлять значения с приблизительно 7 знаками точности. - Тип
numeric
(илиdecimal
) обеспечивает произвольную точность с плавающей запятой, что позволяет хранить значения с очень высокой точностью, но он может быть медленнее в исполнении. - Тип
double precision
имеет в два раза большую точность, чемreal
, ожидая около 15 знаков точности.
- Тип
-
Правила приведения типов:
При выполнении операций сложения PostgreSQL использует строгий порядок приоритетов, чтобы выбрать наиболее подходящий тип для результата. В процессе выбора используется следующее:- Неявные приведения: PostgreSQL предлагает возможность неявно преобразовывать типы. В данном случае
real
может быть неявно преобразован вdouble precision
, так какdouble precision
может содержать все значенияreal
без потери точности. Тем не менее,real
не может быть неявно преобразован вnumeric
из-за возможной потери точности. - Выбор наилучшего типа результата: Когда вы производите операцию с
numeric
иreal
, PostgreSQL выполняет преобразование, которое предпочтительно отдает значениеdouble precision
, поскольку он охватывает все возможные значения обоих типов и предотвращает потерю точности.
- Неявные приведения: PostgreSQL предлагает возможность неявно преобразовывать типы. В данном случае
-
Динамика выбора операций:
Ваше описание шагов, касающихся выбора оператора, в целом верно. Однако решающим моментом является то, что PostgreSQL старается использовать результат с наибольшей точностью, особенно когда участвуют операнды с различной точностью. В этом случае, следуя выбору наилучшего подходящего типа, система определяет результат какdouble precision
вместоreal
.
Заключение
На основании вышеизложенного, можно прийти к выводу, что система PostgreSQL принимает решение о возврате результата типа double precision
вместо real
для эквивалентного выражения 0::numeric + 0::real
из-за стремления обеспечить наибольшую точность результатов, чем это предоставило бы использование типа real
. Это поведение следует из правил обработки типов и не является ошибкой в документировании. Таким образом, ваше понимание процесса приведения типов верно, но необходимо учесть, что PostgreSQL оптимизирует результаты для обеспечения точности вычислений.