2010-03-03 19 views
7

Wir sind mit einer Datenbank fest, die (leider) Gleitkommazahlen anstelle von Dezimalwerten verwendet. Das macht das Runden etwas schwierig. Betrachten Sie das folgende Beispiel (SQL Server T-SQL):"Runde halb hoch" auf Gleitkommawerten

SELECT ROUND(6.925e0, 2) --> returns 6.92 

ROUND tut round half up, aber da floating point numbers cannot accurately represent decimal numbers, das "falsche" Ergebnis (aus der Sicht des Endbenutzers) angezeigt. Ich verstehe warum das passiert.

Ich kam bereits mit zwei möglichen Lösungen nach oben (beide einen Schwimmer zurückkehrt, das ist leider auch eine Anforderung):

  1. in eine Dezimalzahl Datentyp konvertieren vor dem Runden: SELECT CONVERT(float, ROUND(CONVERT(decimal(29,14), 6.925e0), 2))
  2. Multiply bis die dritte Stelle befindet sich auf der linken Seite des Dezimalpunktes (dh genau dargestellt), und führen Sie dann die Rundung: SELECT ROUND(6.925e0 * 1000, -1)/1000

Welches sollte ich wählen? Gibt es eine bessere Lösung? (Leider können wir nicht die Feldtypen in der Datenbank ändern aufgrund einiger Legacy-Anwendungen die gleichen DB zugreifen.)

Gibt es eine gut etablierte Best Practice Lösung für dieses (gemeinsam?) Problem?

(Offensichtlich ist die übliche Technik „Runden zweimal“ wird seit 6.925 hier nicht helfen, ist bereits auf drei Dezimalstellen gerundet - soweit dies in einem Schwimmer möglich ist.)

+1

Ich markiert dies sowohl 'sql-server' und' sprach-agnostic', da die Beispiele in T-SQL sind, aber die Frage nach einem Best-Practice-Algorithmus ist ein generischer. – Heinzi

+0

Es gibt keinen Algorithmus oder eine Funktion, die das erreichen kann, was Sie wollen, weil die meisten Gleitkommazahlen, die Vielfache eines Basis-10-Bruchteils sind, nicht genau darstellbar sind. Zum Beispiel wird 6.925 als etwas über oder unter dem Wert dargestellt, aber niemals exakt. Welchen Weg soll es abgerundet werden? – hirschhornsalz

Antwort

6

Ihre erste Lösung scheint sicherer , und scheint auch konzeptionell näher am Problem zu sein: Konvertieren Sie so schnell wie möglich von Fließkommazahl in Dezimalzahl, führen Sie alle relevanten Berechnungen innerhalb des Dezimaltyps durch, und führen Sie dann eine letzte Konvertierung in Fließkommazahl durch, bevor Sie in die Datenbank schreiben.

Bearbeiten: Sie müssen wahrscheinlich noch eine zusätzliche Runde (zB auf 3 Dezimalstellen, oder was immer für Ihre Anwendung geeignet ist) unmittelbar nach dem Abrufen des Gleitkommawerts und der Konvertierung in Dezimal, um sicherzustellen, dass Sie am Ende der Dezimalwert, der eigentlich beabsichtigt war. 6.925e0 in Dezimal konvertiert würde wieder wahrscheinlich (vorausgesetzt, das Dezimalformat hat> 16 Stellen der Genauigkeit) zu etwas geben, das sehr nahe ist, aber nicht genau gleich, 6.925; Eine Extrarunde würde dafür sorgen.

Die zweite Lösung sieht mir nicht zuverlässig aus: Was passiert, wenn der gespeicherte Wert für zufälligerweise aufgrund der üblichen binären Fließkomma-Probleme eine kleine Menge zu klein ist? Dann nach der Multiplikation mit 1000, kann das Ergebnis immer noch eine Berührung unter 6925 sein, so dass der Rundungsschritt abgerundet wird anstatt oben. Wenn Sie wissen, dass Ihr Wert immer höchstens 3 Ziffern nach dem Punkt hat, können Sie dies beheben, indem Sie eine zusätzliche Runde nach multiplizieren mit 1000, etwa ROUND(ROUND(x * 1000, 0), -1).

(Haftungsausschluss: während ich viel Erfahrung mit Schwimmer und Dezimalausgaben in anderen Kontexten zu tun, ich weiß so gut wie nichts über SQL.)

0

Verwenden Sie ein beliebige Genauigkeit Format wie DECIMAL. Auf diese Weise können Sie es der Sprache überlassen, um es richtig zu machen (oder falsch, je nachdem).

+0

Offensichtlich, ja. Leider, wie in der Frage angegeben, ist dies keine Option. – Heinzi

+0

Wirklich? Ich dachte, Sie hätten es in Punkt 1 oben umgesetzt. –

+0

Ah, ok, dann habe ich deine Antwort missverstanden; Ich dachte, Sie verweisen auf die Änderung des Datentyps in der Datenbank. Danke für die Klarstellung. – Heinzi

0

I die Schwimmersäule richtig mit dem folgenden Befehl abzurunden verwaltet:

SELECT CONVERT (float, ROUND (ROUND (CONVERT (dezimal (38,14), float_column_name), 3), 2))

2

Alte Frage, aber ich bin überrascht, dass die normale Praxis hier nicht erwähnt wird, also füge ich es einfach hinzu. Normalerweise würden Sie eine kleine Menge hinzufügen, von der Sie wissen, dass sie viel kleiner ist als die Genauigkeit der Zahlen, mit denen Sie arbeiten, z. wie folgt:

SELECT ROUND(6.925e0 + 1e-7, 2) 

Natürlich muss die hinzugefügte Menge größer sein als die Genauigkeit des Gleitkomma-Typs, der verwendet wird.

+0

Wow, das klingt nach einer wirklich einfachen und effektiven Lösung. Vielen Dank! Ist diese Idee irgendwo dokumentiert? (Sie erwähnten, dass es "normale Praxis" ist ...) – Heinzi

+0

Nun, ich weiß nichts über Dokumentation, aber ich benutze es seit 25 Jahren. Die Einschränkung besteht natürlich darin, dass Sie den Nummernbereich kennen müssen, mit dem Sie arbeiten. Aber in den meisten praktischen Situationen und sicherlich in der Rundung ist das der Fall. – fishinear