Почему операция с вещественными и числовыми входными данными приводит к двойной точности?

Вопрос или проблема

Выполнение математической операции с 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. Преобразование типов: Операторы):

  1. Выбрать операторы, которые будут рассматриваться из системного каталога pg_operator. Если было использовано имя оператора без указания схемы (что обычно), рассматриваются операторы с соответствующим именем и количеством аргументов, которые видны в текущем поисковом пути.

Я ожидаю, что начнется со всех операторов + – каждый из них принимает пару одних и тех же числовых типов (real + real, integer + integer и т.д.)

  1. Проверить наличие оператора, принимающего точно данные типы аргументов.

На шаге 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

  1. Типы данных и их диапазоны:

    • Тип real представляет собой число с плавающей запятой одинарной точности, которое может представлять значения с приблизительно 7 знаками точности.
    • Тип numeric (или decimal) обеспечивает произвольную точность с плавающей запятой, что позволяет хранить значения с очень высокой точностью, но он может быть медленнее в исполнении.
    • Тип double precision имеет в два раза большую точность, чем real, ожидая около 15 знаков точности.
  2. Правила приведения типов:
    При выполнении операций сложения PostgreSQL использует строгий порядок приоритетов, чтобы выбрать наиболее подходящий тип для результата. В процессе выбора используется следующее:

    • Неявные приведения: PostgreSQL предлагает возможность неявно преобразовывать типы. В данном случае real может быть неявно преобразован в double precision, так как double precision может содержать все значения real без потери точности. Тем не менее, real не может быть неявно преобразован в numeric из-за возможной потери точности.
    • Выбор наилучшего типа результата: Когда вы производите операцию с numeric и real, PostgreSQL выполняет преобразование, которое предпочтительно отдает значение double precision, поскольку он охватывает все возможные значения обоих типов и предотвращает потерю точности.
  3. Динамика выбора операций:
    Ваше описание шагов, касающихся выбора оператора, в целом верно. Однако решающим моментом является то, что PostgreSQL старается использовать результат с наибольшей точностью, особенно когда участвуют операнды с различной точностью. В этом случае, следуя выбору наилучшего подходящего типа, система определяет результат как double precision вместо real.

Заключение

На основании вышеизложенного, можно прийти к выводу, что система PostgreSQL принимает решение о возврате результата типа double precision вместо real для эквивалентного выражения 0::numeric + 0::real из-за стремления обеспечить наибольшую точность результатов, чем это предоставило бы использование типа real. Это поведение следует из правил обработки типов и не является ошибкой в документировании. Таким образом, ваше понимание процесса приведения типов верно, но необходимо учесть, что PostgreSQL оптимизирует результаты для обеспечения точности вычислений.

Оцените материал
Добавить комментарий

Капча загружается...