2010-02-10 14 views
8

Utilizzo di Java su un PC Windows 7 (non è sicuro se ciò è importante) e chiamata Math.cos() su valori che dovrebbero restituire 0 (come pi/2) anziché restituisce piccoli valori, ma valori piccoli che, a meno di non fraintendermi, sono molto più grandi di 1 ulp off da zero.Java Math.cos() Il metodo non restituisce 0 quando previsto

Math.cos(Math.PI/2) = 6.123233995736766E-17 
Math.ulp(Math.cos(Math.PI/2)) = 1.232595164407831E-32 

È questo, infatti, all'interno di 1 ULP e sto semplicemente confuso? E questo sarebbe un metodo wrapper accettabile per risolvere questa inesattezza minore?

public static double cos(double a){ 
    double temp = Math.abs(a % Math.PI); 
    if(temp == Math.PI/2) 
     return 0; 
    return Math.cos(a); 
} 
+0

@ dimo414 : Innanzitutto un commento sul tuo "non so se questo è importante". Il comportamento della maggior parte delle operazioni matematiche non è definito con precisione in modo che il sistema operativo e la CPU possano avere importanza. Se si desidera un'operazione matematica il cui comportamento è strettamente definito (che probabilmente è più semplice da risolvere) si desidera utilizzare StrictMath, non Math (ovviamente le operazioni StrictMath devono essere più lente perché non possono utilizzare l'operazione hardware accelerata disponibile nella CPU). – SyntaxT3rr0r

+0

Math.cos() è solo un wrapper per StrictMath.cos(), che è a sua volta una funzione nativa. – dimo414

risposta

10

Non dimenticare che Math.PI/2 è un'approssimazione. Non sarà esattamente pi/2, quindi il risultato di cos(Math.PI/2) non sta per essere esattamente 0. Math.cos potrebbe restituire una versione abbastanza precisa del coseno del valore esatto restituito da calcolo Math.PI/2.

+0

Verissimo, ma detto questo, mi sembra intuitivo che se Math.PI è il doppio più vicino a Pi, allora Math.PI/2 dovrebbe essere il doppio più vicino a Pi/2. E poiché con la definizione di cos() Pi/2 e 3Pi/2 sono pari a zero, sembra che quando la doppia approssimazione più vicina di questi numeri sia passata a Math.cos(), dovrebbe anche essere zero. Forse questo è semplicemente sbagliato, (che è il punto cruciale della mia domanda), ma questo mi rende intuitivo. – dimo414

7

Non si dovrebbe mai usare == con il doppio. Devi sempre fare entro il margine di errore. 10 -17 è una buona precisione se me lo chiedi. Ulp figura 10 -32 è solo precisson doppio cioè in 10 -17 ordine di grandezza, come 2.220446049250313E-16 è la precisione del numero 10 grandezza.

+0

@Slartibartfsat: +1, esattamente. Mi hai battuto sul tempo. – SyntaxT3rr0r

+0

Il margine di errore deve essere almeno due volte l'ulp (PI/2), perché questa è l'imprecisione di PI/2. cos ha una derivata di -1 in quel punto, quindi l'imprecisione di PI/2 si riflette nel risultato, più l'imprecisione di cos. – starblue

+0

Normalmente sarei d'accordo con te, ma voglio limitare la mia eccezione al minor numero possibile di casi, quindi se l'input è pari/molto/leggermente fuori Math.PI/2 allora lo lascio calcolare in modo nativo, ma in il caso speciale in cui è esattamente Math.PI/2 o uno dei suoi multipli, mi piacerebbe che fosse esattamente zero. Alcuni test limitati hanno dimostrato che funziona, almeno sul mio computer. Questo ragionamento è ok, o dovrei controllare ancora un intervallo? – dimo414

2

Questo è un errore comune quando si sta iniziando, questo collegamento ha una discussione molto tecnica dei motivi per cui. http://docs.sun.com/source/806-3568/ncg_goldberg.html

Ma nella sua forma più semplice, allo stesso modo che non possiamo rappresentare esattamente 1/3 nel sistema decimale, ci sono valori che non possono essere rappresentati esattamente nel sistema binario