[英]How to overcome inaccuracy in Java
當我執行以下程序時,我就知道了精度問題 :
public static void main(String args[])
{
double table[][] = new double[5][4];
int i, j;
for(i = 0, j = 0; i <= 90; i+= 15)
{
if(i == 15 || i == 75)
continue;
table[j][0] = i;
double theta = StrictMath.toRadians((double)i);
table[j][1] = StrictMath.sin(theta);
table[j][2] = StrictMath.cos(theta);
table[j++][3] = StrictMath.tan(theta);
}
System.out.println("angle#sin#cos#tan");
for(i = 0; i < table.length; i++){
for(j = 0; j < table[i].length; j++)
System.out.print(table[i][j] + "\t");
System.out.println();
}
}
輸出為:
angle#sin#cos#tan
0.0 0.0 1.0 0.0
30.0 0.49999999999999994 0.8660254037844387 0.5773502691896257
45.0 0.7071067811865475 0.7071067811865476 0.9999999999999999
60.0 0.8660254037844386 0.5000000000000001 1.7320508075688767
90.0 1.0 6.123233995736766E-17 1.633123935319537E16
(請原諒無組織的輸出)。 我注意到了幾件事:
0.5
存儲為0.49999999999999994
。 1.0
存儲為0.9999999999999999
。 infinity
或undefined
)存儲為1.633123935319537E16
(這是一個非常大的數字)。 自然地,我很困惑地看到輸出(即使在解密輸出之后)。
所以我讀了這篇文章,最好的答案告訴我:
這些精度問題是由於浮點數>點號的內部表示而引起的,您無法做很多事情來避免這種情況。
順便說一句,在運行時打印這些值通常仍會導致正確的結果,至少使用現代C ++編譯器。 對於大多數操作而言,這並不是什么大問題。
2008年10月7日在7:42回答
康拉德·魯道夫
所以,我的問題是:
我應該對結果進行四舍五入嗎? 在那種情況下,我將如何存儲infinity
即Double.POSITIVE_INFINITY
?
您必須對浮點數采取zen *的方法:與其消除錯誤,不如從容應對。
實際上,這通常意味着要執行以下操作:
String.format
指定要顯示的精度(它將為您進行適當的舍入) ==
)。 而是尋找一個足夠小的增量: Math.abs(myValue - expectedValue) <= someSmallError
編輯 :對於無窮大,相同的原理適用,但是要進行一些調整:您必須選擇一些數字以“足夠大”以將其視為無窮大。 再次是因為您必須學會忍受而不是解決不精確的價值觀。 在諸如tan(90度)之類的情況下,雙精度不能以無限的精度存儲π/ 2,因此您的輸入值非常接近(但不完全是90度),因此結果是大,但不是無限。 您可能會問“為什么當您傳遞最接近π/ 2的雙Double.POSITIVE_INFINITY
時,它們不返回Double.POSITIVE_INFINITY
”,但這可能會導致模棱兩可:如果您真的想要該數字的正切而不是90度,該怎么辦? 或者,如果(由於先前的浮點錯誤)您所擁有的東西與π/ 2的距離比最接近的可能值稍遠,但是對於您的需求,它仍然是π/ 2? JDK不會為您做出任意決定,而是會以您的票面價值來對待您接近但並非完全是π/ 2的數字,從而為您帶來大但並非無限的結果。
對於某些操作,尤其是與貨幣相關的操作,您可以使用BigDecimal
消除浮點錯誤:您可以真正表示0.1之類的值(而不是真正接近0.1的值,這是float或double可以做到的最好) 。 但這要慢得多,並且對於諸如sin / cos之類的東西(至少對於內置庫而言)沒有幫助。
*這實際上可能不是禪宗,但在口語上
您必須使用BigDecimal
而不是double
。 不幸的是, StrictMath
不支持BigDecimal
,因此您將不得不使用另一個庫,或者您自己的sin
/ cos
/ tan
。
這是使用任何語言的浮點數所固有的。 實際上,使用任何具有最大固定精度的表示形式都是固有的。
有幾種解決方案。 一種是使用擴展精度數學包-Java通常建議使用BigDecimal
。 BigDecimal
可以處理更多位數的精度,而且-因為它是十進制表示形式,而不是2的補碼表示形式-傾向於以習慣於以10為基數的人不那么驚訝的方式舍入。請注意,不一定使它們更正確 。二進制不能精確表示1/3,但十進制也不能。)
還有擴展精度2的補數浮點表示形式。 Java直接支持float和double(硬件通常也支持它們),但是可以編寫支持更多精度的版本。
當然,任何擴展精度軟件包都會減慢您的計算速度。 因此,除非真正需要它們,否則不應該訴諸於它們。
另一種可能是使用定點二進制而不是浮點。 例如,大多數財務計算的標准解決方案只是簡單地以最小的貨幣單位(美國的便士)為整數來計算,僅針對I來回顯示格式(例如美元和美分) / O。 這也是Java中用於時間的方法-內部時鍾報告整數毫秒(如果使用nanotime調用,則為納秒),這對於大多數實際應用而言,不僅具有足夠的精度,而且具有足夠的值范圍目的。 再次,這意味着舍入趨向於以符合人類期望的方式發生...再次,這與准確性無關,而不是使用戶感到驚訝。 這些表示形式(因為它們以整數或long形式進行處理)允許進行快速計算-實際上比浮點運算要快。
還有其他解決方案,涉及以有理數或其他變體形式進行計算,以在計算成本和精度之間進行折衷。
但是我還必須問...您真的需要比float給您的精度更高的精度嗎? 我知道四舍五入是令人驚訝的,但是在很多情況下,讓它發生是完全可以接受的,當向用戶顯示結果時,可能四舍五入到分數位數不那么令人驚訝。 在很多情況下,float或double可以在實際環境中使用。 這就是為什么硬件支持它們,也就是為什么它們使用該語言。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.