สองเท่ากับ BigDecimal?


303

ฉันต้องคำนวณตัวแปรทศนิยมบางอย่างและเพื่อนร่วมงานของฉันแนะนำให้ฉันใช้BigDecimalแทนdoubleเพราะมันจะแม่นยำมากขึ้น แต่ฉันต้องการรู้ว่ามันคืออะไรและทำอย่างไรให้ได้ประโยชน์สูงสุดBigDecimal?


ลองดูอันนี้ stackoverflow.com/questions/322749/…
Espen Schulstad

คำตอบ:


446

A BigDecimalเป็นวิธีที่แน่นอนในการแสดงตัวเลข A Doubleมีความแม่นยำแน่นอน การทำงานกับขนาดต่าง ๆ เป็นสองเท่า (พูดd1=1000.0และd2=0.001) อาจส่งผลให้0.001ถูกทิ้งทั้งหมดเมื่อรวมกันเมื่อความแตกต่างของขนาดมีขนาดใหญ่มาก ด้วยBigDecimalสิ่งนี้จะไม่เกิดขึ้น

ข้อเสียของBigDecimalมันคือช้าลงและเป็นเรื่องยากมากที่จะเขียนโปรแกรมอัลกอริทึมแบบนั้น (เนื่องจาก+ - *และ/ไม่ได้รับการโอเวอร์โหลด)

BigDecimalถ้าคุณจะจัดการกับเงินหรือความแม่นยำเป็นสิ่งที่ต้องใช้ อย่างอื่นDoublesมีแนวโน้มที่จะดีพอ

ฉันขอแนะนำให้อ่านJavadocของBigDecimalที่พวกเขาทำสิ่งที่อธิบายดีกว่าที่ผมทำที่นี่ :)


ใช่ฉันกำลังคำนวณราคาหุ้นดังนั้นฉันเชื่อว่า BigDecimal มีประโยชน์ในกรณีนี้
Truong Ha

5
@Trongong Ha: เมื่อทำงานกับราคาที่คุณต้องการใช้ BigDecimal และถ้าคุณเก็บไว้ในฐานข้อมูลคุณต้องการสิ่งที่คล้ายกัน
extraneon

98
การกล่าวว่า "BigDecimal เป็นวิธีที่แม่นยำในการแสดงตัวเลข" ทำให้เข้าใจผิด 1/3 และ 1/7 ไม่สามารถแสดงได้อย่างแม่นยำในระบบเลขฐาน 10 (BigDecimal) หรือในระบบเลขฐาน 2 (ลอยหรือสองครั้ง) 1/3 สามารถแสดงได้อย่างแม่นยำในฐาน 3, ฐาน 6, ฐาน 9, ฐาน 12, ฯลฯ และ 1/7 สามารถแสดงได้อย่างแน่นอนในฐาน 7, ฐาน 14, ฐาน 21, ฯลฯ ข้อดี BigDecimal คือความแม่นยำโดยพลการ และมนุษย์นั้นคุ้นเคยกับข้อผิดพลาดในการปัดเศษที่คุณได้รับในฐาน 10
procrastinate_later

3
จุดที่ดีเกี่ยวกับการทำงานช้าลงช่วยให้ฉันเข้าใจว่าทำไม Netflix Ribbon load balancer code เกี่ยวข้องกับ doubles แล้วมีบรรทัดดังนี้:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
michaelok

@extraneon ฉันคิดว่าคุณหมายถึงการพูดว่า "ถ้าความถูกต้องคือต้องใช้BigDecimal", Double จะมี "ความแม่นยำ" มากกว่า (ตัวเลขเพิ่มเติม)
jspinella

164

ภาษาอังกฤษของฉันไม่ดีดังนั้นฉันจะเขียนตัวอย่างง่ายๆที่นี่

    double a = 0.02;
    double b = 0.03;
    double c = b - a;
    System.out.println(c);

    BigDecimal _a = new BigDecimal("0.02");
    BigDecimal _b = new BigDecimal("0.03");
    BigDecimal _c = _b.subtract(_a);
    System.out.println(_c);

เอาท์พุทโปรแกรม:

0.009999999999999998
0.01

บางคนยังต้องการใช้สองเท่าหรือไม่ ;)


11
@eldjon ไม่เป็นความจริง, ดูตัวอย่างนี้: BigDecimal two = new BigDecimal ("2"); BigDecimal eight = BigDecimal ใหม่ ("8"); System.out.println (two.divide (แปด)); ภาพนี้พิมพ์ออกมา 0.25
ลุดวิก

4
คู่ผสม forevr: D
vach

อย่างไรก็ตามหากคุณใช้การลอยแทนคุณจะได้รับความแม่นยำเดียวกันมากกว่า BigDecimal ในกรณีนั้น แต่วิธีที่มีประสิทธิภาพที่ดีขึ้น
EliuX

3
@EliuX Float อาจทำงานกับ 0.03-0.02 แต่ค่าอื่น ๆ ยังคงไม่แน่ชัด: System.out.println(0.003f - 0.002f);BigDecimal เป็นค่าที่แน่นอน:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin

50

มีสองความแตกต่างที่สำคัญจาก double:

  • ความแม่นยำตามอำเภอใจคล้ายกับ BigInteger ซึ่งสามารถมีจำนวนความแม่นยำและขนาดโดยพลการ
  • ฐาน 10 แทนฐาน 2 BigDecimal คือสเกล n * 10 ^ โดยที่ n เป็นจำนวนเต็มขนาดใหญ่ที่ลงนามโดยพลการและสเกลสามารถคิดเป็นจำนวนหลักเพื่อเลื่อนจุดทศนิยมไปทางซ้ายหรือขวา

เหตุผลที่คุณควรใช้ BigDecimal สำหรับการคำนวณทางการเงินไม่ใช่เพราะมันสามารถแทนตัวเลขใด ๆ ได้ แต่มันสามารถแทนตัวเลขทั้งหมดที่สามารถแสดงในรูปของทศนิยมและที่รวมตัวเลขแทบทั้งหมดในโลกการเงิน (คุณไม่เคยโอน 1/3 $ ถึงบางคน).


2
คำตอบนี้อธิบายถึงความแตกต่างอย่างแท้จริงและเหตุผลของการใช้ BigDecimal มากกว่าสองเท่า ความกังวลเรื่องประสิทธิภาพเป็นเรื่องรอง
Vortex

สิ่งนี้ไม่เป็นความจริง 100% คุณเขียนว่า BigDecimal คือ "n * 10 ^ scale" Java ใช้เพื่อลบจำนวนเท่านั้น ดังนั้นจะต้องถูกต้อง: "unscaledValue × 10 ^ -scale" สำหรับตัวเลขบวก BigDecimal ประกอบด้วย "ค่าจำนวนเต็มที่ไม่มีความแม่นยำโดยพลการและสัดส่วนสเกลจำนวนเต็ม 32 บิต" ในขณะที่สเกลคือจำนวนหลักทางด้านขวาของจุดทศนิยม
มือของ NOD

25

หากคุณเขียนค่าเศษส่วนเช่นเดียว1 / 7กับค่าทศนิยมที่คุณได้รับ

1/7 = 0.142857142857142857142857142857142857142857...

142857มีลำดับอนันต์ เนื่องจากคุณสามารถเขียนได้เฉพาะตัวเลขที่ จำกัด คุณจะต้องทราบถึงข้อผิดพลาดในการปัดเศษ (หรือการปัดเศษ) อย่างหลีกเลี่ยงไม่ได้

ตัวเลขที่ชอบ1/10หรือ1/100แสดงเป็นเลขฐานสองที่มีส่วนที่เป็นเศษส่วนก็มีจำนวนไม่สิ้นสุดเช่นกันหลังจากจุดทศนิยม:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles เก็บค่าเป็นเลขฐานสองและดังนั้นอาจแนะนำข้อผิดพลาดโดยการแปลงเลขทศนิยมให้เป็นเลขฐานสองโดยไม่ต้องทำเลขคณิตใด ๆ

ในทางกลับกันตัวเลขทศนิยม (เหมือนBigDecimal) ให้จัดเก็บทศนิยมแต่ละหลักตามที่เป็น ซึ่งหมายความว่าประเภททศนิยมไม่แม่นยำกว่าแบบทศนิยมหรือเลขจุดคงที่ในความหมายทั่วไป (กล่าวคือไม่สามารถจัดเก็บได้1/7โดยไม่สูญเสียความแม่นยำ) แต่แม่นยำยิ่งขึ้นสำหรับตัวเลขที่มีจำนวนทศนิยมทศนิยมเป็น มักจะเป็นกรณีสำหรับการคำนวณเงิน

Java's BigDecimalมีข้อได้เปรียบเพิ่มเติมว่าสามารถมีจำนวนหลักโดยพลการ (แต่ จำกัด ) ของตัวเลขทั้งสองด้านของจุดทศนิยมซึ่งถูก จำกัด โดยหน่วยความจำที่มีอยู่เท่านั้น


7

