Números de ponto flutuante geram confusões algumas vezes, pois estes números não são armazenados como valores exatos dentro da arquitetura dos computadores. O que pode ser ver na tela não é o valor exato do número.
Tipos de campos FLOAT
,
DOUBLE
e DECIMAL
são
assim.
CREATE TABLE t1 (i INT, d1 DECIMAL(9,2), d2 DECIMAL(9,2)); 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.40 | 21.40 | | 2 | 76.80 | 76.80 | | 3 | 7.40 | 7.40 | | 4 | 15.40 | 15.40 | | 5 | 7.20 | 7.20 | | 6 | -51.40 | 0.00 | +------+--------+-------+
O resultado está correto. Embora pareça que os primeiros cinco registros não devessem passar no teste de comparação, eles deviam porque a diferença entre o número mostrado está na décima casa decimal ou depende da arquitetura do computador.
O problema não pode ser resolvido usando ROUND() (ou função similar), porque o resultado ainda é um número de ponto flutuante. Exemplo:
mysql>SELECT i, ROUND(SUM(d1), 2) AS a, ROUND(SUM(d2), 2) AS b
->FROM t1 GROUP BY i HAVING a <> b;
+------+--------+-------+ | i | a | b | +------+--------+-------+ | 1 | 21.40 | 21.40 | | 2 | 76.80 | 76.80 | | 3 | 7.40 | 7.40 | | 4 | 15.40 | 15.40 | | 5 | 7.20 | 7.20 | | 6 | -51.40 | 0.00 | +------+--------+-------+
É assim que o número da coluna 'a' se parece:
mysql>SELECT i, ROUND(SUM(d1), 2)*1.0000000000000000 AS a,
->ROUND(SUM(d2), 2) AS b FROM t1 GROUP BY i HAVING a <> b;
+------+----------------------+-------+ | i | a | b | +------+----------------------+-------+ | 1 | 21.3999999999999986 | 21.40 | | 2 | 76.7999999999999972 | 76.80 | | 3 | 7.4000000000000004 | 7.40 | | 4 | 15.4000000000000004 | 15.40 | | 5 | 7.2000000000000002 | 7.20 | | 6 | -51.3999999999999986 | 0.00 | +------+----------------------+-------+
Dependendo da arquitetura do computador você pode ou não ver resultados similares. Cada CPU pode avaliar um númere de ponto flutuante de forma diferente. Por exemplo, em alguma máquinas você pode obter resultados 'corretos' multiplicando ambos argumentos por 1, como no exemplo a seguir.
AVISO: NUNCA CONFIE NESTE MÉTODO EM SUAS APLICAÇÕES, ESTE É UM EXEMPLO DE UM MÉTODO ERRADO!!!
mysql>SELECT i, ROUND(SUM(d1), 2)*1 AS a, ROUND(SUM(d2), 2)*1 AS b
->FROM t1 GROUP BY i HAVING a <> b;
+------+--------+------+ | i | a | b | +------+--------+------+ | 6 | -51.40 | 0.00 | +------+--------+------+
A razão pela qual o método acima parece funcionar é que na máquina onde o teste foi realizado, a CPU de aritimética de ponto flutuante é realizada arredondando números para serem iguais, mas não há nenhuma regra que qualquer CPU deva fazer assim, então isto não é confiável.
O modo correto de fazermos comparações de ponto flutuante é primeiro decidir qual é a tolerância desejada entre os números e então fazer a comparação com o número tolerado. Por exemplo, se nós concordarmos que números de ponto flutuante devem ser considerados o mesmo, se eles forem o mesmo com precisão de quatro casas deciamis (0.0001), a comparação deve ser feita assim:
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.40 | 0.00 | +------+--------+------+ 1 row in set (0.00 sec)
E vice-versa, se nós quisermos obter registros onde os números são o mesmo, o teste seria:
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.40 | 21.40 | | 2 | 76.80 | 76.80 | | 3 | 7.40 | 7.40 | | 4 | 15.40 | 15.40 | | 5 | 7.20 | 7.20 | +------+-------+-------+
This is a translation of the MySQL Reference Manual that can be found at dev.mysql.com. The original Reference Manual is in English, and this translation is not necessarily as up to date as the English version.