java計算百分比代碼「java百分比用什麼類型」

BigDecimal

這篇文章我們會介紹一下Java 中的BigDecimal,並且會通過一些例子演示它的用法,例如精度的操作

Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數進行精確的運算。雙精度浮點型變數double可以處理16位有效數,但在實際應用中,可能需要對更大或者更小的數進行運算和處理。一般情況下,對於那些不需要準確計算精度的數字,我們可以直接使用Float和Double處理,但是Double.valueOf(String) 和Float.valueOf(String)會丟失精度。所以開發中,如果我們需要精確計算的結果,則必須使用BigDecimal類來操作。

為什麼需要BigDecimal

前面學習基本類型的時候,我們可以使用float 和 double來表示浮點型數字,但是這裡有一個問題那就是基本數據類型float 和 double不應該用於高精度數據的表示,例如貨幣,因為浮點類型的float 和 double會丟失數據的精度

double d1 = 374.56;
double d2 = 374.26;
System.out.println( "d1 - d2 = " + ( d1 - d2 ));

這裡本應輸出的是0.30,但是實際上輸出如下

d1 - d2 = 0.30000000000001137

這就是為什麼在金融相關的程序里數據類型如此重要的原因,所以我們更好的選擇是BigDecimal 而不是float 和 double

Java BigDecimal 類

BigDecimal 是不可變的,任意精度的有符號十進位類型的數字,可用於貨幣計算

上面那個例子如果我們是使用BigDecimal來代替double 我們可以獲得準確的值

BigDecimal bd1 = new BigDecimal("374.56");
BigDecimal bd2 = new BigDecimal("374.26");
  
System.out.println("bd1 - bd2 = " + bd1.subtract(bd2));

現在輸出就和預期的一樣了

bd1 - bd2 = 0.30

Java 中的BigDecimal類繼承自Number並且實現了Comparable介面

public class BigDecimal extends Number implements Comparable<BigDecimal> {

}

BigDecimal 類的構造方法

BigDecimal 提供了很多的構造方法,可以使用int ,char[],BigDecimal,String,doble ,long,int來初始化BigDecimal,BigDecimal 總共提供了18種構造方法,需要注意的實如果使用double 來初始化BigDecimal或許會再次引入精度的問題,下面提供了一個例子

BigDecimal bde = new BigDecimal(23.12);
System.out.println("" + bde.toString());

輸出結果是這樣的

23.120000000000000994759830064140260219573974609375

Thus it is always safe to go with a constructor that takes String as argument when representing a decimal value.

因此使用String 做為構造函數的參數來表示一個十進位的數字的時候總是安全的

BigDecimal bde = new BigDecimal("23.12");
System.out.println("" + bde.toString());

Output

23.12

BigDecimal 的精度

使用BigDecimal的一個理由是BigDecimal提供了精度控制(小數點後的數字的多少)和舍入模式,為了確定小數點後的保留幾位數字你可以使用setScale(int scale) 方法,但是最好的是在使用精度的時候提供舍入模式,也就是setScale的重載方法

  • setScale(int newScale, int roundingMode)
  • setScale(int newScale, RoundingMode roundingMode)

接下來我們通過一個例子演示一下我們為什麼要這樣做,假設我們在通過一個double值構造一個BigDecimal

BigDecimal bde = new BigDecimal(23.12);
System.out.println("Value- " + bde.toString());
System.out.println("Scaled value- " + bde.setScale(1).toString());

Output

Value- 23.120000000000000994759830064140260219573974609375
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
 at java.base/java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4495)
 at java.base/java.math.BigDecimal.needIncrement(BigDecimal.java:4702)
 at java.base/java.math.BigDecimal.divideAndRound(BigDecimal.java:4677)
 at java.base/java.math.BigDecimal.setScale(BigDecimal.java:2811)
 at java.base/java.math.BigDecimal.setScale(BigDecimal.java:2853)
 at org.netjs.Programs.App.main(App.java:15)

從上面的輸出中我們看到進度已經丟失了,輸出的BigDecimal 是
23.120000000000000994759830064140260219573974609375

並且我們看到當我們將精度設置為1 的時候並且沒有提供舍入機制的時候導致Arithmetic異常被拋出

BigDecimal 的舍入模式

如果你注意到了上面我們在講精度設置的時候,它其實是有兩個設置精度的重載方法,第二個參數代表的就是舍入模式模式的參數,BigDecimal提供了八種舍入模式,它們通過static final int 進行表示

