Mysql ‚float‘-Bug/Ungenauigkeit
Ich habe heute mit der OpenGeoDb, eine freie Datenbank für Orte mit Postleitzahlen, Koordinaten und anderen geographischen Angaben, eine kleine Umkreissuche mit weiteren Daten gebastelt. Da dabei einige Überprüfungen gemacht werden müssen, wollte ich ein paar Optimierungen einbauen.
Die erste bestand darin, zu überprüfen, ob die PLZ gleich der gesuchten ist. In diesem Fall muss nicht der Umkreis über SIN, COS und ACOS berechnet werden sondern kann zu 0,0km gesetzt werden. Als nächstes kann es sein, dass der gleiche Ort mehrere PLZ mit gleichen Koordinaten beinhaltet, wie das in der OpenGeoDb bei Berlin der Fall ist. Also ein SELECT … IF(geo.laenge=13.4 AND g.breite=52.5167, …, … ) WHERE …;
Aber das funktionierte nicht. Nach einigem Suchen fand ich heraus, dass die Werte für Berlin (13.4 und 52.5167), die ich aus der Datenbank kopiert hatte, in Mysql anders verarbeitet werden. Ein SELECT geo.laenge – 13.4; liefert dabei 7.44e-07, das bedeutet, das irgend etwas bei der Konvertierung nicht stimmt.
Hier ein weiterer Test zur Verifikation:
root@linux:~# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 58
Server version: 5.0.51a-3ubuntu5.4 (Ubuntu)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use test
Database changed
mysql> CREATE TABLE `floattest` ( test FLOAT NOT NULL);
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO `floattest` VALUES (1.0001);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT test FROM `floattest` WHERE test=1.0001; Empty set (0.00 sec) mysql> SELECT test-1.0001 AS diff FROM `floattest`; +---------------------+ | diff | +---------------------+ | 1.6593933116482e-08 | +---------------------+ 1 row in set (0.00 sec)
Wie zu sehen ist, ist hier auch ein Offset, der meiner Meinung nach aus der Konvertierung aus dem 2er-System stammt. Da anscheinend kein direkter Vergleich möglich ist, habe ich mich per SELECT … IF(ABS(geo.laenge-13.4)<0.0001) geholfen. Das beschleunigt die Datenbank immer noch bei großen Abfragen, da der Verbund aus IF und ABS plus der Vergleich immer noch schneller ist, als die SIN COS und ACOS-Funktionen aus dem Originalquery. Auch eine Rundenfunktion wäre möglich, aber die Lösung mit ABS(wert) ist meiner Meinung nach flexibler, da so auch Orte im Umkreis mit abgefangen werden können, die näher als ein bestimmter Wert epsilon liegen.
Mysql weißt im Abschnitt A.5.8 darauf hin, „da[ss] Fließkommazahlen von Natur aus ungenau sind“. Also seid vorsichtig!