ทำไมไม่ใช้ Double หรือ Float เพื่อเป็นตัวแทนของสกุลเงิน?


939

ฉันได้รับการบอกเสมอว่าไม่เคยเป็นตัวแทนของเงินdoubleหรือfloatประเภทและคราวนี้ฉันตั้งคำถามกับคุณ: ทำไม?

ฉันแน่ใจว่ามีเหตุผลที่ดีมากฉันไม่ทราบว่ามันคืออะไร



80
เพื่อให้ชัดเจนพวกเขาไม่ควรใช้กับสิ่งที่ต้องการความแม่นยำ - ไม่ใช่แค่สกุลเงิน
เจฟฟ์

152
พวกเขาไม่ควรนำมาใช้สำหรับสิ่งที่ต้องถูกต้อง แต่คู่ 53 บิตอย่างมีนัยสำคัญ (~ 16 หลักทศนิยม) จะเพียงพอที่มักจะดีสำหรับสิ่งที่จำเป็นต้องมีเพียงความถูกต้อง
dan04

21
@jeff ความคิดเห็นของคุณผิดพลาดอย่างสมบูรณ์ว่าไบนารีทศนิยมคืออะไรดีและมันไม่ดีสำหรับ อ่านคำตอบโดย zneak ด้านล่างและโปรดลบความคิดเห็นที่ทำให้เข้าใจผิดของคุณ
Pascal Cuoq

คำตอบ:


985

เนื่องจากการลอยและการทวีคูณไม่สามารถแทนค่าฐานทวีคูณที่เราใช้เพื่อเงินอย่างถูกต้อง ปัญหานี้ไม่ได้มีไว้สำหรับ Java เท่านั้น แต่สำหรับภาษาการเขียนโปรแกรมใด ๆ ที่ใช้ชนิดทศนิยม 2 ฐาน

ในฐาน 10 คุณสามารถเขียน 10.25 เป็น 1025 * 10 -2 (จำนวนเต็มคูณด้วยกำลัง 10) ตัวเลขทศนิยมของ IEEE-754นั้นแตกต่างกัน แต่วิธีที่ง่ายมากในการคิดเกี่ยวกับมันคือการคูณด้วยกำลังสองแทน ตัวอย่างเช่นคุณอาจดูที่ 164 * 2 -4 (จำนวนเต็มคูณด้วยกำลังสอง) ซึ่งเท่ากับ 10.25 นั่นไม่ใช่ตัวเลขที่แสดงในหน่วยความจำ แต่ความหมายทางคณิตศาสตร์เหมือนกัน

แม้แต่ในฐาน 10 สัญลักษณ์นี้ไม่สามารถแสดงเศษส่วนที่ง่ายที่สุดได้อย่างแม่นยำ ตัวอย่างเช่นคุณไม่สามารถแทน 1/3: การแทนทศนิยมกำลังทำซ้ำ (0.3333 ... ) ดังนั้นจึงไม่มีจำนวนเต็ม จำกัด ที่คุณสามารถคูณด้วยกำลัง 10 เพื่อให้ได้ 1/3 คุณสามารถชำระในลำดับที่ยาวเป็น 3 และเลขชี้กำลังขนาดเล็กเช่น 333333333 * 10 -10แต่มันไม่ถูกต้อง: ถ้าคุณคูณด้วย 3 คุณจะไม่ได้ 1

อย่างไรก็ตามเพื่อจุดประสงค์ในการนับเงินอย่างน้อยสำหรับประเทศที่มีมูลค่าเงินอยู่ในลำดับความสำคัญของเงินดอลลาร์สหรัฐโดยปกติแล้วสิ่งที่คุณต้องมีก็คือสามารถเก็บทวีคูณได้ 10 -2ได้ดังนั้นมันจึงไม่สำคัญ 1/3 นั้นไม่สามารถแสดงได้

ปัญหาเกี่ยวกับการลอยตัวและเป็นสองเท่าคือส่วนใหญ่ของเงินเหมือนตัวเลขไม่ได้มีการแสดงที่แน่นอนเป็นจำนวนเต็มคูณด้วยพลังของ 2 ในความเป็นจริงเพียงทวีคูณของ 0.01 ระหว่าง 0 และ 1 (ซึ่งมีความสำคัญเมื่อจัดการ ด้วยเงินเพราะเป็นจำนวนเต็มเซ็นต์) ซึ่งสามารถแทนได้อย่างแน่นอนว่าเป็นเลขทศนิยมเลขฐานสองของ IEEE-754 คือ 0, 0.25, 0.5, 0.75 และ 1 ส่วนอื่น ๆ ทั้งหมดจะถูกปิดด้วยจำนวนเล็กน้อย เช่นเดียวกับตัวอย่าง 0.333333 หากคุณใช้ค่าทศนิยมสำหรับ 0.1 และคูณด้วย 10 คุณจะไม่ได้ 1

การแสดงเงินในฐานะdoubleหรือfloatอาจจะดูดีในตอนแรกเมื่อซอฟต์แวร์ลบความผิดพลาดเล็ก ๆ น้อย ๆ แต่เมื่อคุณทำการบวกการลบการคูณและการหารในจำนวนที่ไม่แน่นอนข้อผิดพลาดจะรวมกันและคุณจะได้ค่าที่เห็นได้ชัด ไม่ถูกต้อง สิ่งนี้ทำให้การลอยตัวและไม่เพียงพอเป็นสองเท่าสำหรับการรับมือกับเงินซึ่งต้องการความแม่นยำที่สมบูรณ์แบบสำหรับการทวีคูณของพลัง 10 ฐาน