public final static int ROUND_UP =           0;
public final static int ROUND_DOWN =         1;
public final static int ROUND_CEILING =      2;
public final static int ROUND_FLOOR =        3;
public final static int ROUND_HALF_UP =      4;
public final static int ROUND_HALF_DOWN =    5;
public final static int ROUND_HALF_EVEN =    6;
public final static int ROUND_UNNECESSARY =  7;

需要注意的是在java.math包中也提供舍入模式的枚舉值,需要注意的我們是推薦使用枚舉值來代替使用int 類型的常量做舍入摸模式的參數

下面我們在設置進度的同時設置一下舍入模式,來避免Arithmetic異常

@Test
public void scale() {
    BigDecimal bde = new BigDecimal(23.12);
    System.out.println("Scaled value- " + bde.setScale(1,1).toString());
}

但是我們說了,我們推薦使用枚舉值的舍入模式,而不是直接使用int 類型的常量,接下來我們看一下RoundingMode 提供的枚舉值

  • CEILING– Rounding mode to round towards positive infinity.
  • DOWN– Rounding mode to round towards zero.
  • FLOOR– Rounding mode to round towards negative infinity.
  • HALF_DOWN– Rounding mode to round towards “nearest neighbor” unless both neighbors are equidistant, in which case round down.
  • HALF_EVEN– Rounding mode to round towards the “nearest neighbor” unless both neighbors are equidistant, in which case, round towards the even neighbor.
  • HALF_UP– Rounding mode to round towards “nearest neighbor” unless both neighbors are equidistant, in which case round up.
  • UNNECESSARY – Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.
  • UP– Rounding mode to round away from zero.

下面我們通過例子來總結一下這些舍入模式的舍入方式

Result of rounding input to one digit with the given rounding mode
Input NumberUPDOWNCEILINGFLOORHALF_UPHALF_DOWNHALF_EVENUNNECESSARY
5.56565656throw ArithmeticException
2.53232322throw ArithmeticException
1.62121222throw ArithmeticException
1.12121111throw ArithmeticException
1.011111111
-1.0-1-1-1-1-1-1-1-1
-1.1-2-1-1-2-1-1-1throw ArithmeticException
-1.6-2-1-1-2-2-2-2throw ArithmeticException
-2.5-3-2-2-3-3-2-2throw ArithmeticException
-5.5-6-5-5-6-6-5-6throw ArithmeticException

更多細節參考
docs.oracle.com/javase/8/do…

BigDecimal格式化

NumberFormat 格式化

由於NumberFormat類的format()方法可以使用BigDecimal對象作為其參數,可以利用BigDecimal對超出16位有效數字的貨幣值,百分值,以及一般數值進行格式化控制。

以利用BigDecimal對貨幣和百分比格式化為例。首先,創建BigDecimal對象,進行BigDecimal的算術運算後,分別建立對貨幣和百分比格式化的引用,最後利用BigDecimal對象作為format()方法的參數,輸出其格式化的貨幣值和百分比。

@Test
public void format() {
    NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立貨幣格式化引用
    NumberFormat percent = NumberFormat.getPercentInstance();  //建立百分比格式化引用
    percent.setMaximumFractionDigits(3); //百分比小數點最多3位

    BigDecimal loanAmount = new BigDecimal("15000.48"); //貸款金額
    BigDecimal interestRate = new BigDecimal("0.008"); //利率
    BigDecimal interest = loanAmount.multiply(interestRate); //相乘

    System.out.println("貸款金額:t" + currency.format(loanAmount));
    System.out.println("利率:t" + percent.format(interestRate));
    System.out.println("利息:t" + currency.format(interest));
}

輸出結果

貸款金額:	¥15,000.48
利率:	0.8%
利息:	¥120.00

DecimalFormat 格式化

BigDecimal格式化保留2為小數,不足則補0

@Test
public void format2(){
    DecimalFormat df = new DecimalFormat("#.00");
    System.out.println(formatToNumber(new BigDecimal("3.435")));
    System.out.println(formatToNumber(new BigDecimal(0)));
    System.out.println(formatToNumber(new BigDecimal("0.00")));
    System.out.println(formatToNumber(new BigDecimal("0.001")));
    System.out.println(formatToNumber(new BigDecimal("0.006")));
    System.out.println(formatToNumber(new BigDecimal("0.206")));
}


/**
 * @desc 
 * 1. 0~1之間的BigDecimal小數,格式化後失去前面的0,則前面直接加上0。
 * 2. 傳入的參數等於0,則直接返回字元串"0.00"
 * 3. 大於1的小數,直接格式化返回字元串
 * @return
 */
