1. 程式人生 > >Android BigDecimal對包含小數的值進行精確計算

Android BigDecimal對包含小數的值進行精確計算

我們平常使用float或者double進行計算時會出現精度丟失的情況,例如:

system.out.println(0.06-0.01)

得到的結果為0.0499999999999999

為什麼會出現這種情況呢?

原因在於我們的計算機是二進位制的。浮點數沒有辦法是用二進位制進行精確表示。我們的CPU表示浮點數由兩個部分組成:指數和尾數,這樣的表示方法一般都會失去一定的精確度,有些浮點數運算也會產生一定的誤差。如:2.4的二進位制表示並非就是精確的2.4。反而最為接近的二進位制表示是 2.3999999999999999。浮點數的值實際上是由一個特定的數學公式計算得到的。

在這種情況下我們就需要用到java.math包下面的BigDecimal類,BigDecimal主要用於高精度的資料計算,例如計算金額的時候,還有工程測量計算的時候。BigDecimal的提供了add(),subtract(),multiply()和divide()四種方法,分別為加減乘除,一般計算包含小數的用法為

BigDecimal b1 = new BigDecimal(Double或者String);
BigDecimal b2 = new BigDecimal(Double或者String);
System.out.println(b1.subtract(b2).doubleValue());
我在實際用的時候發現了一個問題,當new BigDecimal()引數型別為Double的時候,得到的結果仍然丟失了精度,例如:
BigDecimal b1 = new BigDecimal((Double)0.06);
BigDecimal b2 = new BigDecimal((Double)0.01);
System.out.println(b1.subtract(b2).doubleValue());
得到的結果仍然為0.04999999999999999
可是如果引數型別為String,結果就是正確的,就像這樣:

BigDecimal b1 = new BigDecimal("0.06");
BigDecimal b2 = new BigDecimal("0.01");
System.out.println(b1.subtract(b2).doubleValue());

得到的結果為0.05。

經過測試發現,原來在new BigDecimal(0.06)的時候,得到b1的結果為0.05999999999999999,這時候就已經精度丟失了,最後得到的結果很顯然也會丟失精度,而使用new BigDecimal("0.06")得到的b1仍然為0.06。

所以如果大家要進行精確計算的話,new BigDecimal()的引數一定要用String型別的。

下面是已經封裝好了的工具類:

        /**
         * 提供精確的加法運算
         *
         * @param v1 被加數
         * @param v2 加數
         * @return 兩個引數的和
         */
        public static double add(double v1, double v2) {
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.add(b2).doubleValue();
        }

        /**
         * 提供精確的加法運算
         *
         * @param v1 被加數
         * @param v2 加數
         * @param scale 保留scale 位小數
         * @return 兩個引數的和
         */
        public static String add(String v1, String v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
        }

        /**
         * 提供精確的減法運算
         *
         * @param v1 被減數
         * @param v2 減數
         * @return 兩個引數的差
         */
        public static double sub(double v1, double v2) {
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.subtract(b2).doubleValue();
        }

        /**
         * 提供精確的減法運算
         *
         * @param v1 被減數
         * @param v2 減數
         * @param scale 保留scale 位小數
         * @return 兩個引數的差
         */
        public static String sub(String v1, String v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
        }

        /**
         * 提供精確的乘法運算
         *
         * @param v1 被乘數
         * @param v2 乘數
         * @return 兩個引數的積
         */
        public static double mul(double v1, double v2) {
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.multiply(b2).doubleValue();
        }

        /**
         * 提供精確的乘法運算
         *
         * @param v1 被乘數
         * @param v2 乘數
         * @param scale 保留scale 位小數
         * @return 兩個引數的積
         */
        public static String mul(String v1, String v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
        }

        /**
         * 提供精確的除法運算。當發生除不盡的情況時,由scale引數指定精度,以後的數字四捨五入
         *
         * @param v1 被除數
         * @param v2 除數
         * @param scale 表示表示需要精確到小數點以後幾位。
         * @return 兩個引數的商
         */
        public static double div(double v1, double v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
        }

        /**
         * 提供精確的除法運算。當發生除不盡的情況時,由scale引數指定精度,以後的數字四捨五入
         *
         * @param v1 被除數
         * @param v2 除數
         * @param scale 表示需要精確到小數點以後幾位
         * @return 兩個引數的商
         */
        public static String div(String v1, String v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
        }

        /**
         * 提供精確的小數位四捨五入處理
         *
         * @param v 需要四捨五入的數字
         * @param scale 小數點後保留幾位
         * @return 四捨五入後的結果
         */
        public static double round(double v, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b = new BigDecimal(Double.toString(v));
            return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
        }

        /**
         * 提供精確的小數位四捨五入處理
         *
         * @param v 需要四捨五入的數字
         * @param scale 小數點後保留幾位
         * @return 四捨五入後的結果
         */
        public static String round(String v, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b = new BigDecimal(v);
            return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
        }

        /**
         * 取餘數
         *
         * @param v1 被除數
         * @param v2 除數
         * @param scale 小數點後保留幾位
         * @return 餘數
         */
        public static String remainder(String v1, String v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException("保留的小數位數必須大於零");
            }
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
        }

        /**
         * 比較大小
         *
         * @param v1 被比較數
         * @param v2 比較數
         * @return 如果v1 大於v2 則 返回true 否則false
         */
        public static boolean compare(String v1, String v2) {
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            int bj = b1.compareTo(b2);
            if (bj > 0)
                return true;
            else
                return false;
        }

如果有地方說的不對,歡迎指正!!!