※錯誤的用法
System.out.println(2-1.1);//應該要0.9 System.out.println(0.2*0.7);//應該要1.4 BigDecimal b1 = new BigDecimal(2); BigDecimal b2 = new BigDecimal(1.1); System.out.println(b1.subtract(b2)); BigDecimal b3 = new BigDecimal(0.2); BigDecimal b4 = new BigDecimal(0.7); System.out.println(b3.multiply(b4));
※一般都會覺得 b1~b4 就對了,其實還是有問題
※使用 valueOf 方法沒問題,因為源碼已經 toString() 了,所以推薦使用 valueOf 取代建構子的方式
※double d = 0.1f;
這行本身精度就不準了,所以再用 toString() 也是不正確的
※使用小數點做四則運算時,有些時候會出現不可預期的行為,所以 java 提供了 BigDecimal 類別
P.S. 這個問題都是小數點才會有,因為電腦懂得是二進位,所以會將小數轉換成二進位,最後再轉成10進位,但某些數字類似10/3這種除不進的情況,所有結果只是接近我們期待的數字,例如
一、0.5 轉二進制
0.5 * 2 = 1 (取整後必須做到餘 0),所以轉成二進制的結果為 0.1
驗算 0.1=> 2 的-1次方為 1/2
0.5 為 5/10為 1/2,所以正確
二、0.75 轉二進制
0.75 *2 = 1.5 取 1,剩 0.5
0.5 *2 = 1 取 1,餘0,轉成二進制的結果為 0.11
驗算 0.11 => 2 的 -1次方 + 2 的 -2 次方,為 1/2 + 1/4 = 3/4
0.75 為 75 /100 = 3/4
所以在 BigDecimal 的建構子給 0.5、0.75 不會有問題,但是看例三
三、0.2 轉二進制
0.2 * 2 取 0,剩 0.4
0.4 * 2 取 0,剩 0.8
0.8 * 2 取 1,剩 0.6
0.6 * 2 取 1,剩 0.2
此時又是 0.2,所以結果為 0.0011 0011 0011…只是比較接近而已,所以建構子裡寫 0.2 會有誤差
※0.1~0.9 只有 0.5 沒問題;0.15~0.95,只有 0.25 和 0.75 沒問題 (加或減 0.5 的一半)
※BigDecimal 的原理就是先變成整數,運算完再除即可,如:
0.2 * 0.07 會放大,2 * 7=14 ,小數點後共 3 位數,
※如果有除不盡時就必須要進位,但預設進位是 ROUND_UNNECESSARY,此種方式是除不盡時就直接拋例外,要參考以下介紹的另外七種方式
※BigDecimal.ROUNT_UP 和 RoundingMode.UP 是一樣的,總共有八種進位方式
※ROUND_UNNECESSARY 是預設值
※只能精確的回傳,如註解的第一個參數是 0,但 42.46 有兩位小數,如果第一個參數不是 2,就會出例外
※UP、CEILING 都是無條件進位; DOWN、FLOOR 都是無條件捨去
※CEILING 和 FLOOR 是天花板與地板的意思,數字越大就代表天花板越高,所以CEILING 會往正的方向、FLOOR 會往負的方向
※UP、DOWN 沒有數字觀念,正負數時,數字都一樣 (只要將正的結果乘-1即可),如下:
※
※
※ROUND_HALF_UP 為四捨五入; ROUND_HALF_DOWN 為五捨六入
※注意:
五捨六入和四捨五入的規則不一樣,注意要進位如果是 5 會不一樣
xxx.550 視同 xxx.55,最後的 0 會自動刪除
.假設要取整,那就要看小數第一位如果是 0~4、6~9 和四捨五入一樣
※個設要取小數第一位,如果小數第二位是 5,再看小數第一位是偶數還奇數,奇進偶不進
※負數時,UP、DOWN、EVEN 都和正數一樣,只是是負的而已
※Math.round 正數和 BigDecimal.ROUND_HALF_UP 一樣;但負數和 BigDecimal.ROUND_HALF_DOWN 一樣
※使用小數點做四則運算時,有些時候會出現不可預期的行為,所以 java 提供了 BigDecimal 類別
P.S. 這個問題都是小數點才會有,因為電腦懂得是二進位,所以會將小數轉換成二進位,最後再轉成10進位,但某些數字類似10/3這種除不進的情況,所有結果只是接近我們期待的數字,例如
一、0.5 轉二進制
0.5 * 2 = 1 (取整後必須做到餘 0),所以轉成二進制的結果為 0.1
驗算 0.1=> 2 的-1次方為 1/2
0.5 為 5/10為 1/2,所以正確
二、0.75 轉二進制
0.75 *2 = 1.5 取 1,剩 0.5
0.5 *2 = 1 取 1,餘0,轉成二進制的結果為 0.11
驗算 0.11 => 2 的 -1次方 + 2 的 -2 次方,為 1/2 + 1/4 = 3/4
0.75 為 75 /100 = 3/4
所以在 BigDecimal 的建構子給 0.5、0.75 不會有問題,但是看例三
三、0.2 轉二進制
0.2 * 2 取 0,剩 0.4
0.4 * 2 取 0,剩 0.8
0.8 * 2 取 1,剩 0.6
0.6 * 2 取 1,剩 0.2
此時又是 0.2,所以結果為 0.0011 0011 0011…只是比較接近而已,所以建構子裡寫 0.2 會有誤差
※0.1~0.9 只有 0.5 沒問題;0.15~0.95,只有 0.25 和 0.75 沒問題 (加或減 0.5 的一半)
※BigDecimal 的原理就是先變成整數,運算完再除即可,如:
0.2 * 0.07 會放大,2 * 7=14 ,小數點後共 3 位數,
所以 14 / 1000 變成 0.014
※使用字串包起來才不會有不可預期的情形發生或者用 valueOf 方法
※注意 toString() 、toPlainString() 、toEngineeringString() 的差別,E+1 表示 10 的 1 次方,且只有在運算有小數點時才會發生不一樣的情形,toEngineeringString() 的指數一定是 3 的倍數
※toString、toPlainString、toEngineeringString 的差別、判斷正負數、判斷兩個 BigDecimal 大小
BigDecimal d1 = new BigDecimal("10"); BigDecimal d2 = new BigDecimal("0.2"); System.out.println(d1.divide(d2)); // 5E+1 System.out.println(d1.signum()); // 判斷正負0,返回 1、-1、0 System.out.println(d1.compareTo(BigDecimal.TEN)); // 判斷兩個 BigDecimal 大小,返回 1、-1、0 BigDecimal a = BigDecimal.valueOf(1_0000); BigDecimal b = a.divide(BigDecimal.valueOf(0.2)); System.out.println(b.toPlainString()); // 50000 System.out.println(b.toString()); // 5.000E+4 System.out.println(b.toEngineeringString()); // 50.00E+3 BigDecimal c = BigDecimal.valueOf(1000); BigDecimal d = c.divide(BigDecimal.valueOf(0.2)); System.out.println(d.toPlainString()); // 5000 System.out.println(d.toString()); // 5.00E+3 System.out.println(d.toEngineeringString()); // 5.00E+3 BigDecimal bg = new BigDecimal("1E4"); System.out.println(bg.toPlainString()); // 10000 System.out.println(bg.toString()); // 1E+4 System.out.println(bg.toEngineeringString()); // 10E+3
※使用字串包起來才不會有不可預期的情形發生或者用 valueOf 方法
※注意 toString() 、toPlainString() 、toEngineeringString() 的差別,E+1 表示 10 的 1 次方,且只有在運算有小數點時才會發生不一樣的情形,toEngineeringString() 的指數一定是 3 的倍數
※比較 equals 和 compareTo
BigDecimal myZero = new BigDecimal("0.0");
System.out.println(myZero.equals(new BigDecimal("0.00"))); // false
System.out.println(myZero.hashCode() == (new BigDecimal("0.00")).hashCode()); // false
System.out.println(myZero.compareTo(new BigDecimal("0.00"))); // 0
// 所以一般都會使用 compareTo,但其實還有 stripTrailingZeros() 方法可用
// 使用 stripTrailingZeros 可將最後的 0 去除
System.out.println(myZero.stripTrailingZeros().equals(new BigDecimal("0.00").stripTrailingZeros())); // true
System.out.println(myZero.stripTrailingZeros().hashCode() == (new BigDecimal("0.00")).stripTrailingZeros().hashCode()); // true
※除法問題
BigDecimal d1 = new BigDecimal("10"); BigDecimal d2 = new BigDecimal("3"); // System.out.println(d1.divide(d2));//Non-terminating decimal expansion; no exact representable decimal result. System.out.println(d1.divide(d2, BigDecimal.ROUND_UP));//4 System.out.println(d1.divide(d2, 2, RoundingMode.UP));//3.34
※如果有除不盡時就必須要進位,但預設進位是 ROUND_UNNECESSARY,此種方式是除不盡時就直接拋例外,要參考以下介紹的另外七種方式
※BigDecimal.ROUNT_UP 和 RoundingMode.UP 是一樣的,總共有八種進位方式
※八種進位方式
BigDecimal bd = new BigDecimal("42.46"); // System.out.println(bd.setScale(0, BigDecimal.ROUND_UNNECESSARY));//java.lang.ArithmeticException: Rounding necessary System.out.println(bd.setScale(2, BigDecimal.ROUND_UNNECESSARY));//42.46
※ROUND_UNNECESSARY 是預設值
※只能精確的回傳,如註解的第一個參數是 0,但 42.46 有兩位小數,如果第一個參數不是 2,就會出例外
※UP、DOWN、CEILING、FLOOR
BigDecimal bd = new BigDecimal("32.543"); System.out.println(bd.setScale(0, RoundingMode.UP));//33 System.out.println(bd.setScale(1, RoundingMode.UP));//32.6 System.out.println(bd.setScale(0, RoundingMode.CEILING));//33 System.out.println(bd.setScale(1, RoundingMode.CEILING));//32.6 System.out.println(bd.setScale(0, RoundingMode.DOWN));//32 System.out.println(bd.setScale(1, RoundingMode.DOWN));//32.5 System.out.println(bd.setScale(0, RoundingMode.FLOOR));//32 System.out.println(bd.setScale(1, RoundingMode.FLOOR));//32.5
※UP、CEILING 都是無條件進位; DOWN、FLOOR 都是無條件捨去
※CEILING 和 FLOOR 是天花板與地板的意思,數字越大就代表天花板越高,所以CEILING 會往正的方向、FLOOR 會往負的方向
※UP、DOWN 沒有數字觀念,正負數時,數字都一樣 (只要將正的結果乘-1即可),如下:
※
BigDecimal bd = new BigDecimal("-32.543"); System.out.println(bd.setScale(0, RoundingMode.UP));//-33 System.out.println(bd.setScale(1, RoundingMode.UP));//-32.6 System.out.println(bd.setScale(0, RoundingMode.CEILING));//-32 System.out.println(bd.setScale(1, RoundingMode.CEILING));//-32.5 System.out.println(bd.setScale(0, RoundingMode.DOWN));//-32 System.out.println(bd.setScale(1, RoundingMode.DOWN));//-32.5 System.out.println(bd.setScale(0, RoundingMode.FLOOR));//-33 System.out.println(bd.setScale(1, RoundingMode.FLOOR));//-32.6
※
※四捨五入、五捨六入
final int scale = 0; System.out.println(new BigDecimal("46.5").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//46 System.out.println(new BigDecimal("46.50").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//46 System.out.println(new BigDecimal("46.501").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.512").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.523").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.534").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.545").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.556").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.567").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.578").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.589").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.590").setScale(scale, BigDecimal.ROUND_HALF_DOWN));//47 System.out.println(new BigDecimal("46.55").setScale(1, BigDecimal.ROUND_HALF_DOWN));//46.5 System.out.println(new BigDecimal("46.551").setScale(1, BigDecimal.ROUND_HALF_DOWN));//46.6
※ROUND_HALF_UP 為四捨五入; ROUND_HALF_DOWN 為五捨六入
※注意:
五捨六入和四捨五入的規則不一樣,注意要進位如果是 5 會不一樣
xxx.550 視同 xxx.55,最後的 0 會自動刪除
.假設要取整,那就要看小數第一位如果是 0~4、6~9 和四捨五入一樣
如果是 5,還要再看第二位之後有沒有數字,有就進位;沒有則不進位
.假設要取小數第一位,那就要看小數第二位如果是 5,再看第二位之後有沒有數字,有就進位;沒有則不進位
奇數時是四捨五入;偶數時是五捨六入
.假設要取小數第一位,那就要看小數第二位如果是 5,再看第二位之後有沒有數字,有就進位;沒有則不進位
※ROUND_HALF_EVEN (四捨六入奇進偶不進)
final int scale = 0; int method = BigDecimal.ROUND_HALF_EVEN; System.out.println(new BigDecimal("45.5").setScale(scale, method));//46 System.out.println(new BigDecimal("46.5").setScale(scale, method));//46 System.out.println(new BigDecimal("45.55").setScale(1, method));//45.6 System.out.println(new BigDecimal("45.65").setScale(1, method));//45.6
.假設要取整,那就看整數位是奇數還偶數
※個設要取小數第一位,如果小數第二位是 5,再看小數第一位是偶數還奇數,奇進偶不進
※四種方法差異
System.out.println(new BigDecimal("45.55").setScale(1, BigDecimal.ROUND_HALF_UP));//45.6 System.out.println(new BigDecimal("45.55").setScale(1, BigDecimal.ROUND_HALF_DOWN));//45.5 System.out.println(new BigDecimal("45.55").setScale(1, BigDecimal.ROUND_HALF_EVEN));//45.6 System.out.println(new BigDecimal("45.65").setScale(1, BigDecimal.ROUND_HALF_UP));//45.7 System.out.println(new BigDecimal("45.65").setScale(1, BigDecimal.ROUND_HALF_DOWN));//45.6 System.out.println(new BigDecimal("45.65").setScale(1, BigDecimal.ROUND_HALF_EVEN));//45.6 System.out.println(new BigDecimal("-2.5").setScale(0, BigDecimal.ROUND_HALF_UP));// -3 System.out.println(new BigDecimal("-2.5").setScale(0, BigDecimal.ROUND_HALF_DOWN));// -2 System.out.println(new BigDecimal("-2.5").setScale(0, BigDecimal.ROUND_HALF_EVEN));// -2 System.out.println(Math.round(-2.5)); // -2 System.out.println("-----------------------------------------"); System.out.println(new BigDecimal("-3.5").setScale(0, BigDecimal.ROUND_HALF_UP));// -4 System.out.println(new BigDecimal("-3.5").setScale(0, BigDecimal.ROUND_HALF_DOWN));// -3 System.out.println(new BigDecimal("-3.5").setScale(0, BigDecimal.ROUND_HALF_EVEN));// -4 System.out.println(Math.round(-3.5)); // -3
※負數時,UP、DOWN、EVEN 都和正數一樣,只是是負的而已
※java9 的 BigDecimal.XXX 過時了,要使用 RoundingMode.XXX 取代
※Math.round 正數和 BigDecimal.ROUND_HALF_UP 一樣;但負數和 BigDecimal.ROUND_HALF_DOWN 一樣
MathContext
BigDecimal one1 = new BigDecimal("123.45565"); BigDecimal one2 = new BigDecimal("123.45565", MathContext.UNLIMITED); BigDecimal one3a = new BigDecimal("123.45565", MathContext.DECIMAL32); BigDecimal one4a = new BigDecimal("123.45565", new MathContext(7, RoundingMode.HALF_UP)); BigDecimal one3b = new BigDecimal("123.45575", MathContext.DECIMAL32); BigDecimal one4b = new BigDecimal("123.45575", new MathContext(7, RoundingMode.HALF_UP)); System.out.println(one1); // 123.45565 System.out.println(one2); // 123.45565 System.out.println(one3a); // 123.4556 System.out.println(one4a); // 123.4557 System.out.println(one3b); // 123.4558 System.out.println(one4b); // 123.4558※one1 和 one2 是一樣的,one1 底層也是用 MathContext.UNLIMITED,是無限制的意思
※MathContext.DECIMAL32 底層是 new MathContext(7, RoundingMode.HALF_EVEN)
7 表示整數加小數位不能超過 7,不包括小數點,但是整數是 0 時不包括,如 0.1234567 是可以顯示到最後的 7 的
※看 one3a 和 one4a 的比較,half_even 是四捨六入,奇數進位;偶數不進位,第 7 位是 6 不進位,所以兩個值不相同
※看 one3b 和 one4b 的比較,四捨六入時,最後是 7 要進位,所以兩個值相同
※MathContext 有個傳 String 的建構子,使用方法 new MathContext("precision=7 roundingMode=HALF_EVEN"),大小寫不能錯
※本來 BigDecimal 建構子,除了0.5外,會有誤差,用 MathContext 就不會了,但 MathContext.UNLIMITED 還是有誤差,但還是不能在建構子裡面做運算,如下:
System.out.println(new BigDecimal(0.8 * 0.4, MathContext.DECIMAL64)); // 0.3200000000000001
System.out.println(new BigDecimal(0.32 / 0.4, MathContext.DECIMAL64)); // 0.7999999999999999
沒有留言:
張貼留言