วิธีแก้ปัญหาที่ใช้งานได้ในเกือบทุกภาษาคือการใช้จำนวนเต็มแทนและนับเซนต์ ตัวอย่างเช่น 1,025 จะเป็น $ 10.25 หลายภาษายังมีประเภทในตัวเพื่อจัดการกับเงิน ในหมู่คนอื่น ๆ Java มีBigDecimalชั้นเรียนและ C # มีdecimalประเภท


3
@Fran คุณจะได้รับข้อผิดพลาดในการปัดเศษและในบางกรณีที่มีการใช้สกุลเงินจำนวนมากการคำนวณอัตราดอกเบี้ยสามารถถูก
หัก

5
... ส่วนใหญ่ฐาน 10 เศษส่วนนั่นคือ ตัวอย่างเช่น 0.1 ไม่มีการแทนทศนิยมแบบไบนารีที่แน่นอน ดังนั้น1.0 / 10 * 10อาจไม่เท่ากับ 1.0
Chris Jester-Young

6
@ linuxuser27 ฉันคิดว่า Fran กำลังพยายามเป็นคนตลก อย่างไรก็ตามคำตอบของ zneak นั้นดีที่สุดเท่าที่ฉันเคยเห็นมาดีกว่า Bloch รุ่นคลาสสิค
Isaac Rabinovitch

5
แน่นอนถ้าคุณรู้ความแม่นยำคุณสามารถปัดเศษผลลัพธ์และหลีกเลี่ยงปัญหาทั้งหมดได้ สิ่งนี้เร็วกว่าและง่ายกว่าการใช้ BigDecimal มาก อีกทางเลือกหนึ่งคือการใช้ความแม่นยำคงที่หรือยาว
Peter Lawrey

2
@zneak คุณรู้หรือไม่ว่าจำนวนตรรกยะเป็นส่วนย่อยของจำนวนจริงใช่ไหม? IEEE-754 ตัวเลขจริงเป็นตัวเลขจริง พวกเขาเพิ่งเกิดขึ้นด้วยเหตุผล
Tim Seguine

314

จาก Bloch, J. , Java ที่มีประสิทธิภาพ, 2nd ed, รายการ 48:

floatและdoubleประเภทโดยเฉพาะอย่างยิ่งไม่เหมาะสำหรับการคำนวณทางการเงินเพราะมันเป็นไปไม่ได้ที่จะเป็นตัวแทน 0.1 (หรืออำนาจเชิงลบอื่น ๆ ของสิบ) เป็นfloatหรือ doubleว่า

ตัวอย่างเช่นสมมติว่าคุณมี $ 1.03 และคุณใช้จ่าย 42c คุณเหลือเงินเท่าไหร่?

System.out.println(1.03 - .42);

0.6100000000000001พิมพ์ออก

วิธีการที่เหมาะสมในการแก้ปัญหานี้คือการใช้งานBigDecimal, intหรือlong สำหรับการคำนวณทางการเงิน

แม้ว่าจะBigDecimalมีคำเตือนอยู่บ้าง (โปรดดูคำตอบที่ยอมรับในปัจจุบัน)


6
ฉันสับสนเล็กน้อยโดยการแนะนำให้ใช้ int หรือ long สำหรับการคำนวณทางการเงิน คุณแสดงถึง 1.03 ว่าเป็น int หรือ long ได้อย่างไร? ฉันลองแล้ว "long a = 1.04;" และ "long a = 104/100;" ไม่มีประโยชน์
ปีเตอร์

49
@ ปีเตอร์คุณใช้long a = 104และนับเป็นเซ็นต์แทนดอลลาร์
zneak

@zneak แล้วจะต้องใช้เปอร์เซ็นต์แบบใดในการคิดดอกเบี้ยทบต้นหรือคล้ายกัน
trusktr

3
@trusktr ฉันจะไปกับประเภททศนิยมของแพลตฟอร์มของคุณ ใน Java BigDecimalว่า
zneak

13
@maaartinus ... และคุณไม่คิดว่าการใช้สองครั้งสำหรับสิ่งเหล่านี้จะเกิดข้อผิดพลาดได้หรือไม่? ผมเคยเห็นปัญหาการปัดเศษลอยตีระบบจริงยาก แม้ในธนาคาร กรุณาอย่าแนะนำให้มันหรือถ้าคุณทำให้การว่าเป็นคำตอบที่แยกต่างหาก (เพื่อให้เราสามารถ downvote มัน: P)
EIS

75

นี่ไม่ใช่เรื่องของความถูกต้องและไม่เป็นเรื่องของความแม่นยำ มันเป็นเรื่องของการประชุมความคาดหวังของมนุษย์ที่ใช้ฐาน 10 สำหรับการคำนวณแทนฐาน 2 ตัวอย่างเช่นการใช้คู่สำหรับการคำนวณทางการเงินไม่ได้ผลิตคำตอบที่ "ผิด" ในแง่คณิตศาสตร์ แต่สามารถสร้างคำตอบที่เป็น ไม่ใช่สิ่งที่คาดหวังในแง่การเงิน

แม้ว่าคุณจะปัดเศษผลลัพธ์ของคุณในนาทีสุดท้ายก่อนที่จะส่งออกคุณยังสามารถได้รับผลลัพธ์เป็นครั้งคราวโดยใช้คู่ที่ไม่ตรงกับความคาดหวัง