public String formatToNumber(BigDecimal obj) {
    DecimalFormat df = new DecimalFormat("#.00");
    if(obj.compareTo(BigDecimal.ZERO)==0) {
        return "0.00";
    }else if(obj.compareTo(BigDecimal.ZERO)>0&&obj.compareTo(new BigDecimal(1))<0){
        return "0"+df.format(obj).toString();
    }else {
        return df.format(obj).toString();
    }
}

BigDecimal 例子

最常見的例子就是精度是2(小數點後保留兩位),並且採用四捨五入的舍入模式(如果指定精度的笑一個數字大於等於5則向上取整,否則向下取整)

@Test
public void examples() {
    BigDecimal bd1 = new BigDecimal("23.126");
    System.out.println("bd1 " + bd1.setScale(2, RoundingMode.HALF_UP).toString());
}
// 輸出結果
bd1 23.13

因為精度設置為2之後,也就是小數點後兩位的後一位數字是6大於等於5,所以向上取整,所以結果是 23.13

BigDecimal bd1 = new BigDecimal("23.1236");
System.out.println("bd1 " + bd1.setScale(2, RoundingMode.HALF_UP).toString());

因為精度設置為2之後,也就是小數點後兩位的後一位數字是3小於5,所以向下取整,所以結果是 23.12

BigDecimal bd1 = new BigDecimal("-15.567");
System.out.println("bd1 " + bd1.setScale(2, RoundingMode.HALF_UP).toString());

對於負數,也是同樣的道理,所以輸出是bd1 -15.57

BigDecimal 的特性

1. 沒有重載操作符

在Java 中支持的(+, -, *, /)數學運算,BigDecimal並不支持,因為這些操作符是針對基本數據類型的,但是BigDecimal是引用類型,也就是基於對象和類的,因此BigDecimal提供了下面的方法

add,subtract,multiply,and,divide

例如乘法在BigDecimal的實現如下

BigDecimal bd1 = new BigDecimal("15.567");

BigDecimal result = BigDecimal.valueOf(68).multiply(bd1);
System.out.println("result " + result.toString());

Output

result 1058.556

2. 使用 compareTo() 來比較BigDecimals 而不是使用 equals()

需要注意如果你使用 equals()來比較兩個BigDecimal數字,那只有當兩個BigDecimal的值和精度都相同的時候equals()猜認為它們是相同的(因此2.0和2.00是不相同的)

BigDecimal bd1 = new BigDecimal("2.00");
BigDecimal bd2 = new BigDecimal("2.0");
System.out.println("bd1 equals bd2 - " + bd1.equals(bd2));

Output

bd1 equals bd2 - false

因此你應該使用compareTo()方法來比較兩個BigDecimal 是否是相等的,BigDecimal實現了comparable介面並且提供了自己的compareTo方法,這個方法只會判斷兩個BigDecimal對象的值是否是相等的忽略了兩個數字的精度(ike 2.0 和 2.00 相等的)

對於bd1.compareTo(bd2) 的返回值

  • -1 bd1 小於 bd2.
  • 0 兩個相等的
  • 1 bd1 大於 bd2.
BigDecimal bd1 = new BigDecimal("2.00");
BigDecimal bd2 = new BigDecimal("2.0");
System.out.println("bd1 compareTo bd2 - " + bd1.compareTo(bd2));

Output

bd1 compareTo bd2 - 0

3. BigDecimals 是不可變的

BigDecimal 對象是不可變的,所以是線程安全的,在進行每一次四則運算時,都會產生一個新的對象 ,所以在做加減乘除運算時要記得要保存操作後的值。

總結

  1. 當我們在進行有著高精度的計算要求的時候不要使用double和float 因為它們有著精度丟失的問題
  2. 如果使用BigDecimal的時候,不要選擇double值作為初始化的值,因為它同樣會引入精度的問題
  3. 如果你使用BigDecimal時候設置了精度,那就同時提供舍入模式,告訴BigDecimal如何舍入從而提供你想要的精度
  4. BigDecimal繼承了Number類和實現了Comparable介面
  5. BigDecimal 針對加減乘除提供可特定的方法,因為BigDecimal不支持(+, -, *, /)數學運算
  6. BigDecimal 的對象是不可變的
  7. BigDecimal因為創建對象開銷的原因,所以很多操作都是比原生類型要慢一些的。
Java數據類型系列之BigDecimal

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/252515.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-14 02:17
下一篇 2024-12-14 02:17

相關推薦

發表回復

登錄後才能評論