咖啡汪日誌———數值計算,精度、舍入、溢位(極客時間)

咖啡汪發表於2020-10-13

一直想要去跑跑數值計算的坑
今天終於還是抽出了時間

一,開篇有益

不知道你有沒有用手機計數器計算過10%+10%
其實結果是:在這裡插入圖片描述
驚不驚喜,意不意外
為什麼不是0.2呢?
因為國外的計數器使用的都是單步計演算法
a+b% 代表的是 a*(1+b%)

Java採用了IEEE745標準實現浮點數的表達和計算
0.1二進位制為:0.0 0011 0011 0011無線迴圈
十進位制為:0.1 000 000 000 000 000 555 111 512 312 …5625
下面是實際的展示:在這裡插入圖片描述

二、浮點數運算避坑

1.使用BigDecimal表示和計算浮點數,務必使用字串的構造方法來初始化BigDecimal

	new BigDecimal(0.1).add(new BigDecimal(0.2));//錯誤的方式
	new BigDecimal("0.1").add(new BigDecimal("0.2"));//正確的方式

2.浮點數的字串格式化也要通過BigDecimal進行(不可使用valueOf/toString)

 		   //(1)直接刪 ,結果為 2.34
 System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_DOWN));
       //(2)直接進位 , 結果為 2.35
 System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_UP));
       //(3)四捨五入,為5向上取 , 結果為2.35
 System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_HALF_UP));
        //(4)五為分水嶺,為5向下取 , 結果為2.34
 System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_HALF_DOWN));
        //(5)接近正無窮大的舍入模式
 System.out.println(new BigDecimal("2.335").setScale(1,BigDecimal.ROUND_CEILING));//結果2.4
 System.out.println(new BigDecimal("-2.335").setScale(1,BigDecimal.ROUND_CEILING));//結果-2.3
        //(6)接近負無窮大的舍入模式
  System.out.println( new BigDecimal("2.33").setScale(1,BigDecimal.ROUND_FLOOR));//結果2.3
 System.out.println( new BigDecimal("-2.33").setScale(1,BigDecimal.ROUND_FLOOR));//結果-2.4
//(7)向“最接近”的數字舍入,距離相等,相相鄰的偶數舍入
  System.out.println( new BigDecimal("1.15").setScale(1,BigDecimal.ROUND_HALF_EVEN));//結果1.2 ,也稱“”銀行家舍入法”(美國多用))
  System.out.println( new BigDecimal("1.25").setScale(1,BigDecimal.ROUND_HALF_EVEN));//結果1.2
  //(8)斷言請求的操作具有精確的結果,因此不需要舍入
  System.out.println(new BigDecimal("1.15").setScale(1,BigDecimal.ROUND_HALF_UP));//結果1.2











3.BigDecimal使用equals 與 compareTo 做判等的區別:
(1)BigDecimal 的 equals 方法中註釋說明:equals 比較的是value和scale
1.0的scale 是1,1的dcale 是0,所以使用equals的比較結果為false
在這裡插入圖片描述
原始碼為:

 /**
     * Compares this {@code BigDecimal} with the specified
     * {@code Object} for equality.  Unlike {@link
     * #compareTo(BigDecimal) compareTo}, this method considers two
     * {@code BigDecimal} objects equal only if they are equal in
     * value and scale (thus 2.0 is not equal to 2.00 when compared by
     * this method).
     *
     * @param  x {@code Object} to which this {@code BigDecimal} is
     *         to be compared.
     * @return {@code true} if and only if the specified {@code Object} is a
     *         {@code BigDecimal} whose value and scale are equal to this
     *         {@code BigDecimal}'s.
     * @see    #compareTo(java.math.BigDecimal)
     * @see    #hashCode
     */
    @Override
    public boolean equals(Object x) {
        if (!(x instanceof BigDecimal))
            return false;
        BigDecimal xDec = (BigDecimal) x;
        if (x == this)
            return true;
        if (scale != xDec.scale)
            return false;
        long s = this.intCompact;
        long xs = xDec.intCompact;
        if (s != INFLATED) {
            if (xs == INFLATED)
                xs = compactValFor(xDec.intVal);
            return xs == s;
        } else if (xs != INFLATED)
            return xs == compactValFor(this.intVal);

        return this.inflated().equals(xDec.inflated());
    }

(2)只是希望比較BigDecimal的 value,可以使用compareTo方法
在這裡插入圖片描述

原始碼如下:

  /**
     * Compares this {@code BigDecimal} with the specified
     * {@code BigDecimal}.  Two {@code BigDecimal} objects that are
     * equal in value but have a different scale (like 2.0 and 2.00)
     * are considered equal by this method.  This method is provided
     * in preference to individual methods for each of the six boolean
     * comparison operators ({@literal <}, ==,
     * {@literal >}, {@literal >=}, !=, {@literal <=}).  The
     * suggested idiom for performing these comparisons is:
     * {@code (x.compareTo(y)} &lt;<i>op</i>&gt; {@code 0)}, where
     * &lt;<i>op</i>&gt; is one of the six comparison operators.
     *
     * @param  val {@code BigDecimal} to which this {@code BigDecimal} is
     *         to be compared.
     * @return -1, 0, or 1 as this {@code BigDecimal} is numerically
     *          less than, equal to, or greater than {@code val}.
     */
    public int compareTo(BigDecimal val) {
        // Quick path for equal scale and non-inflated case.
        if (scale == val.scale) {
            long xs = intCompact;
            long ys = val.intCompact;
            if (xs != INFLATED && ys != INFLATED)
                return xs != ys ? ((xs > ys) ? 1 : -1) : 0;
        }
        int xsign = this.signum();
        int ysign = val.signum();
        if (xsign != ysign)
            return (xsign > ysign) ? 1 : -1;
        if (xsign == 0)
            return 0;
        int cmp = compareMagnitude(val);
        return (xsign > 0) ? cmp : -cmp;
    }

4.HashSet 與 HashMap 不可與BigDecimal一起使用
HashMap中使用鍵物件來計算hashcode值,HashSet中使用成員物件來計算hashcode值,HashSet中的物件重寫了equals()和hashCode()方法
因此,如果放進去的是1.0,取時用 1 來取,是取不到的,參考上面BigDecimal 的 equals() 方法

解決辦法:
(1)使用TreeSet 替換HashSet(TreeSet使用CompareTo方法)
(2)在BigDecimal存入HashSet 與HashMap 中前,用stripTrailingZeros()方法去掉尾部的零,確保value相同的BigDecimal,scale也是相同的。
在這裡插入圖片描述

三、如何避免數值溢位問題?

1.使用Math類的 addExact.subtractExact 等XXExact方法進行數值運算
這樣數值溢位時,會主動丟擲異常
2.使用大數類BigInteger
BigInteget 執行Long 最大值加一,不會有問題
但溢位的資料,使用BigIngeter 的longValueExact(),轉為long時會報錯 ArithmeticException.

相關文章