ใช้เครื่องคิดเลขหรือคำนวณผลลัพธ์ด้วยมือ 1.40 * 165 = 231 อย่างไรก็ตามภายในใช้คู่ผสมในสภาพแวดล้อมของคอมไพเลอร์ / ระบบปฏิบัติการของฉันมันถูกเก็บไว้เป็นเลขฐานสองใกล้กับ 230.99999 ... ดังนั้นถ้าคุณตัดทอนตัวเลขคุณจะได้รับ 230 แทน 231 คุณอาจมีเหตุผลว่าการปัดเศษแทนการตัดทอน ให้ผลลัพธ์ที่ต้องการเป็น 231 นั่นเป็นความจริง แต่การปัดเศษจะต้องมีการปัดเศษเสมอ ไม่ว่าคุณจะใช้เทคนิคการปัดเศษยังคงมีเงื่อนไขขอบเขตเช่นนี้ที่จะปัดเศษเมื่อคุณคาดว่าจะปัดเศษขึ้น พวกเขาหายากมากพอที่จะไม่พบบ่อยครั้งผ่านการทดสอบหรือสังเกตการณ์ คุณอาจต้องเขียนโค้ดเพื่อค้นหาตัวอย่างที่แสดงผลลัพธ์ที่ไม่ทำงานตามที่คาดไว้

สมมติว่าคุณต้องการปัดเศษบางสิ่งให้เป็นเงินที่ใกล้ที่สุด ดังนั้นคุณจะได้ผลลัพธ์สุดท้ายคูณด้วย 100 บวก 0.5 ตัดทอนแล้วหารผลด้วย 100 เพื่อกลับไปที่เพนนี หากหมายเลขภายในที่คุณเก็บไว้คือ 3.46499999 .... แทน 3.465 คุณจะได้รับ 3.46 แทน 3.47 เมื่อคุณปัดจำนวนเป็นเงินที่ใกล้ที่สุด แต่การคำนวณฐาน 10 ของคุณอาจแสดงให้เห็นว่าคำตอบนั้นควรเป็น 3.465 ซึ่งแน่นอนว่าควรจะสูงถึง 3.47 ไม่ใช่ลงที่ 3.46 สิ่งเหล่านี้เกิดขึ้นเป็นครั้งคราวในชีวิตจริงเมื่อคุณใช้เป็นสองเท่าในการคำนวณทางการเงิน มันเป็นของหายากจึงมักจะไม่มีใครสังเกตเห็นว่าเป็นปัญหา แต่มันเกิดขึ้น

หากคุณใช้ฐาน 10 สำหรับการคำนวณภายในของคุณแทนที่จะเป็นสองเท่าคำตอบคือสิ่งที่มนุษย์คาดหวังเสมอโดยไม่มีข้อบกพร่องอื่น ๆ ในรหัสของคุณ


2
ที่เกี่ยวข้องน่าสนใจ: ในคอนโซล Chrome js ของฉัน: Math.round (.4999999999999999): 0 Math.round (.49999999999999999): 1
เคอร์ติส Yallop

16
คำตอบนี้ทำให้เข้าใจผิด 1.40 * 165 = 231 ตัวเลขใด ๆ ที่นอกเหนือจาก 231 นั้นผิดในแง่คณิตศาสตร์ (และประสาทสัมผัสอื่น ๆ )
Karu

2
@Karu ฉันคิดว่านั่นเป็นสาเหตุที่ Randy บอกว่าการลอยตัวไม่ดี ... คอนโซล Chrome JS ของฉันแสดงผลเป็น 230.99999999999997 นั่นเป็นสิ่งที่ผิดซึ่งเป็นจุดที่เกิดขึ้นในคำตอบ
trusktr

6
@Karu: Imho คำตอบไม่ผิดทางคณิตศาสตร์ เป็นเพียงว่ามีคำถาม 2 ข้อที่ถูกตอบไม่ใช่คำถามที่ถูกถาม คำถามที่ผู้แปลคอมไพเลอร์ของคุณคือ 1.39999999 * 164.99999999 และอื่น ๆ ที่ถูกต้องทางคณิตศาสตร์เท่ากับ 230.99999 .... เห็นได้ชัดว่าไม่ใช่คำถามที่ถูกถามในตอนแรก ....
markus

2
@CurtisYallop เพราะค่า double ปิดเป็น 0.49999999999999999 คือ 0.5 ทำไมMath.round(0.49999999999999994)ส่งคืน 1
phuclv

53

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

หากผลลัพธ์ของคุณอยู่บนเส้นขอบระหว่างการปัดเศษขึ้นและปัดเศษลงและเพนนีสุดท้ายมีความสำคัญจริง ๆ คุณควรจะบอกผู้ชมว่าคำตอบเกือบตรงกลาง - โดยแสดงตำแหน่งทศนิยมเพิ่มเติม

ปัญหาที่เกิดขึ้นกับคู่และอื่น ๆ ด้วยลอยคือเมื่อพวกเขาถูกนำมาใช้เพื่อรวมจำนวนมากและตัวเลขขนาดเล็ก ในจาวา

System.out.println(1000000.0f + 1.2f - 1000000.0f);

ผลลัพธ์ใน

1.1875

16
นี้!!!! ฉันกำลังค้นหาคำตอบทั้งหมดเพื่อค้นหาข้อเท็จจริงที่เกี่ยวข้อง !!! ในการคำนวณตามปกติไม่มีใครสนใจถ้าคุณเป็นเศษส่วนของร้อย แต่ที่นี่ด้วยตัวเลขที่สูงได้อย่างง่ายดายบางดอลลาร์หายไปต่อธุรกรรม!
Falco

22
และตอนนี้ลองนึกภาพใครบางคนที่มีรายรับรายวัน 0.01% จาก 1 ล้านดอลลาร์ของเขา - เขาจะไม่ได้รับอะไรเลยในแต่ละวัน - และหลังจากหนึ่งปีที่เขาไม่ได้รับ 1,000 ดอลลาร์เขาจะต้องได้รับเรื่องนี้
Falco

6
ปัญหาไม่ใช่ความแม่นยำ แต่การลอยนั้นไม่ได้บอกคุณว่ามันไม่ถูกต้อง จำนวนเต็มสามารถถือได้ถึง 10 หลักลอยสามารถถือได้ถึง 6 โดยไม่กลายเป็นไม่ถูกต้อง (เมื่อคุณตัดตาม) มันอนุญาตให้สิ่งนี้ในขณะที่จำนวนเต็มได้รับมากเกินไปและภาษาเช่น java จะเตือนคุณหรือไม่อนุญาต เมื่อคุณใช้คู่คุณสามารถไปได้ถึง 16 หลักซึ่งเพียงพอสำหรับการใช้งานหลายกรณี
sigi

39

ลอยและคู่เป็นประมาณ หากคุณสร้าง BigDecimal และส่งทุ่นลอยไปที่ Constructor คุณจะเห็นว่าการลอยนั้นเท่ากับจริง:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

นี่อาจไม่ใช่วิธีที่คุณต้องการเป็นตัวแทน $ 1.01

ปัญหาคือสเป็คของ IEEE ไม่มีวิธีที่จะแสดงเศษส่วนทั้งหมดได้อย่างแน่นอนซึ่งบางส่วนก็กลายเป็นเศษส่วนซ้ำดังนั้นคุณจึงมีข้อผิดพลาดโดยประมาณ เนื่องจากนักบัญชีชอบสิ่งต่าง ๆ ออกมาตรงกับเงินและลูกค้าจะรำคาญถ้าพวกเขาจ่ายบิลและหลังจากการชำระเงินถูกประมวลผลพวกเขาเป็นหนี้. 01 และพวกเขาถูกเรียกเก็บค่าธรรมเนียมหรือไม่สามารถปิดบัญชีได้ดีกว่าที่จะใช้ ประเภทที่แน่นอนเช่นทศนิยม (ใน C #) หรือ java.math.BigDecimal ใน Java

มันไม่ได้ว่าข้อผิดพลาดที่ไม่สามารถควบคุมได้ถ้าคุณรอบ: ดูบทความนี้โดยปีเตอร์ Lawrey มันง่ายกว่าที่จะไม่ต้องปัดเศษในตอนแรก แอปพลิเคชั่นส่วนใหญ่ที่จัดการเงินไม่ต้องใช้คณิตศาสตร์จำนวนมากการดำเนินการประกอบด้วยการเพิ่มสิ่งต่าง ๆ หรือจัดสรรจำนวนเงินให้กับถังที่แตกต่างกัน การแนะนำจุดลอยตัวและการปัดเศษเป็นเรื่องยุ่งยาก


5
float, doubleและBigDecimalมีความหมายที่แน่นอนค่า การแปลงรหัสเป็นวัตถุไม่แน่นอนเช่นเดียวกับการดำเนินการอื่น ๆ ประเภทตัวเองไม่แน่นอน
chux - Reinstate Monica

1
@ chux: ลองอ่านอีกครั้งฉันคิดว่าคุณมีประเด็นที่ถ้อยคำของฉันดีขึ้น ฉันจะแก้ไขและใส่คำใหม่
นาธานฮิวจ์

28

ฉันจะเสี่ยงต่อการถูก downvote แต่ฉันคิดว่าความไม่เหมาะสมของตัวเลขจุดลอยตัวสำหรับการคำนวณสกุลเงินนั้นเกินจริง ตราบใดที่คุณตรวจสอบให้แน่ใจว่าคุณทำการปัดเศษเซ็นอย่างถูกต้องและมีตัวเลขที่สำคัญพอที่จะทำงานด้วยเพื่อตอบโต้การไม่ตรงกันการแทนค่าฐานสองทศนิยมที่อธิบายโดย zneak จะไม่มีปัญหา

ผู้ที่คำนวณด้วยสกุลเงินใน Excel มักใช้ความแม่นยำสองเท่าแบบลอยตัว (ไม่มีประเภทสกุลเงินใน Excel) และฉันยังไม่เห็นใครบ่นเกี่ยวกับข้อผิดพลาดในการปัดเศษ

แน่นอนคุณต้องอยู่ในเหตุผล; เช่นเว็บช็อปแบบง่าย ๆ อาจจะไม่ประสบปัญหาใด ๆ กับการลอยความแม่นยำสองเท่า แต่ถ้าคุณทำเช่นการบัญชีหรืออะไรก็ตามที่ต้องเพิ่มตัวเลขจำนวนมาก (ไม่ จำกัด ) คุณไม่ต้องการสัมผัสตัวเลขทศนิยมด้วยสิบฟุต เสา.


3
นี่เป็นคำตอบที่ดีพอสมควร ในกรณีส่วนใหญ่จะใช้งานได้อย่างสมบูรณ์แบบ
Vahid Amiri

2
ควรสังเกตว่าธนาคารเพื่อการลงทุนส่วนใหญ่ใช้สองเท่าเช่นเดียวกับโปรแกรม C ++ ส่วนใหญ่ บางตัวใช้งานมานาน แต่มีปัญหาในการติดตามสเกล
Peter Lawrey

20

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

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

ต้องบอกว่าแม้ว่าคุณจะใช้เลขคณิตจุดคงที่คุณยังต้องปัดเศษตัวเลขไม่ใช่เพราะข้อเท็จจริงที่ว่า BigInteger และ BigDecimal ให้ข้อผิดพลาดหากคุณได้รับตัวเลขทศนิยมเป็นระยะ มีการประมาณยังอยู่ตรงนี้ด้วย

ตัวอย่างเช่น COBOL ซึ่งใช้ในอดีตสำหรับการคำนวณทางการเงินมีความแม่นยำสูงสุด 18 ตัวเลข ดังนั้นมักจะมีการปัดเศษโดยนัย

โดยสรุปแล้วในความคิดของฉันคู่ที่ไม่เหมาะสมส่วนใหญ่สำหรับความแม่นยำ 16 หลักซึ่งอาจไม่เพียงพอไม่ใช่เพราะมันประมาณ

พิจารณาผลลัพธ์ต่อไปนี้ของโปรแกรมที่ตามมา มันแสดงให้เห็นว่าหลังจากการปัดเศษคู่ให้ผลลัพธ์เหมือนกับ BigDecimal มากถึง 16

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}

15

ผลลัพธ์ของเลขทศนิยมไม่ถูกต้องซึ่งทำให้ไม่เหมาะสมสำหรับการคำนวณทางการเงินใด ๆ ที่ต้องการผลลัพธ์ที่แน่นอนและไม่ประมาณ float และ double ถูกออกแบบมาสำหรับการคำนวณทางวิศวกรรมและวิทยาศาสตร์และหลายครั้งไม่ได้ผลลัพธ์ที่แน่นอนนอกจากนี้การคำนวณจุดลอยตัวอาจแตกต่างจาก JVM ถึง JVM ดูตัวอย่างด้านล่างของ BigDecimal และ double primitive ซึ่งใช้แทนค่าเงินค่อนข้างชัดเจนว่าการคำนวณจุดลอยตัวอาจไม่แม่นยำและควรใช้ BigDecimal สำหรับการคำนวณทางการเงิน

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

เอาท์พุท:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

3
ให้เราลองทำอย่างอื่นนอกเหนือจากการบวก / การลบและการ Mutplicaiton จำนวนเต็มถ้าโค้ดคำนวณอัตรารายเดือนของสินเชื่อ 7% ทั้งสองประเภทจะต้องล้มเหลวในการให้ค่าที่แน่นอนและต้องปัดเศษเป็น 0.01 ที่ใกล้ที่สุด การปัดเศษไปยังหน่วยการเงินที่ต่ำที่สุดคือส่วนหนึ่งของการคำนวณเงินการใช้ประเภททศนิยมหลีกเลี่ยงความต้องการด้วยการบวก / การลบ - แต่ไม่มากนัก
chux - Reinstate Monica

@ chux-ReinstateMonica: หากคิดดอกเบี้ยทบต้นทุกเดือนให้คำนวณดอกเบี้ยในแต่ละเดือนด้วยการรวมยอดดุลรายวันเข้าด้วยกันคูณด้วย 7 (อัตราดอกเบี้ย) แล้วหารหารด้วยเงินที่ใกล้ที่สุดตามจำนวนวันใน ปี. ไม่มีการปัดเศษใด ๆ ยกเว้นหนึ่งครั้งต่อเดือนในขั้นตอนสุดท้าย
supercat

@supercat ความคิดเห็นของฉันเน้นการใช้เลขฐานสองแบบ FP ของหน่วยการเงินที่เล็กที่สุดหรือ FP แบบทศนิยมทั้งคู่มีปัญหาการปัดเศษที่คล้ายกันเช่นในความคิดเห็นของคุณด้วย "และหารการปัดเศษเป็นเงินที่ใกล้ที่สุด" การใช้ฐาน 2 หรือฐาน 10 FP ไม่ได้ให้ประโยชน์ในทางใดทางหนึ่งในสถานการณ์ของคุณ
chux - Reinstate Monica

@ chux-ReinstateMonica: ในสถานการณ์ข้างต้นหากคณิตศาสตร์คิดว่าดอกเบี้ยควรเท่ากับจำนวนครึ่งเซ็นต์แน่นอนโปรแกรมการเงินที่ถูกต้องจะต้องวนตามที่ระบุไว้อย่างแม่นยำ หากการคำนวณจุดลอยตัวให้ค่าดอกเบี้ยเช่น $ 1.23499941 แต่ค่าที่แม่นยำทางคณิตศาสตร์ก่อนการปัดเศษควรเป็น $ 1.235 และการปัดเศษถูกระบุว่าเป็น "ใกล้เคียงที่สุด" การใช้การคำนวณจุดลอยดังกล่าวจะไม่ทำให้เกิดผลลัพธ์ ถูกปิดโดย $ 0.000059 แต่โดยรวม $ 0.01 ซึ่งสำหรับวัตถุประสงค์ด้านการบัญชีคือผิดธรรมดา
supercat

@supercat การใช้เลขฐานสองdoubleFP ไปยัง cent จะไม่มีปัญหาในการคำนวณถึง 0.5 cent เนื่องจาก FP แบบทศนิยมจะไม่มี หากการคำนวณจุดลอยตัวให้ผลตอบแทนที่น่าสนใจเช่น 123.499941 ¢ไม่ว่าจะผ่านทาง FP แบบไบนารีหรือ FP แบบทศนิยมปัญหาการปัดเศษสองครั้งจะเหมือนกัน - ไม่ได้เปรียบในทางใดทางหนึ่ง หลักฐานของคุณดูเหมือนว่าจะถือว่าค่าที่แม่นยำทางคณิตศาสตร์และ FP แบบทศนิยมนั้นเหมือนกัน - บางอย่างที่ FP แบบทศนิยมไม่รับประกัน 0.5 / 7.0 * 7.0 เป็นปัญหาสำหรับสำหรับไบนารีและ deicmal FP IAC ส่วนใหญ่จะเป็น moot ตามที่ฉันคาดหวังรุ่นถัดไปของ C เพื่อให้ FP ทศนิยม
chux - Reinstate Monica

11

ดังที่ได้กล่าวไว้ก่อนหน้านี้ว่า "การแสดงเงินเป็นสองเท่าหรือลอยนั้นอาจดูดีในตอนแรกเนื่องจากซอฟต์แวร์ปัดเศษข้อผิดพลาดเล็ก ๆ น้อย ๆ แต่เมื่อคุณทำการเพิ่มเติมการลบการคูณและการหารบนตัวเลขที่ไม่แน่นอนมากขึ้น เมื่อมีข้อผิดพลาดเกิดขึ้นทำให้สิ่งนี้ลอยตัวและไม่เพียงพอเป็นสองเท่าสำหรับการจัดการกับเงิน

ในที่สุด Java ก็มีวิธีมาตรฐานในการทำงานกับสกุลเงินและเงิน!

JSR 354: API เงินและสกุลเงิน

JSR 354 จัดทำ API สำหรับการเป็นตัวแทนการขนส่งและการคำนวณที่ครอบคลุมด้วยเงินและสกุลเงิน คุณสามารถดาวน์โหลดได้จากลิงค์นี้:

JSR 354: การดาวน์โหลด API เงินและสกุลเงิน

ข้อมูลจำเพาะประกอบด้วยสิ่งต่าง ๆ ดังต่อไปนี้:

  1. API สำหรับจัดการเช่นจำนวนเงินและสกุลเงิน
  2. API เพื่อสนับสนุนการใช้งานที่สามารถเปลี่ยนแทนกันได้
  3. โรงงานสำหรับสร้างอินสแตนซ์ของคลาสการใช้งาน
  4. ฟังก์ชันการคำนวณการแปลงและการจัดรูปแบบของจำนวนเงิน
  5. Java API สำหรับการทำงานกับ Money และ Currencies ซึ่งมีการวางแผนที่จะรวมอยู่ใน Java 9
  6. คลาสข้อมูลจำเพาะและอินเทอร์เฟซทั้งหมดอยู่ในแพ็คเกจ javax.money. *

ตัวอย่างตัวอย่างของ JSR 354: API เงินและสกุลเงิน:

ตัวอย่างการสร้าง MonetaryAmount และพิมพ์ไปยังคอนโซลมีลักษณะดังนี้ ::

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

เมื่อใช้ API การใช้งานอ้างอิงรหัสที่จำเป็นนั้นง่ายกว่ามาก:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

API ยังสนับสนุนการคำนวณด้วย MonetaryAmounts:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

หน่วยสกุลเงินและจำนวนเงิน

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

MonetaryAmount มีวิธีการต่าง ๆ ที่อนุญาตให้เข้าถึงสกุลเงินที่กำหนดจำนวนตัวเลขความแม่นยำและอื่น ๆ :

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number. 
// So we assign numberValue to a variable of type Number
Number number = numberValue;

MonetaryAmounts สามารถปัดเศษโดยใช้ตัวดำเนินการปัดเศษ:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

เมื่อทำงานกับคอลเลกชันของ MonetaryAmounts วิธีการอรรถประโยชน์ที่ดีสำหรับการกรองการเรียงลำดับและการจัดกลุ่มจะพร้อมใช้งาน

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

การดำเนินการ MonetaryAmount ที่กำหนดเอง

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
  BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
  BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
  return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

แหล่งข้อมูล:

การจัดการเงินและสกุลเงินใน Java ด้วย JSR 354

การค้นหา Java 9 Money และ Currency API (JSR 354)

ดูเพิ่มเติมที่: JSR 354 - สกุลเงินและเงิน


5

หากการคำนวณของคุณเกี่ยวข้องกับขั้นตอนต่าง ๆ เลขคณิตความแม่นยำโดยพลการจะไม่ครอบคลุม 100%

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

ความแม่นยำโดยพลการจะไม่ช่วยได้เพราะอาจมีตัวเลขที่มีตำแหน่งทศนิยมจำนวนมากเสมอหรือผลลัพธ์บางอย่างเช่น0.6666666... ไม่มีการแทนค่าโดยพลการจะครอบคลุมตัวอย่างสุดท้าย ดังนั้นคุณจะมีข้อผิดพลาดเล็กน้อยในแต่ละขั้นตอน

ข้อผิดพลาดเหล่านี้จะเพิ่มขึ้นในที่สุดอาจกลายเป็นเรื่องง่ายที่จะไม่สนใจอีกต่อไป นี้เรียกว่าข้อผิดพลาดในการขยายพันธุ์


4

คำตอบส่วนใหญ่ให้ความสำคัญกับเหตุผลที่ไม่ควรใช้การคำนวณเงินและสกุลเงินเป็นสองเท่า และฉันเห็นด้วยกับพวกเขาทั้งหมด

ไม่ได้หมายความว่าจะไม่สามารถใช้คู่ผสมเพื่อจุดประสงค์นั้นได้

ฉันทำงานหลายโครงการที่มีข้อกำหนด gc ต่ำมากและการมีวัตถุ BigDecimal เป็นผู้มีส่วนร่วมอย่างมากในค่าใช้จ่ายนั้น

มันขาดความเข้าใจเกี่ยวกับการแสดงซ้ำและขาดประสบการณ์ในการจัดการความถูกต้องและความแม่นยำที่นำมาซึ่งข้อเสนอแนะที่ชาญฉลาดนี้

คุณสามารถทำให้มันทำงานได้ถ้าคุณสามารถจัดการกับความต้องการความแม่นยำและความแม่นยำของโครงการของคุณซึ่งจะต้องทำตามช่วงของค่าสองเท่าที่เกี่ยวข้องกับ

คุณสามารถอ้างถึงวิธี FuzzyCompare ของฝรั่งเพื่อให้ได้แนวคิดเพิ่มเติม ความอดทนพารามิเตอร์เป็นกุญแจสำคัญ เราจัดการกับปัญหานี้สำหรับแอปพลิเคชันการซื้อขายหลักทรัพย์และเราได้ทำการวิจัยอย่างละเอียดเกี่ยวกับความอดทนที่จะใช้สำหรับค่าตัวเลขที่แตกต่างกันในช่วงที่แตกต่างกัน

นอกจากนี้อาจมีสถานการณ์เมื่อคุณถูกล่อลวงให้ใช้ Double wrappers เป็นคีย์แผนที่พร้อมกับแผนที่แฮชที่กำลังใช้งานอยู่ มันมีความเสี่ยงมากเพราะ Double.equals และรหัสแฮชสำหรับค่าตัวอย่าง "0.5" & "0.6 - 0.1" จะทำให้เกิดความยุ่งเหยิง


2

คำตอบจำนวนมากที่โพสต์ในคำถามนี้จะกล่าวถึง IEEE และมาตรฐานที่อยู่รอบ ๆ เลขทศนิยม

มาจากพื้นหลังวิทยาศาสตร์ที่ไม่ใช่คอมพิวเตอร์ (ฟิสิกส์และวิศวกรรม) ฉันมักจะมองปัญหาจากมุมมองที่แตกต่างกัน สำหรับฉันเหตุผลที่ฉันจะไม่ใช้ double หรือ float ในการคำนวณทางคณิตศาสตร์คือฉันจะสูญเสียข้อมูลมากเกินไป

ทางเลือกคืออะไร? มีมากมาย (และอีกมากมายที่ฉันไม่ทราบ!)

BigDecimal ใน Java เป็นภาษาพื้นเมืองของ Java Apfloat เป็นอีกหนึ่งไลบรารีที่มีความแม่นยำตามอำเภอใจสำหรับ Java

ประเภทข้อมูลเลขฐานสิบใน C # เป็นทางเลือก. NET ของ Microsoft สำหรับตัวเลข 28 หลัก

SciPy (Scientific Python) อาจจะสามารถจัดการกับการคำนวณทางการเงินได้ (ฉันไม่ได้ลอง แต่ก็สงสัยเช่นกัน)

GNU Multiple Precision Library (GMP) และ GNU MFPR Library เป็นแหล่งข้อมูลฟรีและโอเพ่นซอร์สสองแหล่งสำหรับ C และ C ++

นอกจากนี้ยังมีไลบรารีความแม่นยำเชิงตัวเลขสำหรับ JavaScript (!) และฉันคิดว่า PHP ซึ่งสามารถจัดการการคำนวณทางการเงินได้

นอกจากนี้ยังมีกรรมสิทธิ์ (โดยเฉพาะอย่างยิ่งฉันคิดว่าสำหรับ Fortran) และโซลูชั่นโอเพ่นซอร์สเช่นเดียวกับภาษาคอมพิวเตอร์จำนวนมาก

ฉันไม่ใช่นักวิทยาศาสตร์คอมพิวเตอร์โดยการฝึกฝน อย่างไรก็ตามฉันมักจะพึ่งพา BigDecimal ใน Java หรือทศนิยมใน C # ฉันไม่ได้ลองใช้วิธีแก้ไขปัญหาอื่น ๆ ที่ฉันระบุไว้ แต่อาจจะดีมากเช่นกัน

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

คุณอาจมองเข้าไปในห้องสมุดที่ให้บริการฟรีและเป็นกรรมสิทธิ์ใน C, C ++ และ Fortran


1
เกี่ยวกับ SciPy / Numpy ไม่สนับสนุนความแม่นยำคงที่ (เช่นทศนิยมทศนิยมของ Python) ( docs.scipy.org/doc/numpy-dev/user/basics.types.html ) ฟังก์ชั่นบางอย่างจะทำงานไม่ถูกต้องกับทศนิยม (ตัวอย่างเช่น isnan) นุ่นอยู่บนพื้นฐานของ Numpy และริเริ่มที่ AQR ซึ่งเป็นหนึ่งในกองทุนป้องกันความเสี่ยงเชิงปริมาณที่สำคัญ ดังนั้นคุณมีคำตอบเกี่ยวกับการคำนวณทางการเงิน (ไม่ใช่บัญชีร้านขายของชำ)
comte

2

เพื่อเพิ่มคำตอบก่อนหน้านี้ยังมีตัวเลือกในการใช้Joda-Moneyใน Java นอกเหนือจาก BigDecimal เมื่อจัดการกับปัญหาที่ระบุในคำถาม ชื่อ Java modul คือ org.joda.money

ต้องใช้ Java SE 8 หรือใหม่กว่าและไม่มีการขึ้นต่อกัน

เพื่อความแม่นยำมากขึ้นมีการพึ่งพาเวลารวบรวม แต่ไม่จำเป็น

<dependency>
  <groupId>org.joda</groupId>
  <artifactId>joda-money</artifactId>
  <version>1.0.1</version>
</dependency>

ตัวอย่างการใช้ Joda Money:

  // create a monetary value
  Money money = Money.parse("USD 23.87");

  // add another amount with safe double conversion
  CurrencyUnit usd = CurrencyUnit.of("USD");
  money = money.plus(Money.of(usd, 12.43d));

  // subtracts an amount in dollars
  money = money.minusMajor(2);

  // multiplies by 3.5 with rounding
  money = money.multipliedBy(3.5d, RoundingMode.DOWN);

  // compare two amounts
  boolean bigAmount = money.isGreaterThan(dailyWage);

  // convert to GBP using a supplied rate
  BigDecimal conversionRate = ...;  // obtained from code outside Joda-Money
  Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);

  // use a BigMoney for more complex calculations where scale matters
  BigMoney moneyCalc = money.toBigMoney();

เอกสารประกอบ: http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html

ตัวอย่างการใช้งาน: https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money


0

ตัวอย่างบางส่วน ... งานนี้ (ใช้งานไม่ได้ตามที่คาดหวัง) ในเกือบทุกภาษาการเขียนโปรแกรม ... ฉันได้ลองใช้ Delphi, VBScript, Visual Basic, JavaScript และตอนนี้กับ Java / Android:

    double total = 0.0;

    // do 10 adds of 10 cents
    for (int i = 0; i < 10; i++) {
        total += 0.1;  // adds 10 cents
    }

    Log.d("round problems?", "current total: " + total);

    // looks like total equals to 1.0, don't?

    // now, do reverse
    for (int i = 0; i < 10; i++) {
        total -= 0.1;  // removes 10 cents
    }

    // looks like total equals to 0.0, don't?
    Log.d("round problems?", "current total: " + total);
    if (total == 0.0) {
        Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
    } else {
        Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
    }

เอาท์พุท:

round problems?: current total: 0.9999999999999999 round problems?: current total: 2.7755575615628914E-17 round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!


3
ปัญหาไม่ใช่ว่าข้อผิดพลาดในการปัดเศษเกิดขึ้น แต่คุณไม่ได้จัดการกับมัน ปัดเศษผลลัพธ์เป็นทศนิยมสองตำแหน่ง (ถ้าคุณต้องการเซ็นต์) และคุณทำเสร็จแล้ว
maaartinus

0

โฟลทเป็นรูปแบบเลขฐานสิบของทศนิยมที่มีการออกแบบที่แตกต่างกัน พวกเขาเป็นสองสิ่งที่แตกต่างกัน มีข้อผิดพลาดเล็กน้อยระหว่างสองประเภทเมื่อแปลงเป็นแบบอื่น นอกจากนี้โฟลตยังได้รับการออกแบบมาเพื่อแสดงค่าจำนวนมากสำหรับวิทยาศาสตร์ นั่นหมายความว่ามันถูกออกแบบมาให้สูญเสียความแม่นยำไปยังจำนวนที่น้อยมากและมากที่สุดด้วยจำนวนไบต์คงที่ ทศนิยมไม่สามารถแสดงถึงค่าจำนวนอนันต์ แต่จะเป็นเพียงจำนวนหลักทศนิยมนั้น ดังนั้นโฟลตและทศนิยมจึงมีวัตถุประสงค์ที่แตกต่างกัน

มีบางวิธีในการจัดการข้อผิดพลาดสำหรับค่าสกุลเงิน:

  1. ใช้จำนวนเต็มยาวและนับเป็นเซ็นต์แทน

  2. ใช้ความแม่นยำสองเท่ารักษาตัวเลขสำคัญของคุณไว้ที่ 15 เท่านั้นเพื่อให้สามารถจำลองทศนิยมได้อย่างแม่นยำ ปัดเศษก่อนนำเสนอค่า ปัดเศษเมื่อทำการคำนวณ

  3. ใช้ไลบรารีทศนิยมเช่น Java BigDecimal ดังนั้นคุณไม่จำเป็นต้องใช้ double เพื่อจำลองทศนิยม

ป.ล. เป็นที่น่าสนใจที่จะรู้ว่าเครื่องคิดเลขวิทยาศาสตร์แบบใช้มือถือส่วนใหญ่ทำงานเป็นทศนิยมแทนที่จะลอย ดังนั้นไม่มีใครร้องเรียนข้อผิดพลาดการแปลงลอย

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