Executar uma operação matemática em um real
operando e um numeric
operando no PostgreSQL resulta em um double precision
valor, mas acho isso surpreendente. Por que o resultado não seria um real
, em vez disso?
Confirmei esse comportamento com o PostgreSQL 13 e 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??
Li partes substanciais da documentação do PostgreSQL, mais relevantes: 10.2. Conversão de tipo: operadores
E eu consultei pg_catalog.pg_cast
(ou psql
's \dC
) para entender quais são as conversões implícitas relevantes:
> SET search_path = 'pg_catalog';
> SELECT format_type(castsource, NULL) AS source,
format_type(casttarget, NULL) AS target
FROM pg_cast
WHERE castcontext = 'i' -- i.e. casts allowed implicitly
AND ARRAY[format_type(castsource, NULL), format_type(casttarget, NULL)]
<@ '{numeric,real,double precision}'::text[];
O resultado é:
source | target
---------+------------------
real | double precision
numeric | real
numeric | double precision
numeric | numeric
(4 rows)
Então, pelo que entendi, para 0::numeric + 0::real
o PostgreSQL deveria (os trechos são todos de 10.2. Conversão de Tipo: Operadores ):
- Selecione os operadores a serem considerados do
pg_operator
catálogo do sistema. Se um nome de operador não qualificado por esquema foi usado (o caso usual), os operadores considerados são aqueles com o nome correspondente e a contagem de argumentos que são visíveis no caminho de pesquisa atual.
Espero que comece com todos +
os operadores - cada um aceita um par dos mesmos tipos numéricos ( real + real
, integer + integer
, etc.)
- Verifique se há um operador que aceita exatamente os tipos de argumentos de entrada.
Na etapa 2, nenhum operador é uma correspondência exata para real
e numeric
, então espero que nada aconteça aqui.
3a. Descarte operadores candidatos cujos tipos de entrada não correspondem e não podem ser convertidos (usando uma conversão implícita) para corresponder.
Espero que isso descarte a maioria dos candidatos. Ele deve manter exatamente dois: o de real
, e o de double precision
que ele aparentemente acaba usando. (Note que real
não pode ser implicitamente convertido para numeric
, então ele não deve manter o de numeric
.)
3b. Se qualquer argumento de entrada for de um tipo de domínio, trate-o como sendo do tipo base do domínio para todas as etapas subsequentes.
Espero que isso não faça nada, pois não há tipos de domínio aqui.
3c. Execute todos os candidatos e mantenha aqueles com as correspondências mais exatas em tipos de entrada. Mantenha todos os candidatos se nenhum tiver correspondências exatas. Se apenas um candidato permanecer, use-o; caso contrário, continue para a próxima etapa.
Aqui, espero que ele descarte o operador for double precision
, porque o operador for real
tem uma correspondência exata, enquanto o for double precision
tem zero correspondências exatas. E então, como só resta um candidato aqui, ele deve usar o operador for real
, e espero que o resultado seja um real
. Mas, em vez disso, o resultado é um double precision
valor.
Então, minha pergunta é por que o PostgreSQL está escolhendo o double precision
operador neste caso, onde ele precisa converter implicitamente ambos os operandos em vez de apenas um deles? A documentação está errada, ou meu entendimento dela está errado?
As duas peças que faltam:
O documento sobre tipos de resultados em operações matemáticas de tipo misto:
A lista em questão é "
smallint
,integer
,bigint
,numeric
,real
, edouble precision
". Essa é a regra geral, mas é uma consequência de quais operadores são incorporados, discutidos abaixo.Preferência de tipo. Você pode verificar em
pg_type.ispreferred
. Lá,double precision
aparece como o tipo preferido sobre ambos,real
bem como sobrenumeric
, no grupo Tipos numéricos :(precisão dupla)
(real)
(pequenoint)
(int)
(int grande)
Você parou em 10.2. Conversão de tipo: Operadores logo antes de chegar ao passo que ele usa:
Ele vai para 3d, porque em 3c ele descobre que não há
real+numeric
operador (nenhuma correspondência exata, três meias correspondências):E dos três candidatos mais próximos,
real+real
(elenco para a direita)real+double precision
(elenco para a direita) enumeric+numeric
(elenco para a esquerda), o do meio vence, graças ao seu tipo de destino ser o preferido.O primeiro é apenas para tornar esta resposta mais genérica e aplicar a outras combinações. O segundo é precisamente o motivo pelo qual você vê
real+numeric
resultado emreal+numeric::double precision
que produz umdouble precision
, em vez de umreal+numeric::real
que lhe daria umreal
.Ele converte apenas um, o
numeric
. Seu teste usou constantes que são dobradas por constantes, deixando apenas o resultado pré-avaliado e ocultando as operações subjacentes. Você pode ver isso mais claramente se usarexplain verbose
e uma tabela com valores diferentes:demo em db<>fiddle
, pg_typeof((r + r))
, pg_typeof(((n)::precisão dupla + r))
, pg_typeof((r + (n)::precisão dupla))