Respostas:
Foi isso que descobri quando tive essa dúvida.
mysql> create table numbers (a decimal(10,2), b float);
mysql> insert into numbers values (100, 100);
mysql> select @a := (a/3), @b := (b/3), @a * 3, @b * 3 from numbers \G
*************************** 1. row ***************************
@a := (a/3): 33.333333333
@b := (b/3): 33.333333333333
@a + @a + @a: 99.999999999000000000000000000000
@b + @b + @b: 100
O decimal fez exatamente o que deveria ser feito nesses casos, truncou o restante, perdendo a parte 1/3.
Portanto, para somas o decimal é melhor, mas para divisões o float é melhor, até certo ponto, é claro. Quero dizer, usar DECIMAL não fornecerá uma "aritmética à prova de falhas" de nenhuma maneira.
Espero que isto ajude.
@a
dando 99.999999999000000000000000000000 ao DECIMAL? O que é tecnicamente correto.
Um "float" na maioria dos ambientes é do tipo de ponto flutuante binário. Ele pode armazenar com precisão os valores da base 2 (até um determinado ponto), mas não pode armazenar com precisão muitos valores da base 10 (decimal). Os flutuadores são mais apropriados para cálculos científicos. Eles não são adequados para a maioria das matemáticas voltadas para os negócios, e o uso inadequado de carros alegóricos o morderá. Muitos valores decimais não podem ser representados exatamente na base 2. 0.1
não pode, por exemplo, e você vê resultados estranhos como 1.0 - 0.1 = 0.8999999
.
Os decimais armazenam números da base 10. Decimal é um bom tipo para a maioria das matemáticas comerciais (mas qualquer tipo de "dinheiro" interno é mais apropriado para cálculos financeiros), onde o intervalo de valores excede o fornecido por tipos inteiros e são necessários valores fracionários. Os decimais, como o nome indica, são projetados para números da base 10 - eles podem armazenar com precisão valores decimais (novamente, até um determinado ponto).
O MySQL mudou recentemente a maneira como armazenam o tipo DECIMAL . No passado, eles armazenavam os caracteres (ou nybbles) de cada dígito, compreendendo uma representação ASCII (ou nybble) de um número - vs - um número inteiro de complemento de dois, ou alguma derivada do mesmo.
O formato de armazenamento atual para DECIMAL é uma série de números inteiros de 1,2,3 ou 4 bytes cujos bits são concatenados para criar um número de complemento de dois com um ponto decimal implícito, definido por você, e armazenado no esquema DB quando você declara a coluna e especifique o tamanho DECIMAL e a posição do ponto decimal.
A título de exemplo, se você usar um int de 32 bits, poderá armazenar qualquer número de 0 a 4.294.967.295. Isso abrangerá apenas 999.999.999 de maneira confiável; portanto, se você jogou 2 bits e usou (1 << 30 -1), não desistiu de nada. Cobrir todos os números de 9 dígitos com apenas 4 bytes é mais eficiente do que cobrir 4 dígitos em 32 bits usando 4 caracteres ASCII ou 8 dígitos de nybble. (um nybble é de 4 bits, permitindo valores de 0 a 15, mais do que o necessário para 0 a 9, mas você não pode eliminar esse desperdício indo para 3 bits, porque isso cobre apenas os valores de 0 a 7)
O exemplo usado nos documentos online do MySQL usa DECIMAL (18,9) como exemplo. Ele tem 9 dígitos à frente e 9 dígitos atrás do ponto decimal implícito, o que, conforme explicado acima, requer o seguinte armazenamento.
Como 18 caracteres de 8 bits: 144 bits
Como 18 nybbles de 4 bits: 72 bits
Como 2 números inteiros de 32 bits: 64 bits
Atualmente, o DECIMAL suporta um máximo de 65 dígitos, como DECIMAL (M, D), onde o maior valor permitido para M é 65 e o maior valor permitido de D é 30.
Para não exigir pedaços de 9 dígitos por vez, números inteiros menores que 32 bits são usados para adicionar dígitos usando números inteiros de 1,2 e 3 bytes. Por alguma razão que desafia a lógica, foram usadas entradas assinadas em vez de entradas não assinadas e, dessa maneira, 1 bit é descartado, resultando nos seguintes recursos de armazenamento. Para ints de 1,2 e 4 bytes, o bit perdido não importa, mas para os int de 3 bytes é um desastre, porque um dígito inteiro é perdido devido à perda desse bit único.
Com um int de 7 bits: 0 - 99
Com um int de 15 bits: 0 - 9.999
Com um int de 23 bits: 0 - 999.999 (0 - 9.999.999 com um int de 24 bits)
Inteiros de 1,2,3 e 4 bytes são concatenados juntos para formar um "pool de bits" que DECIMAL usa para representar o número precisamente como um número inteiro de complemento de dois. O ponto decimal NÃO é armazenado, está implícito.
Isso significa que não são necessárias conversões ASCII para int do mecanismo de banco de dados para converter o "número" em algo que a CPU reconheça como um número. Sem arredondamentos, sem erros de conversão, é um número real que a CPU pode manipular.
Os cálculos nesse número inteiro arbitrariamente grande devem ser feitos em software, pois não há suporte de hardware para esse tipo de número, mas essas bibliotecas são muito antigas e altamente otimizadas, tendo sido escritas há 50 anos para suportar dados de ponto flutuante de precisão arbitrária do IBM 370 Fortran . Eles ainda são muito mais lentos que a álgebra inteira de tamanho fixo feita com hardware inteiro da CPU ou cálculos de ponto flutuante feitos na FPU.
Em termos de eficiência de armazenamento, como o expoente de um flutuador é anexado a todo e qualquer flutuador, especificando implicitamente onde está o ponto decimal, é massivamente redundante e, portanto, ineficiente para o trabalho do DB. Em um banco de dados, você já sabe onde o ponto decimal deve ser colocado na frente, e todas as linhas da tabela que possuem um valor para uma coluna DECIMAL precisam apenas olhar para a especificação 1 e única de onde esse ponto decimal deve ser colocado, armazenado no esquema como os argumentos para um DECIMAL (M, D) como a implicação dos valores M e D.
As muitas observações encontradas aqui sobre qual formato deve ser usado para vários tipos de aplicativos estão corretas, por isso não abordarei o assunto. Eu dediquei um tempo para escrever isso aqui, porque quem está mantendo a documentação on-line do MySQL não entende nenhuma das opções acima e, após rodadas de tentativas cada vez mais frustrantes de explicá-las, desisti. Uma boa indicação de quão mal eles entenderam o que estavam escrevendo é a apresentação muito confusa e quase indecifrável do assunto.
Como pensamento final, se você precisa de computação de ponto flutuante de alta precisão, houve enormes avanços no código de ponto flutuante nos últimos 20 anos, e o suporte de hardware para o flutuador de 96 bits e de precisão quádrupla está ao virar da esquina, mas existem boas bibliotecas de precisão arbitrárias por aí, se a manipulação do valor armazenado for importante.
Não apenas específica para o MySQL, a diferença entre os tipos float e decimal é a maneira como eles representam valores fracionários. Os tipos de ponto flutuante representam frações em binário, que podem representar apenas valores como {m*2^n | m, n Integers}
. valores como 1/5 não podem ser representados com precisão (sem erro de arredondamento). Os números decimais são igualmente limitados, mas representam números como {m*10^n | m, n Integers}
. Os decimais ainda não podem representar números como 1/3, mas geralmente ocorre em muitos campos comuns, como finanças, que a expectativa é que certas frações decimais sempre possam ser expressas sem perda de fidelidade. Como um número decimal pode representar um valor como $0.20
(um quinto de um dólar), é preferido nessas situações.
decimal é para quantidades fixas, como dinheiro, nas quais você deseja um número específico de casas decimais. Os flutuadores servem para armazenar ... números de precisão de ponto flutuante.
Eu achei isso útil:
Geralmente, os valores flutuantes são bons para cálculos científicos, mas não devem ser usados para valores financeiros / monetários. Para Matemática Orientada a Negócios, sempre use Decimal.
Fonte: http://code.rohitink.com/2013/06/12/mysql-integer-float-decimal-data-types-differences/
mysql> CREATE TABLE num(id int ,fl float,dc dec(5,2));
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO num VALUES(1,13.75,13.75);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO num VALUES(2,13.15,13.15);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM num WHERE fl = 13.15;
Empty set (0.00 sec)
mysql> SELECT * FROM num WHERE dc = 13.15;
+------+-------+-------+
| id | fl | dc |
+------+-------+-------+
| 2 | 13.15 | 13.15 |
+------+-------+-------+
1 row in set (0.00 sec)
mysql> SELECT SUM(fl) ,SUM(dc) FROM num;
+--------------------+---------+
| SUM(fl) | SUM(dc) |
+--------------------+---------+
| 26.899999618530273 | 26.90 |
+--------------------+---------+
1 row in set (0.00 sec)
mysql> SELECT * FROM num WHERE ABS(fl - 13.15)<0.01;
+------+-------+-------+
| id | fl | dc |
+------+-------+-------+
| 2 | 13.15 | 13.15 |
+------+-------+-------+
1 row in set (0.00 sec)
Tipos de ponto flutuante (valor aproximado) - FLOAT, DOUBLE
Os tipos FLOAT e DOUBLE representam valores numéricos aproximados dos dados. O MySQL usa quatro bytes para valores de precisão única e oito bytes para valores de precisão dupla.
Para FLOAT, o padrão SQL permite uma especificação opcional da precisão (mas não do intervalo do expoente) em bits, após a palavra-chave FLOAT entre parênteses. O MySQL também suporta esta especificação de precisão opcional, mas o valor de precisão é usado apenas para determinar o tamanho do armazenamento. Uma precisão de 0 a 23 resulta em uma coluna FLOAT de precisão única de 4 bytes. Uma precisão de 24 a 53 resulta em uma coluna DOUBLE de precisão dupla de 8 bytes.
O MySQL permite uma sintaxe fora do padrão: FLOAT (M, D) ou REAL (M, D) ou DOUBLE PRECISION (M, D). Aqui, “(M, D)” significa que os valores podem ser armazenados com até M dígitos no total, dos quais os dígitos D podem estar após o ponto decimal. Por exemplo, uma coluna definida como FLOAT (7,4) será semelhante a -999,9999 quando exibida. O MySQL executa arredondamentos ao armazenar valores, portanto, se você inserir 999.00009 em uma coluna FLOAT (7,4), o resultado aproximado será 999.0001.
Como os valores de ponto flutuante são aproximados e não são armazenados como valores exatos, as tentativas de tratá-los como exatos nas comparações podem levar a problemas. Eles também estão sujeitos a dependências de plataforma ou implementação.
Para máxima portabilidade, o código que requer armazenamento de valores aproximados de dados numéricos deve usar FLOAT ou DOUBLE PRECISION sem especificação de precisão ou número de dígitos.
https://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html
Problemas com valores de ponto flutuante
Os números de ponto flutuante às vezes causam confusão porque são aproximados e não são armazenados como valores exatos . Um valor de ponto flutuante, conforme escrito em uma instrução SQL, pode não ser o mesmo que o valor representado internamente. Tentativas de tratar valores de ponto flutuante como exatas nas comparações podem levar a problemas. Eles também estão sujeitos a dependências de plataforma ou implementação. Os tipos de dados FLOAT e DOUBLE estão sujeitos a esses problemas. Para colunas DECIMAIS, o MySQL executa operações com uma precisão de 65 dígitos decimais, o que deve resolver os problemas de imprecisão mais comuns.
O exemplo a seguir usa DOUBLE para demonstrar como os cálculos feitos usando operações de ponto flutuante estão sujeitos a erro de ponto flutuante.
mysql> CREATE TABLE t1 (i INT, d1 DOUBLE, d2 DOUBLE);
mysql> INSERT INTO t1 VALUES (1, 101.40, 21.40), (1, -80.00, 0.00),
-> (2, 0.00, 0.00), (2, -13.20, 0.00), (2, 59.60, 46.40),
-> (2, 30.40, 30.40), (3, 37.00, 7.40), (3, -29.60, 0.00),
-> (4, 60.00, 15.40), (4, -10.60, 0.00), (4, -34.00, 0.00),
-> (5, 33.00, 0.00), (5, -25.80, 0.00), (5, 0.00, 7.20),
-> (6, 0.00, 0.00), (6, -51.40, 0.00);
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b
-> FROM t1 GROUP BY i HAVING a <> b;
+------+-------+------+
| i | a | b |
+------+-------+------+
| 1 | 21.4 | 21.4 |
| 2 | 76.8 | 76.8 |
| 3 | 7.4 | 7.4 |
| 4 | 15.4 | 15.4 |
| 5 | 7.2 | 7.2 |
| 6 | -51.4 | 0 |
+------+-------+------+
O resultado está correto. Embora os cinco primeiros registros pareçam não satisfazer a comparação (os valores de aeb não parecem diferentes), eles podem fazê-lo porque a diferença entre os números aparece em torno da décima casa decimal, dependendo dos fatores como a arquitetura do computador ou a versão do compilador ou o nível de otimização. Por exemplo, CPUs diferentes podem avaliar números de ponto flutuante de maneira diferente.
Se as colunas d1 e d2 tivessem sido definidas como DECIMAL em vez de DOUBLE, o resultado da consulta SELECT teria apenas uma linha - a última mostrada acima.
A maneira correta de fazer a comparação de números de ponto flutuante é primeiro decidir sobre uma tolerância aceitável para diferenças entre os números e depois fazer a comparação com o valor de tolerância. Por exemplo, se concordarmos que os números de ponto flutuante devem ser considerados iguais se forem iguais dentro de uma precisão de um em dez mil (0,0001), a comparação deve ser escrita para encontrar diferenças maiores que o valor de tolerância:
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
-> GROUP BY i HAVING ABS(a - b) > 0.0001;
+------+-------+------+
| i | a | b |
+------+-------+------+
| 6 | -51.4 | 0 |
+------+-------+------+
1 row in set (0.00 sec)
Por outro lado, para obter linhas onde os números são iguais, o teste deve encontrar diferenças dentro do valor de tolerância:
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
-> GROUP BY i HAVING ABS(a - b) <= 0.0001;
+------+------+------+
| i | a | b |
+------+------+------+
| 1 | 21.4 | 21.4 |
| 2 | 76.8 | 76.8 |
| 3 | 7.4 | 7.4 |
| 4 | 15.4 | 15.4 |
| 5 | 7.2 | 7.2 |
+------+------+------+
5 rows in set (0.03 sec)
Os valores de ponto flutuante estão sujeitos a dependências de plataforma ou implementação. Suponha que você execute as seguintes instruções:
CREATE TABLE t1(c1 FLOAT(53,0), c2 FLOAT(53,0));
INSERT INTO t1 VALUES('1e+52','-1e+52');
SELECT * FROM t1;
Em algumas plataformas, a instrução SELECT retorna inf e -inf. Em outros, retorna 0 e -0.
Uma implicação dos problemas anteriores é que, se você tentar criar um escravo de replicação despejando o conteúdo da tabela com o mysqldump no mestre e recarregando o arquivo de despejo no escravo, as tabelas que contêm colunas de ponto flutuante podem diferir entre os dois hosts.
https://dev.mysql.com/doc/refman/5.5/en/problems-with-float.html
Regra rígida e rápida
Se tudo o que você precisa fazer é adicionar, subtrair ou multiplicar os números que você está armazenando, DECIMAL é o melhor.
Se você precisar dividir ou fazer qualquer outra forma de aritmética ou álgebra nos dados, você certamente ficará mais feliz com o float. As bibliotecas de ponto flutuante e nos processadores Intel, o próprio processador de ponto flutuante, têm TONs de operações para corrigir, corrigir, detectar e lidar com a nevasca das exceções que ocorrem ao executar funções matemáticas típicas - especialmente funções transcendentais.
Quanto à precisão, uma vez escrevi um sistema de orçamento que calculava a porcentagem de contribuição de cada uma das mais de 3.000 contas, para 3.600 unidades de orçamento, por mês, no nó de consolidação dessa unidade, e depois com base nessa matriz de porcentagens (3.000 + x 12 x 3.600) Multipliquei os valores orçados pelos nós organizacionais mais altos até os próximos três níveis dos nós organizacionais e depois calculei todos os valores (3.000 + 12) para todas as 3.200 unidades de detalhe. Milhões e milhões e milhões de cálculos de ponto flutuante de precisão dupla, qualquer um dos quais descartaria o acúmulo de todas essas projeções em uma consolidação de baixo para cima, voltando ao nível mais alto da organização.
O erro total de ponto flutuante após todos esses cálculos foi zero . Isso foi em 1986, e as bibliotecas de ponto flutuante hoje são muito, muito melhores do que eram antes. A Intel faz todos os seus cálculos intermediários de dobras na precisão de 80 bits, o que praticamente elimina o erro de arredondamento. Quando alguém lhe diz "é erro de ponto flutuante", é quase certo que NÃO é verdade.
float
(e double
) representa frações binárias
decimal
representa frações decimais
declare @float as float(10)
declare @Decimal as decimal(10)
declare @Inetger as int
set @float =10.7
set @Decimal =10.7
set @Inetger=@Decimal
print @Inetger
em float quando o valor é definido como número inteiro, impressão 10, mas no decimal 11
FLOAT(m,n)
, isso leva a dois arredondamentos; enquanto isso, ele não oferece nada de útil.