2014-06-25 4 views
6

Während der Arbeit mit str() -Funktion fand ich, dass es in einigen Fällen falsch rundet, während round() Funktion wie erwartet funktioniert. Sehen Sie sich das Beispiel an:SQL str() vs Runde() Funktion

declare @v decimal(18,2) = 29.95 
select str(@v, 18, 1) 
--29.9 
select round(@v, 1) 
--30.00 

set @v = 39.95 
select str(@v, 18, 1) 
--40.00 
select round(@v, 1) 
--40.00 

Kann jemand erklären, warum es passiert?

EDIT1: I verschiedene Abhilfen mit den folgenden Basiscode geprüft:

declare @v decimal(18,2) = 9.95 
declare @r varchar(100) 
declare @c int = 1000000 
declare @ms int 
declare @dt datetime2 

set @dt = sysdatetime() 

while @c > 0 
begin 
    set @r = --different roundings 
    set @c = @c - 1 
end 

set @ms = DATEDIFF(ms, @dt, sysdatetime()) 
select @ms, @r 

Option 1 (der ursprüngliche, rundet fälschlicherweise in einigen Fällen):

str(@v, 18, 1) 

Option 2 (leicht modifiziert aber richtig abgerundet):

str(round(@v, 1), 18, 1) 

Option 3 (Doppelumwandlung und Rundung):

convert(varchar(20), convert(decimal(18,1), round(@v, 1))) 

Option 4 (nur Doppel conversion):

convert(varchar(20), convert(decimal(18,1), @v)) 

Ergebnisse: Option 1 und 2 sind in etwa 2-mal langsamer als die letzten zwei, aber das Ergebnis ist rechtsbündig. Am schnellsten ist Option 4.

+1

[Was jeder Computer Wissenschaftler sollten über Gleitkommaarithmetik Bescheid wissen (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – GarethD

+0

Vielen Dank! Es ist ein wirklich guter Artikel! –

Antwort

1

Der Parameter str() ist ein Gleitkomma, daher wird der Dezimalwert implizit in einen Gleitkommawert (53) konvertiert, der dann in eine Zeichenfolge konvertiert wird. Sie sehen also einen Gleitkomma-Rundungsfehler.

Machen Sie eine kleine Änderung an Ihrer Abfrage und Sie können sehen, was im tatsächlichen Ausführungsplan passiert.

declare @v decimal(18,2) = 29.95 
select top(1) str(@v, 18, 1) 
<ScalarOperator ScalarString="str(CONVERT_IMPLICIT(float(53),[@v],0),(18),(1))"> 
+0

Wie verwende ich die letzte Codezeile, die Sie geschrieben haben? Und was wäre die Problemumgehung, damit ich Varchar bei bestmöglicher Performance richtig gerundet bekomme? –

+0

@DmitryTimofeev Sie sollten die letzte Zeile nicht verwenden. Das ist die Antwort auf Ihr "Warum es passiert?". –

+0

Ich kam eigentlich zu dem gleichen :), nur dachte, vielleicht gibt es eine bessere Lösung. –

0

Syntax von STR(); STR (float_expression [, length [, decimal]]) sagt eindeutig, dass die Zahl ein float_expression ist. Deshalb unabhängig von der Zahl, die Sie geben zunächst zu einem FLOAT umgewandelt wird (n), in den Standardwert von n = 53

So

SELECT STR (4.65,5,1), STR SELECT (3.65,5 1)

,

entspricht:

SELECT STR (CAST (4,65 AS FLOAT (53)), 5,1), STR (CAST (3,65 AS FLOAT (53)), 5,1)

Wenn Sie n angeben, sagen Sie n = 4, wird es die Antwort geben, die Sie erwarten (dh; 4.7 und 3.7)

SELECT STR (CAST (4,65 AS FLOAT (4)), 5,1), STR (CAST (3,65 AS FLOAT (4)), 5,1) --4.7, 3,7

+0

Dies liefert immer noch ein falsches Ergebnis: deklarieren @v dezimal (18,2) = 9,95 STR AUS (CAST (@v AS FLOAT (53)), 5,1) STRG (CAST (@v AS FLOAT (4)), 5,1) –