Ich habe einige Kunden, die seltsame Rechnungen bekommen. Ich konnte das Kernproblem eingrenzen:
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 hier passiert? Ich meine, es hat sicherlich etwas mit dem dezimalen Datentyp zu tun, aber ich kann mich nicht wirklich darum kümmern ...
Es gab viel Verwirrung darüber, welcher Datentyp die Zahlenliterale waren, also beschloss ich, die reale Linie zu zeigen:
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)
Ich habe dafür gesorgt, dass das Ergebnis jeder Operation einen Operanden eines anderen Typs als hat DECIMAL(19, 4)
explizit umgewandelt hat, bevor ihn auf den äußeren Kontext anwende.
Trotzdem bleibt das Ergebnis 200.00
.
Ich habe jetzt ein Beispiel erstellt, das ihr auf eurem Computer ausführen könnt.
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 zum Teufel - Boden soll sowieso eine ganze Zahl zurückgeben. Was ist denn hier los? :-D
Ich glaube, ich habe es jetzt wirklich geschafft, es auf das Wesentliche zu reduzieren :-D
-- 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))
)
quelle
float
Floor()
kommt nicht zurückint
. Es gibt den gleichen Typ wie der ursprüngliche Ausdruck zurück , wobei der Dezimalteil entfernt wird. Im ÜbrigenIIF()
ergibt die Funktion den Typ mit der höchsten Priorität ( docs.microsoft.com/en-us/sql/t-sql/functions/… ). Bei der zweiten Stichprobe, bei der Sie in int umwandeln, ist die einfache Umwandlung als numerisch (19,4) die höhere Priorität.float
ing Punkttypen Griff Währung .Antworten:
Ich muss damit beginnen, dies ein wenig auszupacken, damit ich sehen kann, was los ist:
Lassen Sie uns nun genau sehen, welche Typen SQL Server für jede Seite der Subtraktionsoperation verwendet:
Ergebnisse:
So
199.96
istnumeric(5,2)
und je längerFloor(Cast(etc))
istnumeric(38,1)
.Die Regeln für die resultierende Genauigkeit und Skalierung einer Subtraktionsoperation (dh :)
e1 - e2
sehen folgendermaßen aus:Das bewertet sich so:
Sie können auch den Link Regeln verwenden, um herauszufinden, woher die
numeric(38,1)
ursprünglich kamen (Hinweis: Sie haben zwei Werte mit einer Genauigkeit von 19 multipliziert).Aber:
Hoppla. Die Genauigkeit beträgt 40. Wir müssen sie reduzieren, und da die Reduzierung der Genauigkeit immer die niedrigstwertigen Stellen abschneiden sollte, bedeutet dies auch eine Reduzierung der Skalierung. Der endgültige resultierende Typ für den Ausdruck ist
numeric(38,0)
, welcher für199.96
Runden zu200
.Sie können dies wahrscheinlich beheben, indem Sie die
CAST()
Operationen aus dem großen Ausdruck heraus in eineCAST()
um das gesamte Ausdrucksergebnis verschieben und konsolidieren . Also das:Wird:
Ich könnte sogar den äußeren Gips entfernen.
Wir lernen hier sollten wir Typen wählen , die Präzision anzupassen und skalieren wir tatsächlich haben gerade jetzt , und nicht das erwartete Ergebnis. Es ist nicht sinnvoll, nur Zahlen mit hoher Genauigkeit zu verwenden, da SQL Server diese Typen während arithmetischer Operationen mutiert, um Überläufe zu vermeiden.
Mehr Informationen:
Sql_Variant_Property()
quelle
Behalten Sie die beteiligten Datentypen für die folgende Aussage im Auge:
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
200.0
( eine Ziffer nach der Dezimalstelle, nicht Null ) anstelle von erhalten199.96
.Anmerkungen:
FLOOR
Gibt die größte Ganzzahl zurück, die kleiner oder gleich dem angegebenen numerischen Ausdruck ist, und das Ergebnis hat den gleichen Typ wie die Eingabe. Es gibt INT für INT, FLOAT für FLOAT und NUMERIC (x, 0) für NUMERIC (x, y) zurück.Nach dem Algorithmus :
Die Beschreibung enthält auch Einzelheiten darüber, wie genau die Skala innerhalb von Additions- und Multiplikationsoperationen reduziert wird. Basierend auf dieser Beschreibung:
NUMERIC(19, 4) * NUMERIC(19, 4)
istNUMERIC(39, 8)
und festgeklemmtNUMERIC(38, 7)
NUMERIC(1, 1) * NUMERIC(38, 0)
istNUMERIC(40, 1)
und festgeklemmtNUMERIC(38, 1)
NUMERIC(5, 2) - NUMERIC(38, 1)
istNUMERIC(40, 2)
und festgeklemmtNUMERIC(38, 1)
Hier ist mein Versuch, den Algorithmus in JavaScript zu implementieren. Ich habe die Ergebnisse mit SQL Server abgeglichen. Es beantwortet den wesentlichen Teil Ihrer Frage.
quelle