簡體   English   中英

如何克服Java中的不准確性

[英]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    

(請原諒無組織的輸出)。 我注意到了幾件事:

  • sin 30即0.5存儲為0.49999999999999994
  • tan 45即1.0存儲為0.9999999999999999
  • tan 90(即infinityundefined )存儲為1.633123935319537E16 (這是一個非常大的數字)。

自然地,我很困惑地看到輸出(即使在解密輸出之后)。

所以我讀了這篇文章,最好的答案告訴我:

這些精度問題是由於浮點數>點號的內部表示而引起的,您無法做很多事情來避免這種情況。

順便說一句,在運行時打印這些值通常仍會導致正確的結果,至少使用現代C ++編譯器。 對於大多數操作而言,這並不是什么大問題。

2008年10月7日在7:42回答

康拉德·魯道夫

所以,我的問題是:

有什么方法可以防止這種不准確的結果(在Java中)?

我應該對結果進行四舍五入嗎? 在那種情況下,我將如何存儲infinityDouble.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM