當前位置: 妍妍網 > 碼農

BigDecimal 為什麽可以保證精度不遺失?

2024-06-01碼農

在金融領域,為了保證數據的精度,往往會使用BigDecimal。本文就來探討下為什麽BigDecimal可以保證精度不遺失。

類介紹

首先來看一下BigDecimal的類別宣告以及幾個內容:

public classBigDecimalextendsNumberimplementsComparable<BigDecimal{
// 該BigDecimal的未縮放值
privatefinal BigInteger intVal;
// 精度,可以理解成小數點後的位數
privatefinalint scale;
// BigDecimal中的十進制位數,如果位數未知,則為0(備用資訊)
privatetransientint precision;
// Used to store the canonical string representation, if computed.
// 這個我理解就是存實際的BigDecimal值
privatetransient String stringCache;
// 擴大成long型數值後的值
privatefinaltransientlong intCompact;
}

從例子入手

透過debug來發現源碼中的奧秘是了解類執行機制很好的方式。請看下面的testBigDecimal方法:

@Test
publicvoidtestBigDecimal(){
BigDecimal bigDecimal1 = BigDecimal.valueOf(2.36);
BigDecimal bigDecimal2 = BigDecimal.valueOf(3.5);
BigDecimal resDecimal = bigDecimal1.add(bigDecimal2);
System.out.println(resDecimal);
}

在執行了 BigDecimal.valueOf(2.36) 後,檢視debug資訊可以發現上述提到的幾個內容被賦了值:

接下來進到add方法裏面,看看它是怎麽計算的:

/**
 * Returns a BigDecimal whose value is (this + augend), 
 * and whose scale is max(this.scale(), augend.scale()).
 */

public BigDecimal add(BigDecimal augend){
if (this.intCompact != INFLATED) {
if ((augend.intCompact != INFLATED)) {
return add(this.intCompact, this.scale, augend.intCompact, augend.scale);
else {
return add(this.intCompact, this.scale, augend.intVal, augend.scale);
}
else {
if ((augend.intCompact != INFLATED)) {
return add(augend.intCompact, augend.scale, this.intVal, this.scale);
else {
return add(this.intVal, this.scale, augend.intVal, augend.scale);
}
}
}

看一下傳進來的值:

進入第8行的add方法:

privatestatic BigDecimal add(finallong xs, int scale1, finallong ys, int scale2){
long sdiff = (long) scale1 - scale2;
if (sdiff == 0) {
return add(xs, ys, scale1);
elseif (sdiff < 0) {
int raise = checkScale(xs,-sdiff);
long scaledX = longMultiplyPowerTen(xs, raise);
if (scaledX != INFLATED) {
return add(scaledX, ys, scale2);
else {
BigInteger bigsum = bigMultiplyPowerTen(xs,raise).add(ys);
return ((xs^ys)>=0) ? // same sign test
new BigDecimal(bigsum, INFLATED, scale2, 0)
: valueOf(bigsum, scale2, 0);
}
else {
int raise = checkScale(ys,sdiff);
long scaledY = longMultiplyPowerTen(ys, raise);
if (scaledY != INFLATED) {
return add(xs, scaledY, scale1);
else {
BigInteger bigsum = bigMultiplyPowerTen(ys,raise).add(xs);
return ((xs^ys)>=0) ?
new BigDecimal(bigsum, INFLATED, scale1, 0)
: valueOf(bigsum, scale1, 0);
}
}
}

這個例子中,該方法傳入的參數分別是:xs=236,scale1=2,ys=35,scale2=1

該方法首先計算scale1 - scale2,根據差值走不同的計算邏輯,這裏求出來是1,所以進入到最下面的else程式碼塊(這塊是關鍵):

  • 首先17行校驗了一下數值範圍

  • 18行將ys擴大了10的n次倍,這裏n=raise=1,所以返回的scaledY=350

  • 接著就進入到20行的add方法:

  • privatestatic BigDecimal add(long xs, long ys, int scale){
    long sum = add(xs, ys);
    if (sum!=INFLATED)
    return BigDecimal.valueOf(sum, scale);
    returnnew BigDecimal(BigInteger.valueOf(xs).add(ys), scale);
    }

    這個方法很簡單,就是計算和,然後返回BigDecimal物件:

    結論

    所以可以得出結論:BigDecimal在計算時,實際會把數值擴大10的n次倍,變成一個long型整數進行計算,整數計算時自然可以實作精度不遺失。同時結合精度scale,實作最終結果的計算。

    來源:juejin.cn/post/7348709938023940136

    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中