Warum ist 199.96 - 0 = 200 in SQL?
Habe ich einige clients, die sich seltsam Rechnungen. Ich war in der Lage zu isolieren den Kern des Problems:
SELECT 199.96 - (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))) -- 200 what the?
SELECT 199.96 - (0.0 * FLOOR(1.0 * CAST(199.96 AS DECIMAL(19, 4)))) -- 199.96
SELECT 199.96 - (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * 199.96)) -- 199.96
SELECT 199.96 - (CAST(0.0 AS DECIMAL(19, 4)) * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))) -- 199.96
SELECT 199.96 - (CAST(0.0 AS DECIMAL(19, 4)) * FLOOR(1.0 * CAST(199.96 AS DECIMAL(19, 4)))) -- 199.96
SELECT 199.96 - (CAST(0.0 AS DECIMAL(19, 4)) * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * 199.96)) -- 199.96
-- It gets weirder...
SELECT (0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))) -- 0
SELECT (0 * FLOOR(1.0 * CAST(199.96 AS DECIMAL(19, 4)))) -- 0
SELECT (0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * 199.96)) -- 0
-- so... ... 199.06 - 0 equals 200... ... right???
SELECT 199.96 - 0 -- 199.96 ...NO....
Hat jemand eine Ahnung, was zum Teufel ist hier passiert? Ich meine, es hat sicher etwas zu tun mit dem decimal-Datentyp, aber ich kann nicht wirklich wickeln Sie meinen Kopf herum...
Gab es eine Menge Verwirrung darüber, von welchen Datentyp die Anzahl Literale wurden, so entschied ich mich um zu zeigen das wahre line:
PS.SharePrice - (CAST((@InstallmentCount - 1) AS DECIMAL(19, 4)) * CAST(FLOOR(@InstallmentPercent * PS.SharePrice) AS DECIMAL(19, 4))))
PS.SharePrice DECIMAL(19, 4)
@InstallmentCount INT
@InstallmentPercent DECIMAL(19, 4)
Machte ich sicher, dass das Ergebnis jeder operation mit einem Operanden eines anderen Typs als DECIMAL(19, 4)
gewirkt wird ausdrücklich vor der Anwendung, um den äußeren Kontext.
Dennoch bleibt das Ergebnis 200.00
.
Habe ich jetzt erstellt ein eingekocht Probe, die Sie Jungs können ausführen auf Ihrem computer.
DECLARE @InstallmentIndex INT = 1
DECLARE @InstallmentCount INT = 1
DECLARE @InstallmentPercent DECIMAL(19, 4) = 1.0
DECLARE @PS TABLE (SharePrice DECIMAL(19, 4))
INSERT INTO @PS (SharePrice) VALUES (599.96)
-- 2000
SELECT
IIF(@InstallmentIndex < @InstallmentCount,
FLOOR(@InstallmentPercent * PS.SharePrice),
1999.96)
FROM @PS PS
-- 2000
SELECT
IIF(@InstallmentIndex < @InstallmentCount,
FLOOR(@InstallmentPercent * CAST(599.96 AS DECIMAL(19, 4))),
1999.96)
FROM @PS PS
-- 1996.96
SELECT
IIF(@InstallmentIndex < @InstallmentCount,
FLOOR(@InstallmentPercent * 599.96),
1999.96)
FROM @PS PS
-- Funny enough - with this sample explicitly converting EVERYTHING to DECIMAL(19, 4) - it still doesn't work...
-- 2000
SELECT
IIF(@InstallmentIndex < @InstallmentCount,
FLOOR(@InstallmentPercent * CAST(199.96 AS DECIMAL(19, 4))),
CAST(1999.96 AS DECIMAL(19, 4)))
FROM @PS PS
Jetzt habe ich etwas...
-- 2000
SELECT
IIF(1 = 2,
FLOOR(CAST(1.0 AS decimal(19, 4)) * CAST(199.96 AS DECIMAL(19, 4))),
CAST(1999.96 AS DECIMAL(19, 4)))
-- 1999.9600
SELECT
IIF(1 = 2,
CAST(FLOOR(CAST(1.0 AS decimal(19, 4)) * CAST(199.96 AS DECIMAL(19, 4))) AS INT),
CAST(1999.96 AS DECIMAL(19, 4)))
Was solls - Etage soll eine Ganzzahl zurück sowieso. Was ist denn hier Los? 😀
Ich denke, dass ich es nun geschafft wirklich Kochen es nach unten, um die Wesen 😀
-- 1.96
SELECT IIF(1 = 2,
CAST(1.0 AS DECIMAL (36, 0)),
CAST(1.96 AS DECIMAL(19, 4))
)
-- 2.0
SELECT IIF(1 = 2,
CAST(1.0 AS DECIMAL (37, 0)),
CAST(1.96 AS DECIMAL(19, 4))
)
-- 2
SELECT IIF(1 = 2,
CAST(1.0 AS DECIMAL (38, 0)),
CAST(1.96 AS DECIMAL(19, 4))
)
- 199.96 -0 nicht gleich 200. Alle diejenigen wirft, und die Böden mit impliziten Konvertierungen, die floating-point und zurück sind doch garantiert in der Folge der Präzision Verlust.
- was ist der Typ von
199.96
durch die Art und Weise? Es ist nicht eine Dezimalzahl. Es konvertiert implizit einen Typ, der kompatibel mit den anderen Operanden - 199.96 hat der Typ decimal(19, 4) in der realen Probe.
- nur, wenn es kam aus einer Tabelle. Als literal in einem Ausdruck ist es wahrscheinlich eine
float
- Die Antwort von Tapakah Ua (mit meinen änderungen) erklärt, warum das neue sample nicht funktioniert, selbst wenn alles Stimmen, um
decimal(19,4)
. Sql Server mutieren die Typen während der Arithmetik, und in diesem Fall das Ergebnis von der Art der mutation die überläufe der größten Art Sql-Server unterstützt, welche Kräfte es zu entfernen, die Waage. So dass man am Ende bis Rundung die199.96
Wert. - Oh... und
Floor()
hat nicht kehren Sie einint
. Es gibt die gleiche Art wie die ursprüngliche Ausdruck, mit dem dezimalen Teil entfernt. Für den rest, derIIF()
Funktion führt die Art mit der höchsten Priorität (docs.microsoft.com/en-us/sql/t-sql/functions/...). Also das 2. Beispiel, wo Sie cast zu int, die höhere Priorität ist die einfache Besetzung als numeric(19,4). - Tolle Antwort (wer wusste, Sie könnte prüfen, Metadaten eines sql-Variante?) aber ich 2012 die erwarteten Ergebnisse erhalten (199.96).
- MS SQL Server und Sybase beide nennen Ihre Sprache T-SQL-denn an einer Stelle waren Sie arbeiten zusammen auf der gleichen Basis-Produkt. Siehe diese MS blog-post für details.
- Ich bin nicht allzu vertraut mit MS SQL, aber ich muss sagen, dass der Blick auf all diese cast-Operationen und so weiter schnell meine Aufmerksamkeit gefangen.. also ich muss link, denn niemand sollte jemals mit
float
ing-point-Typen auf die Währung. - Nicht verwenden, floating-point-zahlen, mit Geld umzugehen.
- Und für diese Angelegenheit, vermeiden Sie SQL Server, wenn möglich 🙂
- Keine floating-point-Datentypen verwendet, die in die Frage. Und ich denke, dass keine floating-point-operation verwendet wird, in jedem Beispiel.
- Sind nicht die
1.0
s angefangen als floating-point-zahlen, bevor dieas decimal(...)
Teil? - ein
1.0
ist eine dezimale Konstante1e0
ist eine float-Konstante.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Brauche ich vom Auspacken ein bisschen, damit ich sehen kann, was Los ist:
Nun wollen wir sehen, genau das, was Arten von SQL Server für jede Seite der Subtraktion:
Ergebnisse:
So
199.96
istnumeric(5,2)
und mehrFloor(Cast(etc))
istnumeric(38,1)
.Den Regeln für die resultierende Genauigkeit und Skalierung von einer Subtraktion (dh:
e1 - e2
) wie folgt Aussehen:Auswertet, wie diese:
Können Sie auch die link Regeln, um herauszufinden, wo die
numeric(38,1)
kam in den ersten Platz (Tipp: man multipliziert zwei Präzisions-19-Werte).Aber:
Oops. Die Präzision ist 40. Wir haben zu reduzieren, und da die Verringerung der Präzision sollte immer schneiden Sie die am wenigsten signifikanten Ziffern, bedeutet die Reduzierung der Skala, zu. Endgültigen Typ für den Ausdruck wird
numeric(38,0)
, die für199.96
Runden zu200
.Können Sie wahrscheinlich dieses Problem beheben durch Umzug und die Zusammenlegung der
CAST()
Vorgänge aus dem inneren des großen Ausdruck eineCAST()
um den gesamten Ausdruck Ergebnis. Also diese:Wird:
Könnte ich sogar entfernen Sie das äußere werfen, wie gut.
Lernen wir hier, die wir wählen sollten Typen übereinstimmen, die Genauigkeit und die Dezimalstellen haben wir eigentlich jetzt, anstatt das erwartete Ergebnis. Es macht keinen Sinn, einfach zu gehen, für die große Präzision der zahlen, da SQL Server mutieren diejenigen Arten, die während der arithmetischen Operationen zu vermeiden, dass die überläuft.
Weitere Informationen:
Sql_Variant_Property()
Halten Sie ein Auge auf Datentypen beteiligt, für die folgende Aussage:
NUMERIC(19, 4) * NUMERIC(19, 4)
istNUMERIC(38, 7)
(siehe unten)FLOOR(NUMERIC(38, 7))
istNUMERIC(38, 0)
(siehe unten)0.0
istNUMERIC(1, 1)
NUMERIC(1, 1) * NUMERIC(38, 0)
istNUMERIC(38, 1)
199.96
istNUMERIC(5, 2)
NUMERIC(5, 2) - NUMERIC(38, 1)
istNUMERIC(38, 1)
(siehe unten)Dies erklärt, warum Sie am Ende mit
200.0
(eine Stelle nach dem Dezimaltrennzeichen, nicht null) statt199.96
.Hinweise:
BODEN
gibt die größte ganze Zahl kleiner als oder gleich dem angegebenen numerischen Ausdruck und Ergebnis hat den gleichen Typ wie der input. Es gibt INT für INT, FLOAT für FLOAT-und NUMERIC(x, 0), NUMERIC(x, y).Laut der Algorithmus:
Die Beschreibung enthält auch die details, wie genau die Waage ist reduziert von innen addition und Multiplikation Operationen. Basierend auf dieser Beschreibung:
NUMERIC(19, 4) * NUMERIC(19, 4)
istNUMERIC(39, 8)
und gespannt zuNUMERIC(38, 7)
NUMERIC(1, 1) * NUMERIC(38, 0)
istNUMERIC(40, 1)
und gespannt zuNUMERIC(38, 1)
NUMERIC(5, 2) - NUMERIC(38, 1)
istNUMERIC(40, 2)
und gespannt zuNUMERIC(38, 1)
Hier ist mein Versuch, die zur Umsetzung des Algorithmus in JavaScript. Ich habe überprüft die Ergebnisse mit SQL Server. Es beantwortet die Wesen Teil Ihrer Frage.
JS: