1. บทนำ
ในการยืมคำศัพท์จากหนังสือ "Effactive Java" เป้าหมายการออกแบบหลักของการลอยและประเภทสองประเภทสำหรับการคำนวณทางวิทยาศาสตร์และการคำนวณทางวิศวกรรม พวกเขาดำเนินการจุดลอยตัวแบบไบนารีซึ่งได้รับการออกแบบอย่างระมัดระวังเพื่อให้การประมาณที่รวดเร็วยิ่งขึ้นในช่วงตัวเลขที่กว้าง อย่างไรก็ตามพวกเขาไม่ได้ให้ผลลัพธ์ที่แม่นยำอย่างสมบูรณ์และไม่ควรใช้ในสถานการณ์ที่จำเป็นต้องมีผลลัพธ์ที่แม่นยำ อย่างไรก็ตามการคำนวณเชิงพาณิชย์มักต้องการผลลัพธ์ที่แม่นยำและ BigDecimal มีประโยชน์ในเวลานี้
2. บทนำสู่ BigDecimal
BigDecimal ประกอบด้วยค่าที่ไม่ใช่จำนวนเต็มของความแม่นยำใด ๆ และสเกลจำนวนเต็ม 32 บิต หากเป็นศูนย์หรือเป็นบวกสเกลคือจำนวนตัวเลขหลังจากจุดทศนิยม หากเป็นจำนวนลบให้คูณค่าที่ไม่ปรับขนาดของจำนวนด้วยกำลังมาตราส่วนลบที่ 10 ดังนั้นค่าที่แสดงโดย bigdecimal คือ (unscaledValue × 10 ระดับ)
3. รหัสทดสอบ
3.1 Constructor (ประเภทพารามิเตอร์การทดสอบหลักคือสองตัวสร้างทั่วไปที่มีคู่และสตริง)
การคัดลอกรหัสมีดังนี้: bigdecimal adouble = ใหม่ bigdecimal (1.22);
System.out.println ("สร้างด้วยค่าคู่:" + adouble);
Abtring BigDecimal = ใหม่ BigDecimal ("1.22");
System.out.println ("สร้างด้วยค่าสตริง:" + Abtring);
คุณคิดว่าผลลัพธ์จะเป็นอย่างไร? หากคุณไม่คิดว่าคนแรกจะส่งออก 1.22 แสดงความยินดีกับคำตอบอย่างถูกต้องผลลัพธ์ผลลัพธ์จะเป็นดังนี้:
คัดลอกรหัสดังนี้: สร้างด้วย doublevalue: 1.21999999999999999733546474089962430298328399658203125
สร้างด้วยค่าสตริง: 1.22
คำอธิบายของ JDK:
1. ผลลัพธ์ของวิธีการก่อสร้างที่มีประเภทพารามิเตอร์สองเท่าไม่สามารถคาดเดาได้ บางคนอาจคิดว่า bigdecimal ที่สร้างขึ้นโดยการเขียน newbigdecimal (0.1) ใน Java เท่ากับ 0.1 (ค่าที่ไม่ใช่การปรับขนาด 1 ซึ่งมีขนาด 1) แต่จริง ๆ แล้วเท่ากับ 0.1000000000000000000000555511151231257827021181818181818 นี่เป็นเพราะ 0.1 ไม่สามารถแสดงได้อย่างถูกต้องเป็นสองเท่า (หรือสำหรับกรณีนี้ไม่สามารถแสดงเป็นทศนิยมไบนารีความยาว จำกัด ใด ๆ ) ด้วยวิธีนี้ค่าที่ส่งผ่านเข้าไปในวิธีการก่อสร้างจะไม่เท่ากับ 0.1 (แม้ว่าจะเป็นพื้นผิวเท่ากับค่านั้น)
2. ในทางกลับกันวิธีการก่อสร้างสตริงสามารถคาดการณ์ได้อย่างสมบูรณ์: การเขียนไปยัง newbigdecimal ("0.1") จะสร้าง bigdecimal ซึ่งเท่ากับ 0.1 ที่คาดหวัง ดังนั้นในการเปรียบเทียบโดยทั่วไปแนะนำให้ใช้สตริงคอนสตรัคเตอร์ก่อน
3. เมื่อต้องใช้สองครั้งเป็นแหล่งที่มาของ BigDecimal โปรดทราบว่าตัวสร้างนี้ให้การแปลงที่แม่นยำ มันไม่ได้ให้ผลลัพธ์เช่นเดียวกับการดำเนินการต่อไปนี้: ก่อนอื่นใช้วิธี double.toString (double) จากนั้นใช้ตัวสร้าง BigDecimal (String) เพื่อแปลงคู่เป็นสตริง เพื่อให้ได้ผลลัพธ์ให้ใช้วิธีการคงที่ (สองเท่า)
3.2 การดำเนินการเพิ่มเติม
การคัดลอกรหัสมีดังนี้: bigdecimal a = new bigdecimal ("1.22");
System.out.println ("สร้างด้วยค่าสตริง:" + a);
bigdecimal b = ใหม่ bigdecimal ("2.22");
a.add (b);
System.out.println ("APLUS B คือ:" + A);
เป็นเรื่องง่ายที่จะคิดว่ามันจะส่งออก:
คัดลอกรหัสดังนี้: สร้างด้วย StringValue: 1.22
A Plus B คือ: 3.44
แต่ในความเป็นจริง A Plus B คือ: 1.22
4. การวิเคราะห์รหัสที่มา
4.1 วิธีการ (doubleval)
คัดลอกรหัสดังต่อไปนี้: ค่าคงที่สาธารณะ bigdecimal valueof (double val) {
// การแจ้งเตือน: ศูนย์ส่งคืนสองครั้ง '0.0' ดังนั้นเราจึงไม่สามารถ FastPath ได้
// เพื่อใช้ศูนย์คงที่ สิ่งนี้อาจมีความสำคัญพอที่จะ
// ปรับวิธีการโรงงานแคชหรือส่วนตัวไม่กี่
// ค่าคงที่ในภายหลัง
ReturnNew BigDecimal (double.toString (val)); // ดูจุดที่สามใน 3.1 เกี่ยวกับคำอธิบาย JDK
-
4.2 เพิ่ม (bigdecimal augend) วิธีการ
Public BigDecimal Add (BigDecimal Augend) {long xs = this.intCompact; // bigdecimal แสดงด้วยจำนวนเต็มตัวอย่างเช่น A มีค่า intCompact ของ 122 long ys = augend.intCompact; // เหมือนกับข้างต้น BigInteger fst = (this.intCompact! ! = พองตัว)? null: augend.intval; int rscale = this.scale; // ทศนิยมวาง sdiff ยาว = (ยาว) rscale - augend.scale; // ความแตกต่างระหว่างตำแหน่งทศนิยมถ้า (sdiff! = 0) {// สถานที่ทศนิยมที่มีทศนิยมมากที่สุดเป็นผลถ้า (sdiff <0) rscale = augend.scale; if (xs == พองตัว || (xs = longmultiplypowerten (xs, เพิ่ม)) == พองตัว) fst = bigmultiplypowerten (เพิ่ม); } else {int raide = augend.checkscale (sdiff); ถ้า (ys == พองตัว || (ys = longmultiplypowerten (ys, เพิ่ม)) == พองตัว) SND = Augend.BigMultiplyPowerten (เพิ่ม); }} ถ้า (xs! = พองตัว && ys! = สูงเกินจริง) {long sum = xs + ys; if ((((sum ^ xs) & (sum ^ ys)))> = 0l) // ตัดสินว่ามีการล้นหรือไม่ ส่งคืน bigdecimal.valueof (ผลรวม, rcale); // กลับอินสแตนซ์ bigdecimal ที่ได้รับโดยใช้วิธีการคงที่ของ BigDecimal} ถ้า (fst == null) fst = biginteger.valueof (xs); // วิธีการคงที่ของโรงงานคงที่ BigInteger sum = fst.add (SND); return (fst.signum == snd.signum)? ใหม่ bigdecimal (ผลรวม, สูงเกินจริง, rcale, 0): ใหม่ bigdecimal (ผลรวม, compactvalfor (ผลรวม), rcale, 0); // กลับวัตถุ bigdecimal ที่ได้จากวิธีการก่อสร้างอื่น ๆ }}ข้างต้นเป็นเพียงการวิเคราะห์ซอร์สโค้ดนอกจากนี้ การลบการคูณและการหารจริงจะส่งคืนวัตถุขนาดใหญ่ใหม่ เนื่องจาก BigInteger และ BigDecimal นั้นไม่เปลี่ยนรูปวัตถุใหม่จะถูกสร้างขึ้นเมื่อดำเนินการในแต่ละขั้นตอนของการดำเนินการดังนั้น A.Add (B); แม้ว่าการดำเนินการเพิ่มเติมจะดำเนินการ แต่ A ไม่ได้บันทึกค่าหลังจากการดำเนินการเพิ่มเติม การใช้งานที่ถูกต้องควรเป็น A = a.add (b);
5. สรุป
(1) การคำนวณเชิงพาณิชย์ใช้ bigdecimal
(2) ลองใช้ตัวสร้างด้วยสตริงประเภทพารามิเตอร์
(3) bigdecimals ไม่เปลี่ยนรูป เมื่อดำเนินการแต่ละขั้นตอนของการดำเนินการวัตถุใหม่จะถูกสร้างขึ้น ดังนั้นเมื่อทำการเพิ่มการลบการคูณและการแบ่งคุณจะต้องบันทึกค่าหลังจากการดำเนินการ
(4) เรามักจะเพิกเฉยต่อรายละเอียดการใช้งานบางอย่างของ JDK พื้นฐานทำให้เกิดข้อผิดพลาดดังนั้นเราจึงต้องให้ความสนใจมากขึ้น
การอ้างอิง: คลาส BigDecimal