2010-03-03 8 views
7

Siamo bloccati con un database che (sfortunatamente) utilizza float anziché valori decimali. Questo rende l'arrotondamento un po 'difficile. Si consideri il seguente esempio (SQL Server T-SQL):"Mezzo round up" su valori in virgola mobile

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

ROUND fa round half up, ma dal momento che floating point numbers cannot accurately represent decimal numbers, viene visualizzato il risultato "sbagliato" (dal punto di vista dell'utente finale). Capisco perché questo accade.

ho già si avvicinò con due possibili soluzioni (sia di ritorno un galleggiante, che è, purtroppo, anche un requisito):

  1. convertire in un tipo di dati decimali prima di concludere: SELECT CONVERT(float, ROUND(CONVERT(decimal(29,14), 6.925e0), 2))
  2. Multiply fino la terza cifra si trova sul lato sinistro del punto decimale (cioè con precisione rappresentati), e poi fare l'arrotondamento: SELECT ROUND(6.925e0 * 1000, -1)/1000

Quale dovrei scegliere? C'è qualche soluzione migliore? (Purtroppo, non possiamo cambiare i tipi di campo del database a causa di alcune applicazioni legacy accesso alla stessa DB.)

Esiste un ben consolidato migliore soluzione pratica per questo problema (comune?)?

(Ovviamente, la tecnica comune "arrotondamento due volte" non aiuterà qui dal 6,925 è già arrotondato al terzo decimale - per quanto ciò è possibile in un galleggiante.)

+1

Ho taggato questo sia 'sql-server' che' language-agnostic', poiché gli esempi sono in T-SQL ma la domanda per un algoritmo best-practice è generica. – Heinzi

+0

Non esiste un algoritmo o una funzione che possa ottenere ciò che si vuole perché la maggior parte dei float che sono multipli di una base 10 non sono esattamente rappresentabili. Ad esempio, 6.925 è rappresentato come un valore leggermente sopra o sotto, ma mai esatto. In che modo vuoi che sia arrotondato? – hirschhornsalz

risposta

6

La tua prima soluzione sembra più sicuro e sembra anche un approccio concettualmente più vicino al problema: convertire il più presto possibile da virgola mobile a decimale, eseguire tutti i calcoli rilevanti all'interno del tipo decimale, quindi eseguire una conversione dell'ultimo minuto in virgola mobile prima di scrivere nel DB.

Modifica: è probabile che sia ancora necessario eseguire un giro aggiuntivo (ad esempio fino a 3 posizioni decimali o qualsiasi altra applicazione appropriata per l'applicazione) subito dopo aver recuperato il valore float e convertito in valori decimali, per assicurarsi di finire il valore decimale che era effettivamente inteso. 6.925e0 convertito in decimale sarebbe di nuovo probabile (supponendo che il formato decimale abbia> 16 cifre di precisione) per dare qualcosa che è molto vicino, ma non esattamente uguale a, 6.925; un giro in più si prenderà cura di questo.

La seconda soluzione non mi sembra affidabile: cosa succede se il valore memorizzato per 6.925e0 risulta essere, a causa dei soliti problemi di binario in virgola mobile, una piccola quantità troppo piccola? Quindi, dopo la moltiplicazione per 1000, il risultato potrebbe essere ancora un tocco sotto 6925, in modo che il passaggio di arrotondamento si arrotonda anziché aumentare. Se sai che il tuo valore ha sempre al massimo 3 cifre dopo il punto, puoi risolvere il problema eseguendo un ulteriore round dopo il moltiplicando per 1000, ad esempio ROUND(ROUND(x * 1000, 0), -1).

(Disclaimer: mentre Ho un sacco di esperienza nel trattare con galleggiante e le questioni decimali in altri contesti, so quasi niente di SQL.)

0

Usa un formato precisione arbitraria, come decimali. In questo modo puoi lasciarlo alla lingua per farlo bene (o sbagliato, a seconda dei casi).

+0

Ovviamente, sì. Purtroppo, come indicato nella domanda, questa non è un'opzione. – Heinzi

+0

Davvero? Pensavo avessi implementato nel punto 1 sopra. –

+0

Ah, ok, allora ho frainteso la tua risposta; Pensavo che ti stavi riferendo a cambiare il tipo di dati nel database. Grazie per il chiarimento. – Heinzi

0

sono riuscito per girare la colonna galleggiante correttamente utilizzando il seguente comando:

SELEZIONA CONVERT (float, ROUND (ROUND (CONVERT (decimale (38,14), float_column_name), 3), 2))

2

Vecchia domanda, ma sono sorpreso che la normale pratica non sia menzionata qui, quindi la aggiungo. Normalmente, dovresti aggiungere una piccola quantità che sai essere molto più piccola della precisione dei numeri con cui stai lavorando, ad es. In questo modo:

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

Ovviamente l'importo aggiunto deve essere maggiore della precisione del tipo a virgola mobile utilizzato.

+0

Wow, sembra una soluzione davvero semplice ed efficace. Grazie! Questa idea è documentata da qualche parte? (Hai detto che si tratta di "pratica normale" ...) – Heinzi

+0

Beh, non conosco la documentazione, ma la uso da 25 anni. La limitazione, ovviamente, è che è necessario conoscere la gamma di numeri con cui si sta lavorando. Ma nella maggior parte delle situazioni pratiche, e certamente nell'arrotondamento, questo è il caso. – fishinear