2010-08-05 12 views
6

Quindi mi sto tuffando in questo problema ... Ho un'applicazione web molto utilizzata che, per la prima volta in 2 anni, non ha eseguito un controllo di uguaglianza su due doppi utilizzando la funzione di uguaglianza che un collega ha detto stava anche usando da anni.Problemi con il controllo dell'uguaglianza di due doppi in .NET - cosa c'è di sbagliato in questo metodo?

L'obiettivo della funzione che sto per incollare qui è di confrontare due valori doppi a 4 cifre di precisione e restituire i risultati del confronto. Per motivi di illustrazione, i miei valori sono:

Dim double1 As Double = 0.14625000000000002 ' The result of a calculation 
Dim double2 As Double = 0.14625 ' A value that was looked up in a DB 

Se li passo in questa funzione:

Public Shared Function AreEqual(ByVal double1 As Double, ByVal double2 As Double) As Boolean 

    Return (CType(double1 * 10000, Long) = CType(double2 * 10000, Long)) 

End Function 

il paragone non regge. Dopo la moltiplicazione e cast a Long, il paragone finisce per essere:

Return 1463 = 1462 

Sono una specie di rispondere alla mia domanda proprio qui, ma posso vedere che double1 è dentro la precisione di un doppio (17 cifre) e il cast funziona correttamente.

La mia prima vera domanda è: se cambio la riga sopra alla seguente, perché funziona correttamente (restituisce True)?

Return (CType(CType(double1, Decimal) * 10000, Long) = _ 
    CType(CType(double2, Decimal) * 10000, Long)) 

Does not Decimal avere una precisione ancora maggiore, in tal modo il cast a Long dovrebbe essere ancora 1463, e il confronto tornare False? Penso che sto avendo una scoreggia cerebrale su questa roba ...

In secondo luogo, se dovessi cambiare questa funzione per rendere il confronto che sto cercando più preciso o meno soggetto a errori, consiglieresti di cambiarlo in qualcosa di molto più semplice? Per esempio:

Return (Math.Abs(double1 - double2) < 0.0001) 

sarei pazzo a provare qualcosa di simile:

Return (double1.ToString("N5").Equals(double2.ToString("N5"))) 

(non avrei mai fare quanto sopra, sono solo curioso di sapere le vostre reazioni Sarebbe terribilmente inefficiente nel mio. applicazione.)

In ogni caso, se qualcuno potesse far luce sulla differenza che sto vedendo tra il casting Double se Decimal s a Long, sarebbe fantastico.

Grazie!

risposta

3

Affidarsi a un cast in questa situazione è soggetto a errori, come hai scoperto, a seconda delle regole utilizzate durante il casting, potresti non ottenere il numero che ti aspetti.

Vi consiglio caldamente di scrivere il codice di confronto senza cast. La tua linea Math.Abs ​​è perfettamente a posto.

Per quanto riguarda la prima domanda:

La mia prima vera domanda è: Se cambio la riga sopra al seguente, perché funziona correttamente (restituisce True)?

Il motivo è che il cast da Doppio a Decimale perde precisione, con un confronto tra 0,1425 e 0,1425.

+0

"... il cast dal doppio al decimale sta perdendo precisione ..." Questo mi fa male al cervello. Perché la precisione si perde? È perché un decimale è una base diversa rispetto al doppio; cioè perché i numeri sono memorizzati in modo diverso? –

+0

Non importa. Sto scrivendo senza pensare. Intendevo che il cast si stava restringendo (cioè perdendo informazioni), il che è vero, ma non riesco a capire perché. In base alla documentazione Decimal, la trasmissione da Double a Decimal non perde nulla, ma il debugger mostra la porzione minuscola che scompare: ad esempio Dim test As New Decimal (0,14625000000000002) restituisce un test contenente 0,14625. Tuttavia, Dim test As Decimal = 0.14625000000000002D funziona. Andrò a leggere l'articolo collegato sull'aritmetica in virgola mobile e smetterò di rispondere a questo punto ... –

+2

E un altro po 'di ricerca attraverso la documentazione mostra: "Questo costruttore arrotonda il valore a 15 cifre significative usando l'arrotondamento al più vicino. il numero ha più di 15 cifre e le cifre meno significative sono zero." –

4
+0

Non sono sicuro che il problema sia un fraintendimento dell'aritmetica in virgola mobile. Il problema è usare un cast per provare e correggere l'aritmetica in virgola mobile. –

+0

@Jason Berkan: l'uso del cast dimostra l'incomprensione. –

+0

Beh, come ho detto, il codice è di un collega e ha creduto che fosse adeguato - mi è stato assegnato il compito di eseguire il debugging. La soluzione 'Math.Abs ​​()' che ho inserito nell'OP era il mio suggerimento come soluzione, volevo solo capire di più sul problema del casting di tipo quindi posso evitarlo in futuro. Gli esempi nel mio post erano semplicemente il risultato della mia curiosità per il comportamento di casting, qualcosa per cui apparentemente ho bisogno di fare più ricerche. –

2

Quando si utilizza CType, stai dicendo il vostro programma "Non mi importa come tutto i numeri, basta assicurarsi il risultato è questo altro tipo". Non è esattamente quello che vuoi dire al tuo programma quando confronti i numeri.

Confrontando i numeri in virgola mobile è un dolore e non avrei mai fidarsi di una funzione Round in qualsiasi lingua se non si conosce esattamente come si comporta (ad esempio, a volte arrotonda .5 su e giù a volte, a seconda del precedente numero ... è un casino).

In .NET, potrei effettivamente utilizzare Math.Truncate() dopo aver moltiplicato il mio doppio valore.Quindi, Math.Truncate(.14625 * 10000) (che è Math.Truncate(1462.5)) equivale a 1462 perché elimina tutti i valori decimali. Usando Truncate() con i dati del tuo esempio, entrambi i valori finirebbero per essere uguali perché 1) rimangono doppi e 2) hai fatto in modo che il decimale fosse rimosso da ciascuno.

In realtà non credo che il confronto tra stringhe sia molto brutto in questa situazione, dal momento che il confronto in virgola mobile è piuttosto spiacevole di per sé. Certo, se si confrontano i numeri, è probabilmente meglio attenersi a tipi numerici, ma l'utilizzo del confronto tra stringhe è un'altra opzione.

+0

Non sono d'accordo, il confronto tra stringhe è troppo complesso. La specifica per la funzione è che "AreEqual" dovrebbe essere "True" se la differenza tra i numeri è inferiore a "0,0001". In altre parole, 'AreEqual = (Math.Abs ​​(double1 - double2) <0.0001)' – MarkJ

+0

@Mark Non sto suggerendo che * dovresti * usare il confronto String, ma non penso che il confronto con String sia tanto brutto quanto usando un valore numerico conversioni per fare l'arrotondamento per te. Tuttavia, il metodo che hai specificato sembra essere molto meglio rispetto al confronto tra stringhe o all'imply round-by-conversion, quindi probabilmente andrei con quello che hai specificato. –