BigDecimal เป็นไลบรารีตัวเลขที่มีความแม่นยำสูงของ Oracle BigDecimal เป็นส่วนหนึ่งของภาษาจาวาและมีประโยชน์สำหรับแอพพลิเคชั่นหลากหลายตั้งแต่ด้านการเงินไปจนถึงวิทยาศาสตร์

ไม่มีอะไรผิดปกติกับการใช้คู่สำหรับการคำนวณบางอย่าง อย่างไรก็ตามสมมติว่าคุณต้องการคำนวณ Math.Pi * Math.Pi / 6 นั่นคือค่าของฟังก์ชัน Riemann Zeta สำหรับอาร์กิวเมนต์ที่แท้จริงของสองโครงการ (โครงการที่ฉันกำลังทำงานอยู่) ส่วนจุดลอยตัวนำเสนอคุณด้วยปัญหาที่เจ็บปวดของข้อผิดพลาดในการปัดเศษ

ในทางกลับกัน BigDecimal มีตัวเลือกมากมายสำหรับการคำนวณนิพจน์เพื่อความแม่นยำโดยพลการ วิธีการเพิ่มทวีคูณและหารดังที่อธิบายไว้ในเอกสารของ Oracle ด้านล่าง "แทนที่" ของ +, *, และ / ใน BigDecimal Java World:

http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

เมธอด comparTo มีประโยชน์อย่างยิ่งในขณะและสำหรับลูป

อย่างไรก็ตามระวังในการใช้ตัวสร้างของคุณสำหรับ BigDecimal ตัวสร้างสตริงมีประโยชน์มากในหลายกรณี ตัวอย่างเช่นรหัส

BigDecimal onethird = new BigDecimal ("0.33333333333");

ใช้การแทนค่าสตริงที่ 1/3 เพื่อแสดงถึงจำนวนที่ซ้ำกันแบบไม่สิ้นสุดไปจนถึงระดับความแม่นยำที่ระบุ ข้อผิดพลาดในการปัดเศษมีแนวโน้มมากที่สุดใน JVM ที่ลึกที่สุดซึ่งข้อผิดพลาดในการปัดเศษจะไม่รบกวนการคำนวณภาคปฏิบัติของคุณส่วนใหญ่ อย่างไรก็ตามจากประสบการณ์ส่วนตัวฉันพบว่ามีการคืบคลานขึ้นมา เมธอด setScale มีความสำคัญในเรื่องนี้ดังที่เห็นได้จากเอกสารของ Oracle


BigDecimal เป็นส่วนหนึ่งของไลบรารีตัวเลขที่มีความแม่นยำของจาวา 'In-house' ค่อนข้างไร้ความหมายในบริบทนี้โดยเฉพาะอย่างยิ่งที่ IBM เขียนขึ้น
มาร์ควิสแห่ง Lorne

@EJP: ฉันดูคลาส BigDecimal และเรียนรู้ว่า IBM เขียนเฉพาะบางส่วนเท่านั้น ความคิดเห็นลิขสิทธิ์ด้านล่าง: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK

7

หากคุณกำลังเผชิญกับการคำนวณมีกฎหมายเกี่ยวกับวิธีการคำนวณและความแม่นยำที่คุณควรใช้ หากคุณล้มเหลวคุณจะทำสิ่งผิดกฎหมาย เหตุผลที่แท้จริงเพียงอย่างเดียวคือการแสดงบิตของทศนิยมกรณีไม่แม่นยำ อย่างที่ Basil ใส่ไว้ตัวอย่างคือคำอธิบายที่ดีที่สุด เพียงเติมตัวอย่างของเขานี่คือสิ่งที่เกิดขึ้น:

static void theDoubleProblem1() {
    double d1 = 0.3;
    double d2 = 0.2;
    System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));

    float f1 = 0.3f;
    float f2 = 0.2f;
    System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));

    BigDecimal bd1 = new BigDecimal("0.3");
    BigDecimal bd2 = new BigDecimal("0.2");
    System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}

เอาท์พุท:

Double:  0,3 - 0,2 = 0.09999999999999998
Float:   0,3 - 0,2 = 0.10000001
BigDec:  0,3 - 0,2 = 0.1

นอกจากนี้เรายังมี:

static void theDoubleProblem2() {
    double d1 = 10;
    double d2 = 3;
    System.out.println("Double:\t 10 / 3 = " + (d1 / d2));

    float f1 = 10f;
    float f2 = 3f;
    System.out.println("Float:\t 10 / 3 = " + (f1 / f2));

    // Exception! 
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}

ให้ผลลัพธ์:

Double:  10 / 3 = 3.3333333333333335
Float:   10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion

แต่:

static void theDoubleProblem2() {
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}

มีผลลัพธ์:

BigDec:  10 / 3 = 3.3333 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.