ฉันต้องคำนวณตัวแปรทศนิยมบางอย่างและเพื่อนร่วมงานของฉันแนะนำให้ฉันใช้BigDecimal
แทนdouble
เพราะมันจะแม่นยำมากขึ้น แต่ฉันต้องการรู้ว่ามันคืออะไรและทำอย่างไรให้ได้ประโยชน์สูงสุดBigDecimal
?
ฉันต้องคำนวณตัวแปรทศนิยมบางอย่างและเพื่อนร่วมงานของฉันแนะนำให้ฉันใช้BigDecimal
แทนdouble
เพราะมันจะแม่นยำมากขึ้น แต่ฉันต้องการรู้ว่ามันคืออะไรและทำอย่างไรให้ได้ประโยชน์สูงสุดBigDecimal
?
คำตอบ:
A BigDecimal
เป็นวิธีที่แน่นอนในการแสดงตัวเลข A Double
มีความแม่นยำแน่นอน การทำงานกับขนาดต่าง ๆ เป็นสองเท่า (พูดd1=1000.0
และd2=0.001
) อาจส่งผลให้0.001
ถูกทิ้งทั้งหมดเมื่อรวมกันเมื่อความแตกต่างของขนาดมีขนาดใหญ่มาก ด้วยBigDecimal
สิ่งนี้จะไม่เกิดขึ้น
ข้อเสียของBigDecimal
มันคือช้าลงและเป็นเรื่องยากมากที่จะเขียนโปรแกรมอัลกอริทึมแบบนั้น (เนื่องจาก+
-
*
และ/
ไม่ได้รับการโอเวอร์โหลด)
BigDecimal
ถ้าคุณจะจัดการกับเงินหรือความแม่นยำเป็นสิ่งที่ต้องใช้ อย่างอื่นDoubles
มีแนวโน้มที่จะดีพอ
ฉันขอแนะนำให้อ่านJavadocของBigDecimal
ที่พวกเขาทำสิ่งที่อธิบายดีกว่าที่ผมทำที่นี่ :)
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
BigDecimal
", Double จะมี "ความแม่นยำ" มากกว่า (ตัวเลขเพิ่มเติม)
ภาษาอังกฤษของฉันไม่ดีดังนั้นฉันจะเขียนตัวอย่างง่ายๆที่นี่
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
บางคนยังต้องการใช้สองเท่าหรือไม่ ;)
System.out.println(0.003f - 0.002f);
BigDecimal เป็นค่าที่แน่นอน:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
มีสองความแตกต่างที่สำคัญจาก double:
เหตุผลที่คุณควรใช้ BigDecimal สำหรับการคำนวณทางการเงินไม่ใช่เพราะมันสามารถแทนตัวเลขใด ๆ ได้ แต่มันสามารถแทนตัวเลขทั้งหมดที่สามารถแสดงในรูปของทศนิยมและที่รวมตัวเลขแทบทั้งหมดในโลกการเงิน (คุณไม่เคยโอน 1/3 $ ถึงบางคน).
หากคุณเขียนค่าเศษส่วนเช่นเดียว1 / 7
กับค่าทศนิยมที่คุณได้รับ
1/7 = 0.142857142857142857142857142857142857142857...
142857
มีลำดับอนันต์ เนื่องจากคุณสามารถเขียนได้เฉพาะตัวเลขที่ จำกัด คุณจะต้องทราบถึงข้อผิดพลาดในการปัดเศษ (หรือการปัดเศษ) อย่างหลีกเลี่ยงไม่ได้
ตัวเลขที่ชอบ1/10
หรือ1/100
แสดงเป็นเลขฐานสองที่มีส่วนที่เป็นเศษส่วนก็มีจำนวนไม่สิ้นสุดเช่นกันหลังจากจุดทศนิยม:
1/10 = binary 0.0001100110011001100110011001100110...
Doubles
เก็บค่าเป็นเลขฐานสองและดังนั้นอาจแนะนำข้อผิดพลาดโดยการแปลงเลขทศนิยมให้เป็นเลขฐานสองโดยไม่ต้องทำเลขคณิตใด ๆ
ในทางกลับกันตัวเลขทศนิยม (เหมือนBigDecimal
) ให้จัดเก็บทศนิยมแต่ละหลักตามที่เป็น ซึ่งหมายความว่าประเภททศนิยมไม่แม่นยำกว่าแบบทศนิยมหรือเลขจุดคงที่ในความหมายทั่วไป (กล่าวคือไม่สามารถจัดเก็บได้1/7
โดยไม่สูญเสียความแม่นยำ) แต่แม่นยำยิ่งขึ้นสำหรับตัวเลขที่มีจำนวนทศนิยมทศนิยมเป็น มักจะเป็นกรณีสำหรับการคำนวณเงิน
Java's BigDecimal
มีข้อได้เปรียบเพิ่มเติมว่าสามารถมีจำนวนหลักโดยพลการ (แต่ จำกัด ) ของตัวเลขทั้งสองด้านของจุดทศนิยมซึ่งถูก จำกัด โดยหน่วยความจำที่มีอยู่เท่านั้น
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
/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
หากคุณกำลังเผชิญกับการคำนวณมีกฎหมายเกี่ยวกับวิธีการคำนวณและความแม่นยำที่คุณควรใช้ หากคุณล้มเหลวคุณจะทำสิ่งผิดกฎหมาย เหตุผลที่แท้จริงเพียงอย่างเดียวคือการแสดงบิตของทศนิยมกรณีไม่แม่นยำ อย่างที่ 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