MySQL 5.5.32, Oracle 11g
O MySQL retorna resultados estranhos para consultas envolvendo mod()
módulos não inteiros. Por que é isso?
Eu tenho uma coluna definida assim:
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| col | double(15,6) | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
Selecionei um valor conhecido dessa coluna para usar no teste - 5850. Se eu consultar a tabela, testando essa coluna para números inteiros, obtenho estes resultados:
mysql> SELECT thing_id,name FROM table WHERE col=5850 AND mod(col,1) != 0;
Empty set (1.07 sec)
Isso é esperado. Mas isso não é:
mysql> SELECT thing_id,name FROM table WHERE col=5850 AND mod(col,.1) != 0;
+-----------+-----------+
| thing_id | name |
+-----------+-----------+
| 4444444 | some |
| 4444445 | names |
...
| 55555555 | go |
| 55555556 | here |
+-----------+-----------+
416 rows in set (1.07 sec)
O mesmo resulta da substituição col MOD .1
e col % .1
, portanto, tem a ver com o cálculo, não com a semântica.
Compare isso com o comportamento do Oracle (usando o SQL Developer, se isso for importante), onde o tipo de col
é NUMBER
:
SELECT thing_id,name FROM table WHERE col=5850 AND mod(col,.1) != 0;
thing_id name
[NO RECORDS]
A precisão na coluna MySQL parece fazer essa diferença, mas não entendo como. Conceitualmente, não deveria integer_value % .1 = 0
?
Vou começar com um link: O que todo programador deve saber sobre aritmética de ponto flutuante .
Resumindo, tipos aritméticos de ponto flutuante, como os tipos
float
edouble
mysql, nunca devem ser usados para aritmética precisa. E vocêcol mod 0.1
está tentando fazer exatamente isso, uma verificação aritmética precisa. Ele está tentando descobrir se o valor emcol
é um múltiplo exato de0.1
. E falha miseravelmente, como é óbvio pelos seus resultados.A solução é usar um tipo fixo, como
decimal(m, n)
para fazer esses tipos de verificações.Quanto ao motivo pelo qual você obteve resultados diferentes no Oracle, é porque você usou o
NUMBER
que é implementado de maneira diferente (com precisão decimal). Os equivalentes no Oracle de float e double sãoBINARY_FLOAT
eBINARY_DOUBLE
e usam precisão binária. Consulte as notas nos documentos do Oracle: Visão geral dos tipos de dados numéricos :Se você insiste em usar
double
(não vejo porque), mais alguns testes com omod
operador/função no mysql, revela que:Quando você usa números inteiros para os argumentos, digamos que sim
x mod K
, o resultado é sempre um número inteiro entre0
eK-1
. O comportamento matemático esperado.Quando você usa floats/doubles, digamos que sim
x mod K
, o resultado é qualquer número de0.0
aK
included . (como seus testes que mostram5850.0 mod 0.1
resultar em0.1
). Não fiz milhões de testes, então pode haver casos em que o resultado pode ser ainda maior do queK
! A aritmética de ponto flutuante pode dar resultados estranhos.Portanto, uma solução alternativa (que nem sempre funciona!) seria usar esta condição:
Para ser sincero, sinto-me triste por ter feito a sugestão de solução alternativa. Leia novamente O que todo programador deve saber sobre aritmética de ponto flutuante e não use a solução alternativa.
Use tipos de precisão fixa para operações aritméticas de precisão.