Eu encontrei o que parece ser uma perda de precisão inesperada ao converter de float4
diretamente para numeric
no PostgreSQL:
select 1079414::float4,
(1079414::float4)::numeric(15,2) as float4_to_numeric, -- loses precision
((1079414::float4)::float8)::numeric(15,2) as float4_to_float8_to_numeric;
Isso produz:
float4 | float4_to_numeric | float4_to_float8_to_numeric
------------+-------------------+----------------------------
1,079,414 | 1,079,410 | 1,079,414
A conversão direta de float4
para numeric
inesperadamente produz 1,079,410
em vez do esperado 1,079,414
.
A documentação do PostgreSQL afirma : "Em todas as plataformas suportadas atualmente, o real
tipo tem um intervalo de aproximadamente 1E-37 a 1E+37 com uma precisão de pelo menos 6 dígitos decimais." E percebo que são 7 dígitos decimais.
No entanto, a Wikipedia observa : "Qualquer número inteiro com valor absoluto menor que 2^24 pode ser representado exatamente no formato de precisão simples"
Como 1.079.414 é muito menor que 2^24 (16.777.216), eu esperaria que ele fosse representado exatamente.
Curiosamente, converter float8
primeiro para e depois para numeric
preserva o valor total, sugerindo que o PostgreSQL está armazenando a precisão total, mas de alguma forma a perde durante a conversão direta para numeric
.
Esse comportamento é um bug ou há alguma razão subjacente para esse comportamento? O que está acontecendo durante a conversão de tipo que causa essa perda de precisão?
Vejo que já existe uma pergunta semelhante , mas esse exemplo usa decimais, enquanto este é sobre um número inteiro menor que 2^24.
Vamos dar uma olhada na implementação
Essas conversões de tipo são implementadas por funções:
ftod
para conversão float4 -> float8, nada surpreendente, conversão de tipo direta dentro dos tipos de dados da linguagem C. Mais formalmente, pode diferir devido à implementação do compiladorfloat4_numeric
para a conversão de float4 para numérico é um pouco mais inesperado e é implementado convertendo para uma representação de string comFLT_DIG
precisão codificada.FLT_DIG
é uma constante de tempo de compilação padrão definida como o número de dígitos decimais de precisão para o tipo de dados float. Mais relacionado a esta questão , provavelmente.FLT_DIG
é comumente 6.É interessante mencionar aqui que a exibição textual dos valores float4 é influenciada pelo
extra_float_digits
parâmetro de configuração de tempo de execução. Por padrão, extra_float_digits é 1, então isso dá FLT_DIG + 1 = 7 na maioria dos casos. Mas float4_numeric não leva em contaextra_float_digits
.É improvável que seja considerado um bug, pois não viola a promessa de ter pelo menos 6 dígitos decimais.