2009-07-02 10 views
9

Ho eseguito alcuni test con calcoli in virgola mobile per ridurre al minimo la perdita di precisione. Mi sono imbattuto in un fenomeno che voglio mostrare qui e spero di ottenere una spiegazione.SQL Server: calcolo con valori letterali numerici

Quando scrivo

print 1.0/(1.0/60.0) 

il risultato è

60.0024000960 

Quando scrivo la stessa formula e fare cast esplicito al float

print cast(1.0 as float)/(cast(1.0 as float)/cast(60.0 as float)) 

il risultato è

60 

Fino ad ora ho pensato che letterali numerici con decimali vengono trattati automaticamente come float valori con la precisione adeguata. La trasmissione a real mostra lo stesso risultato della trasmissione a float.

  • C'è qualche documentazione su come SQL Server valuta i valori letterali numerici?
  • Di quali tipi di dati sono quei letterali?
  • Devo davvero lanciarli su float ottenere una precisione migliore (che suona come un'ironia per me :)?
  • C'è un modo più semplice di ingombrare le mie formule con i cast?

risposta

9

SQL Server utilizza il tipo di dati più piccolo possibile.

Quando si esegue questo script

SELECT SQL_VARIANT_PROPERTY(1.0, 'BaseType') 
SELECT SQL_VARIANT_PROPERTY(1.0, 'Precision') 
SELECT SQL_VARIANT_PROPERTY(1.0, 'Scale') 
SELECT SQL_VARIANT_PROPERTY(1.0, 'TotalBytes') 

vedrai che SQL Server implicitamente utilizzato un NUMERICO (2, 1) tipo di dati.
La divisione di 60,0 converte il risultato in NUMERICO (8, 6).
Il calcolo finale converte il risultato in NUMERICO (17, 10).


Modifica

Tratto da linea di SQL Server Data Type Conversion

In istruzioni Transact-SQL, una costante con un punto decimale viene automaticamente convertito in un valore dato numerico, utilizzando la precisione minima e la scala necessaria. Ad esempio, la costante 12.345 viene convertito in un valore numerico con una precisione di 5 e una scala di 3.

+0

Ah .. Non conoscevo questo metodo fino ad ora. Grazie mille :) – VVS

+0

Quindi non c'è modo di aggirare un cast esplicito per ottenere il risultato atteso? C'è un altro modo per dire a SQL Server che un determinato valore letterale è di tipo (ad es. Float? – VVS

+0

@VVS, oltre a un cast esplicito come si sta già facendo, non che io sappia. SQL Server interpreta sempre il valore come numerico.Il problema può essere alleviato un po 'se tu stesso aggiungi precisione ai tuoi valori come 1.000000000000000000000000000000000000. –

4

Sì, avete spesso di gettare loro di galleggiare ottenere una migliore precisione.Il mio prendere su di esso:

For better precision cast decimals before calculations

+0

La motivazione dell'articolo è imperfetta. È basato su alcuni valori arbitrari. Sarebbe facile trovare un contro esempio a favore del tipo decimale. Queste affermazioni indiane basate su un esempio empirico sono trappole in sé. – f470071

3

penso che dovrebbe essere compreso che cosa sta succedendo dietro le quinte per riferimento futuro in casi analoghi.

I valori numerici letterali con punto decimale esclusa la notazione scientifica rappresentano il tipo di dati decimale che viene archiviato come il tipo Decimale più piccolo possibile. Stessa citazione come Lieven Keersmaekers di da: https://msdn.microsoft.com/en-us/library/ms191530%28SQL.90%29.aspx#_decimal

In istruzioni SQL, una costante con un punto decimale viene automaticamente convertito in un valore dato numerico, utilizzando il minimo precisione e scala necessaria. Ad esempio, la costante è 12.345 convertito in un valore numerico con una precisione di 5 e una scala di 3.

Gli zeri finali a destra del punto decimale specificare scala. Gli zero iniziali a sinistra del punto decimale vengono ignorati.

Alcuni esempi:

1.0 -> Decimal(2,1) 
60.0 -> Decimal(3,1) 
1.00 -> Decimal(3,2) 
01.0 -> Decimal (2,1) 

Un altro punto da considerare è Data Type precedence. Quando un operatore combina due espressioni di diversi tipi di dati, le regole per la precedenza sul tipo di dati specificano che il tipo di dati con la precedenza più bassa viene convertito nel tipo di dati con la precedenza più alta. E ancora un altro punto da considerare è se eseguiamo operazioni aritmetiche su tipi decimali che il tipo Decimale risultante, vale a dire precisione e scala dipendono da entrambi gli operandi e l'operazione stessa. Questo è descritto nel documento Precision, Scale, and Length.

Quindi, una parte della vostra espressione tra parentesi

(1.0/60.0) is evaluated to 0.016666 and the resulting type is Decimal (8,6) 

utilizzando sopra le regole circa precisione e la scala di espressioni decimali. Inoltre viene utilizzato l'arrotondamento o l'arrotondamento del banchiere. È importante notare che vengono utilizzati arrotondamenti diversi per il tipo decimale e float. Se continuiamo l'espressione

1.0/0.016666 is evaluated to 60.002400096 and the resulting type is Decimal (17,10) 

Così la parte della differenza è dovuta alla differente arrotondamento usato per i tipi decimali rispetto a galleggiante.

In base alle regole precedenti, sarebbe sufficiente utilizzare una sola fusione tra parentesi. Ogni altro letterale sarà promosso a fluttuare secondo le regole di Precedenza del tipo di dati.

1.0/(1.0/cast(60.0 as float)) 

E un'altra cosa IMPORTANTE. Anche questa espressione mobile non calcola il risultato esatto. È solo così che il front-end (SSMS o qualsiasi altra cosa) arrotonda il valore a (credo) di 6 cifre di precisione e poi tronca gli zeri finali. Quindi, ad esempio, 1.000001 diventa 1.

Semplice, non è vero?