ภาษาโปรแกรมสามารถทำอะไรได้บ้างเพื่อหลีกเลี่ยงข้อผิดพลาดที่เกิดจากการลอยตัว?


28

ความเข้าใจผิดของการคำนวณจุดลอยตัวและการย่อ - สั้นเป็นสาเหตุหลักของความประหลาดใจและความสับสนในการเขียนโปรแกรม (พิจารณาจำนวนคำถามใน Stack Overflow ที่เกี่ยวข้องกับ "ตัวเลขไม่ถูกต้อง" เมื่อพิจารณาถึงโปรแกรมเมอร์หลายคนยังไม่เข้าใจความหมายของมัน แต่ก็มีศักยภาพที่จะแนะนำข้อบกพร่องที่ละเอียดอ่อนจำนวนมาก (โดยเฉพาะอย่างยิ่งในซอฟต์แวร์ทางการเงิน) การเขียนโปรแกรมภาษาอะไรสามารถทำได้เพื่อหลีกเลี่ยงข้อผิดพลาดสำหรับผู้ที่ไม่คุ้นเคยกับแนวความคิดในขณะที่ยังคงนำเสนอความเร็วเมื่อความถูกต้องไม่สำคัญสำหรับผู้ที่ทำเข้าใจแนวคิด?


26
สิ่งเดียวที่ภาษาการเขียนโปรแกรมสามารถทำได้เพื่อหลีกเลี่ยงข้อผิดพลาดของการประมวลผลจุดลอยตัวคือการห้าม โปรดทราบว่าสิ่งนี้รวมถึงจุดลอยตัวพื้นฐาน 10 เช่นกันซึ่งโดยทั่วไปไม่ถูกต้องยกเว้นว่าแอปพลิเคชันทางการเงินจะได้รับการปรับให้เหมาะสมก่อน
David Thornley

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

ตัวอย่างที่ดีของปัญหาจุดลอยตัว: stackoverflow.com/questions/10303762/0-0-0-0-0
Austin Henley

คำตอบ:


47

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

แน่นอนว่ามันดูเหมือนลอย มันมีจุดทศนิยมในนั้น แต่นั่นเป็นเพียงเพราะคุณคุ้นเคยกับหน่วยงานที่ทำให้เกิดความสับสน เงินมักมาในปริมาณที่เป็นจำนวนเต็มเสมอ ในอเมริกามันเป็นเซนต์ (ในบริบทบางอย่างฉันคิดว่ามันอาจเป็นโรงสีแต่ไม่สนใจสิ่งนั้นในตอนนี้)

ดังนั้นเมื่อคุณพูด $ 1.23 นั่นเป็น 123 เซนต์จริง ๆ เสมอเสมอทำคณิตศาสตร์ของคุณในแง่เหล่านั้นและคุณจะไม่เป็นไร สำหรับข้อมูลเพิ่มเติมดู:

ตอบคำถามโดยตรงภาษาการเขียนโปรแกรมควรรวมประเภทเงินเป็นแบบดั้งเดิมที่เหมาะสม

ปรับปรุง

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


20
@JoelFan: คุณเข้าใจผิดว่าเป็นแนวคิดสำหรับการใช้งานเฉพาะแพลตฟอร์ม
whatsisname

12
มันไม่ง่ายอย่างนั้น การคำนวณดอกเบี้ยจะสร้างเซนต์เศษส่วนและต้องปัดเศษในบางจุดตามวิธีที่ระบุ
kevin cline

24
Fictional -1 เนื่องจากฉันไม่มีตัวแทนสำหรับ downvote :) ... นี่อาจเป็นสิ่งที่ถูกต้องสำหรับสิ่งที่อยู่ในกระเป๋าเงินของคุณ แต่มีสถานการณ์การบัญชีมากมายที่คุณสามารถรับมือกับเศษสิบเปอร์เซ็นต์หรือเศษเล็กเศษน้อย Decimalเป็นระบบที่มีสติเพียงอย่างเดียวสำหรับจัดการกับสิ่งนี้และความคิดเห็นของคุณ"ไม่สนใจในตอนนี้"เป็นลางสังหรณ์แห่งการลงโทษสำหรับโปรแกรมเมอร์ทุกที่: P
detly

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

6
ทุกอย่างจะสมบูรณ์แบบโดยการแทนที่คำว่า "จำนวนเต็ม" ด้วย "rational" -
Emilio Garavaglia

15

การสนับสนุนประเภททศนิยมช่วยได้ในหลายกรณี หลายภาษามีประเภททศนิยม แต่มีการใช้น้อยเกินไป

การทำความเข้าใจกับการประมาณที่เกิดขึ้นเมื่อทำงานกับการแสดงตัวเลขจริงเป็นสิ่งสำคัญ การใช้ทั้งทศนิยมและทศนิยมประเภท9 * (1/9) != 1คือคำสั่งที่ถูกต้อง เมื่อค่าคงที่เครื่องมือเพิ่มประสิทธิภาพอาจปรับการคำนวณให้เหมาะสมเพื่อให้ถูกต้อง

การให้บริการตัวดำเนินการโดยประมาณจะช่วยได้ อย่างไรก็ตามการเปรียบเทียบดังกล่าวเป็นปัญหา โปรดทราบว่า. 9999 ล้านล้านดอลลาร์มีค่าประมาณ 1 ล้านล้านดอลลาร์ คุณกรุณาฝากส่วนต่างในบัญชีธนาคารของฉันได้ไหม


2
0.9999...ล้านล้านดอลลาร์มีความแม่นยำเท่ากับ 1 ล้านล้านดอลลาร์
เพียงความคิดเห็นที่ถูกต้องของฉัน

5
@JUST: ใช่ แต่ฉันไม่ได้พบเครื่องคอมพิวเตอร์ใด ๆ 0.99999...ที่มีการลงทะเบียนที่จะถือ พวกเขาทั้งหมดถูกตัดทอนในบางจุดทำให้เกิดความไม่เท่าเทียมกัน 0.9999ก็เพียงพอแล้วสำหรับงานวิศวกรรม เพื่อจุดประสงค์ทางการเงินไม่ใช่
BillThor

2
แต่ระบบชนิดใดที่ใช้เงินล้านล้านดอลล่าร์เป็นหน่วยฐานแทนที่จะเป็นดอลลาร์
แบรด

@Brad ลองคำนวณ (1 ล้านล้าน / 3) * 3 ในเครื่องคิดเลขของคุณ คุณได้ค่าเท่าไหร่
BillThor

8

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

ฉันจำได้ว่าอาจารย์พูดว่า "ตัวเลขจุดลอยตัวเป็นค่าประมาณใช้ประเภทจำนวนเต็มเพื่อเงินใช้ FORTRAN หรือภาษาอื่นด้วยหมายเลข BCD เพื่อการคำนวณที่แม่นยำ" (แล้วเขาก็ชี้ให้เห็นถึงการประมาณโดยใช้ตัวอย่างคลาสสิกของ 0.2 เป็นไปไม่ได้ที่จะแสดงอย่างถูกต้องในจุดลอยตัวไบนารี) สิ่งนี้ก็ปรากฏขึ้นในสัปดาห์นั้นในแบบฝึกหัดในห้องปฏิบัติการ

การบรรยายเดียวกัน: "หากคุณต้องได้รับความแม่นยำมากขึ้นจากจุดลอยตัวให้เรียงลำดับคำศัพท์ของคุณเพิ่มตัวเลขขนาดเล็กเข้าด้วยกันไม่ใช่ตัวเลขขนาดใหญ่" ที่ติดอยู่ในใจของฉัน

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

ก่อนที่คุณจะบ่นเกี่ยวกับคุณภาพของกีตาร์เรียนรู้การเล่น

ฉันมีเพื่อนร่วมงานสี่ปีที่แล้วที่ทำงานให้กับ JPL เขาแสดงความไม่เชื่อว่าเราใช้ FORTRAN สำหรับบางสิ่ง (เราต้องการแบบจำลองเชิงตัวเลขที่แม่นยำที่สุดคำนวณจากออฟไลน์) "เราแทนที่ FORTRAN ทั้งหมดด้วย C ++" เขากล่าวอย่างภาคภูมิใจ ฉันหยุดสงสัยว่าทำไมพวกเขาพลาดดาวเคราะห์


2
+1 เครื่องมือที่เหมาะสมสำหรับงานที่เหมาะสม ถึงแม้ว่าฉันจะไม่ใช้ FORTRAN โชคดีที่ฉันไม่ได้ทำงานกับระบบการเงินในที่ทำงาน
James Khoury

"หากคุณต้องได้รับความแม่นยำมากขึ้นจากจุดลอยตัวให้เรียงลำดับคำศัพท์ของคุณเพิ่มตัวเลขขนาดเล็กเข้าด้วยกันไม่ใช่ตัวเลขขนาดใหญ่" ตัวอย่างใด ๆ เกี่ยวกับเรื่องนี้?
mamcx

@amcx ลองจินตนาการถึงจำนวนทศนิยมที่มีทศนิยมเพียงหนึ่งหลักของการตกตะกอน การคำนวณ1.0 + 0.1 + ... + 0.1(ซ้ำ 10 ครั้ง) จะส่งคืน1.0เนื่องจากผลลัพธ์ระหว่างกลางถูกปัดเศษ ทำมันรอบวิธีอื่น ๆ คุณจะได้รับผลกลางของ0.2, 0.3, ... , และในที่สุด1.0 2.0นี่เป็นตัวอย่างที่รุนแรง แต่ด้วยจำนวนจุดลอยตัวที่แท้จริงปัญหาที่คล้ายกันก็เกิดขึ้น แนวคิดพื้นฐานคือการเพิ่มตัวเลขที่มีขนาดใกล้เคียงกันทำให้เกิดข้อผิดพลาดน้อยที่สุด เริ่มต้นด้วยตัวเลขที่น้อยที่สุดเนื่องจากผลรวมของพวกมันนั้นใหญ่กว่าและเหมาะสำหรับการเพิ่มในจำนวนที่มากกว่า
maaartinus

สิ่งที่ลอยอยู่ใน Fortran และ C ++ นั้นจะเหมือนกันเป็นส่วนใหญ่ ทั้งสองมีความถูกต้องและออฟไลน์และฉันค่อนข้างมั่นใจว่า Fortran ไม่มี BCD ดั้งเดิม ...
Mark

8

คำเตือน: จุดลอยตัวประเภทSystem.Doubleขาดความแม่นยำสำหรับการทดสอบความเท่าเทียมกันโดยตรง

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

ฉันไม่เชื่อว่าทุกอย่างสามารถทำได้หรือควรจะทำในระดับภาษา


1
ฉันไม่ได้ใช้ทุ่นหรือสองเท่าในเวลานานดังนั้นฉันอยากรู้ นั่นคือคำเตือนของคอมไพเลอร์ที่มีอยู่จริงหรือแค่คำเดียวที่คุณอยากเห็น?
Karl Bielefeldt

1
@Karl - โดยส่วนตัวฉันไม่ได้เห็นหรือต้องการมัน แต่ฉันคิดว่ามันจะมีประโยชน์สำหรับนักพัฒนาที่ทุ่มเท แต่เป็นสีเขียว
ChaosPandion

1
ชนิดเลขทศนิยมแบบไบนารีนั้นไม่ได้คุณภาพดีกว่าหรือแย่กว่าDecimalการทดสอบความเสมอภาค ความแตกต่างระหว่าง1.0m/7.0m*7.0mและ1.0mอาจมีลำดับความสำคัญน้อยกว่าความแตกต่างระหว่าง1.0/7.0*7.0แต่ไม่ใช่ศูนย์
supercat

1
@ Patrick - ฉันไม่แน่ใจว่าคุณกำลังทำอะไรอยู่ มีความแตกต่างอย่างมากระหว่างสิ่งที่เป็นจริงสำหรับกรณีหนึ่งและเป็นจริงสำหรับทุกกรณี
ChaosPandion

1
@ChaosPandion ปัญหาของตัวอย่างในโพสต์นี้ไม่ใช่การเปรียบเทียบความเท่าเทียมกันมันเป็นตัวอักษรที่เป็นทศนิยม ไม่มีการลอยด้วยค่าที่แน่นอน 1.0 / 10 คณิตศาสตร์จุดลอยตัวให้ผลลัพธ์ที่แม่นยำ 100% เมื่อคำนวณด้วยตัวเลขจำนวนเต็มที่เหมาะสมภายในแมนทิสซา
แพทริค

7

โดยค่าเริ่มต้นภาษาควรใช้การปันส่วนที่มีความแม่นยำโดยพลการสำหรับตัวเลขที่ไม่ใช่จำนวนเต็ม

ผู้ที่ต้องการเพิ่มประสิทธิภาพสามารถขอลอย ใช้พวกเขาเป็นค่าเริ่มต้นทำให้รู้สึกใน C และภาษาการเขียนโปรแกรมระบบอื่น ๆ แต่ไม่ได้รับความนิยมในภาษาส่วนใหญ่ในวันนี้


1
คุณจะจัดการกับตัวเลขที่ไม่มีเหตุผลได้อย่างไร?
dsimcha

3
คุณทำแบบเดียวกับการลอย: การประมาณ
Waquo

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

1
คำนวณกับ rationals ความแม่นยำ arbitrary มักจะเป็นคำสั่งของขนาดช้า (อาจจะเป็นคำสั่งหลายขนาดช้ากว่า) doubleการคำนวณด้วยฮาร์ดแวร์ที่ได้รับการสนับสนุน หากการคำนวณจำเป็นต้องแม่นยำต่อส่วนต่อล้านมันจะดีกว่าถ้าใช้ไมโครวินาทีคำนวณให้ภายในไม่กี่ส่วนต่อพันล้านกว่าจะใช้การคำนวณที่สองอย่างแม่นยำ
supercat

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

4

ปัญหาที่ใหญ่ที่สุดสองปัญหาที่เกี่ยวข้องกับหมายเลขจุดลอยตัวคือ:

  • หน่วยที่ไม่สอดคล้องกันที่นำไปใช้กับการคำนวณ (โปรดทราบว่าสิ่งนี้มีผลกระทบต่อเลขคณิตเลขจำนวนเต็มในลักษณะเดียวกัน)
  • ความล้มเหลวในการทำความเข้าใจว่าหมายเลข FP นั้นเป็นการประมาณและวิธีการปัดเศษอย่างชาญฉลาด

ประเภทแรกของความล้มเหลวสามารถแก้ไขได้โดยการจัดให้มีประเภทคอมโพสิตที่มีค่าและข้อมูลหน่วย ตัวอย่างเช่น a lengthหรือareaค่าที่รวมหน่วย (เมตรหรือตารางเมตรหรือฟุตและตารางฟุตตามลำดับ) มิฉะนั้นคุณจะต้องขยันหมั่นเพียรในการทำงานกับหน่วยวัดชนิดหนึ่งเสมอและแปลงเป็นหน่วยอื่นเมื่อเราแบ่งปันคำตอบกับมนุษย์

ประเภทที่สองของความล้มเหลวคือความล้มเหลวของแนวคิด ความล้มเหลวปรากฏตัวเมื่อมีคนคิดว่าพวกเขาเป็นตัวเลขที่แน่นอน มันส่งผลกระทบต่อการดำเนินงานที่เท่าเทียมกันข้อผิดพลาดในการปัดเศษสะสม ฯลฯ ตัวอย่างเช่นอาจเป็นเรื่องที่ถูกต้องสำหรับการวัดหนึ่งระบบสองระบบที่เทียบเท่ากันภายในระยะขอบที่ผิดพลาด นั่นคือ. 999 และ 1.001 นั้นเท่ากับ 1.0 เมื่อคุณไม่สนใจความแตกต่างที่เล็กกว่า +/- .1 อย่างไรก็ตามไม่ใช่ว่าทุกระบบจะผ่อนปรน

หากมีสิ่งอำนวยความสะดวกระดับภาษาใด ๆ ที่จำเป็นแล้วฉันจะเรียกมันว่าความเสมอภาคความแม่นยำ ใน NUnit, JUnit และเฟรมเวิร์กการทดสอบที่สร้างขึ้นในทำนองเดียวกันคุณสามารถควบคุมความแม่นยำที่ถือว่าถูกต้อง ตัวอย่างเช่น:

Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));

ตัวอย่างเช่นหาก C # หรือ Java ถูกดัดแปลงให้มีตัวดำเนินการที่มีความแม่นยำอาจมีลักษณะดังนี้:

if(.999 == 1.001 within .1) { /* do something */ }

อย่างไรก็ตามหากคุณให้คุณสมบัติเช่นนั้นคุณต้องพิจารณากรณีที่ความเท่าเทียมกันนั้นดีถ้าด้าน +/- ไม่เหมือนกัน ตัวอย่างเช่น + 1 / -10 จะพิจารณาตัวเลขสองตัวที่เทียบเท่ากันหากหนึ่งในนั้นอยู่ในระยะที่มากกว่า 1 หรือ 10 น้อยกว่าหมายเลขแรก ในการจัดการกรณีนี้คุณอาจต้องเพิ่มrangeคำหลักด้วย:

if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }

2
ฉันจะเปลี่ยนคำสั่ง ปัญหาทางความคิดเป็นที่แพร่หลาย ปัญหาการแปลงหน่วยค่อนข้างน้อยโดยการเปรียบเทียบ
S.Lott

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

มันสามารถทำได้ง่ายมากในห้องสมุด
Michael K

1
@ dan04: ฉันคิดในแง่ของ "การคำนวณทั้งหมดที่แม่นยำภายในหนึ่งเปอร์เซ็นต์" หรือที่คล้ายกัน ฉันได้เห็น tar-pit ซึ่งเป็นหน่วยวัดการใช้งานและฉันก็อยู่ห่างออกไป
TMN

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

3

ภาษาการเขียนโปรแกรมสามารถทำอะไรได้บ้าง ไม่ทราบว่ามีคำตอบสำหรับคำถามนั้นหรือไม่เพราะสิ่งใดที่คอมไพเลอร์ / ล่ามทำในนามของโปรแกรมเมอร์เพื่อให้ชีวิตของเขา / เธอง่ายขึ้นมักจะทำงานกับประสิทธิภาพความชัดเจนและการอ่านได้ ฉันคิดว่าทั้งวิธี C ++ (จ่ายเฉพาะสิ่งที่คุณต้องการ) และวิธี Perl (หลักการของความประหลาดใจน้อยที่สุด) นั้นใช้ได้ทั้งคู่ แต่ขึ้นอยู่กับแอปพลิเคชัน

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

สิ่งที่โปรแกรมเมอร์ต้องการรู้:

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

3

ภาษาการเขียนโปรแกรมสามารถทำอะไรได้บ้างเพื่อหลีกเลี่ยงข้อผิดพลาด [floating point] ... ?

ใช้ค่าเริ่มต้นที่สมเหตุสมผลเช่นการรองรับการถอดรหัสในตัว

Groovy ทำสิ่งนี้ได้เป็นอย่างดีถึงแม้จะมีความพยายามอยู่บ้างคุณก็ยังสามารถเขียนโค้ดเพื่อแนะนำจุดที่ไม่แน่นอนได้


3

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

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


ไม่เห็นด้วยภาษาส่วนใหญ่ในปัจจุบันให้การสนับสนุนมากเกินไปสำหรับประเภททศนิยมเลขฐานสอง (ทำไมถึงกำหนด == แม้กระทั่งสำหรับลอย?) และการสนับสนุนไม่เพียงพอสำหรับปันส่วนหรือทศนิยม
jk

@jk: แม้ว่าผลลัพธ์ของการคำนวณใด ๆ จะไม่ได้รับการรับรองเท่ากับผลของการคำนวณอื่น ๆ การเปรียบเทียบความเท่าเทียมกันจะยังคงมีประโยชน์สำหรับกรณีที่ค่าเดียวกันได้รับมอบหมายให้กับตัวแปรสองตัว หลวมเกินไปเนื่องจากx== yไม่ได้หมายความว่าการทำการคำนวณxจะทำให้ได้ผลลัพธ์เช่นเดียวกับการคำนวณแบบเดียวกันบนy)
supercat

@ supercat คุณยังคงต้องการการเปรียบเทียบ แต่ฉันต้องการภาษาที่ฉันต้องระบุความอดทนสำหรับการเปรียบเทียบแต่ละจุดลอยตัวจากนั้นฉันยังสามารถกลับไปสู่ความเท่าเทียมกันได้โดยการเลือกความอดทน = 0 แต่อย่างน้อยฉันก็ถูกบังคับให้ทำเช่นนั้น ตัวเลือก
jk

3

สิ่งหนึ่งที่ภาษาสามารถทำได้ - ลบการเปรียบเทียบความเท่าเทียมกันจากประเภทจุดลอยอื่น ๆ นอกเหนือจากการเปรียบเทียบโดยตรงกับค่า NAN

การทดสอบความเท่าเทียมกันจะมีอยู่ก็ต่อเมื่อการเรียกใช้ฟังก์ชันที่ใช้ค่าสองค่าและเดลตาหรือสำหรับภาษาเช่น C # ที่อนุญาตให้ประเภทมีวิธีการ EqualsTo ที่ใช้ค่าอื่นและเดลต้า


3

ฉันคิดว่ามันแปลกที่ไม่มีใครชี้เคล็ดลับจำนวนตรรกยะของตระกูล Lisp

เปิด sbcl อย่างจริงจังและทำสิ่งนี้: (+ 1 3)แล้วคุณจะได้ 4. ถ้า*( 3 2)คุณได้ 6 คุณได้ลอง(/ 5 3)แล้วคุณจะได้ 5/3 หรือ 5 ใน 5

สิ่งนี้น่าจะช่วยได้บ้างในบางสถานการณ์ใช่ไหม


ฉันสงสัยว่าถ้าเป็นไปได้ที่จะรู้ว่าผลลัพธ์จะต้องแสดงเป็น 1/3 หรืออาจเป็นทศนิยมที่แน่นอน?
mamcx

ข้อเสนอแนะที่ดี
Peter Porfy

3

สิ่งหนึ่งที่ผมอยากจะเห็นจะได้รับการยอมรับว่าdoubleจะfloatควรจะได้รับการยกย่องว่าเป็นแปลงขยับขยายในขณะfloatที่จะdoubleมีการกวดขัน (*) ที่อาจดูเหมือนเคาน์เตอร์ง่าย แต่พิจารณาประเภทที่จริงหมายถึง:

  • 0.1f หมายถึง "13,421,773.5 / 134,217,728 บวกหรือลบ 1 / 268,435,456 หรือมากกว่านั้น"
  • 0.1 หมายถึงจริงๆ 3,602,879,701,896,397 / 36,028,797,018,963,968 บวกหรือลบ 1 / 72,057,594,037,927,936 หรือประมาณ "

หากหนึ่งมีdoubleที่ถือเป็นตัวแทนที่ดีที่สุดของปริมาณ "หนึ่งในสิบ" และแปลงfloatเป็นผลลัพธ์จะเป็น "13,421,773.5 / 134,217,728 บวกหรือลบ 1 / 268,435,456 หรือซึ่งเป็นคำอธิบายที่ถูกต้องของค่า

ในทางตรงกันข้ามหากมีสิ่งfloatใดที่มีตัวแทนที่ดีที่สุดของปริมาณ "หนึ่งในสิบ" และแปลงdoubleเป็นผลลัพธ์จะเป็น "13,421,773.5 / 134,217,728 บวกหรือลบ 1 / 72,057,594,037,927,936 หรือนัยอื่น ๆ " - ระดับความแม่นยำโดยนัย ซึ่งผิดโดยมีปัจจัยมากกว่า 53 ล้าน

แม้ว่ามาตรฐาน IEEE-744 กำหนดให้ต้องดำเนินการทางคณิตศาสตร์แบบเลขทศนิยมเนื่องจากเลขทศนิยมทุกตัวจะแสดงปริมาณตัวเลขที่แน่นอนที่กึ่งกลางของช่วงนั้นอย่างแม่นยำ แต่ไม่ควรนำมาใช้เพื่อบ่งบอกว่าค่าจุดลอยตัวเป็นตัวแทนของความถูกต้องแน่นอน ปริมาณเชิงตัวเลข แต่ความต้องการที่ค่าจะถือว่าเป็นศูนย์กลางของช่วงของพวกเขาเกิดขึ้นจากข้อเท็จจริงสามประการ: (1) การคำนวณจะต้องดำเนินการราวกับว่าตัวถูกดำเนินการมีค่าที่แม่นยำเฉพาะบางอย่าง; (2) สมมติฐานที่สอดคล้องและมีเอกสารเป็นประโยชน์มากกว่าสมมติฐานที่ไม่สอดคล้องกันหรือไม่มีเอกสาร (3) ถ้ามีใครจะทำให้สมมติฐานที่สอดคล้องกันไม่มีสมมติฐานที่สอดคล้องกันอื่น ๆ จะดีกว่าสมมติว่าปริมาณหมายถึงจุดศูนย์กลางของช่วง

ฉันจำได้เมื่อ 25 ปีที่แล้วบางคนคิดแพ็กเกจตัวเลขสำหรับ C ซึ่งใช้ "range range" ซึ่งแต่ละอันประกอบด้วย 128-bit float; การคำนวณทั้งหมดจะทำในลักษณะที่จะคำนวณค่าต่ำสุดและสูงสุดที่เป็นไปได้สำหรับแต่ละผลลัพธ์ หากทำการคำนวณซ้ำขนาดใหญ่เป็นเวลานานและมีค่าเป็น [12.53401391134 12.53902812673] ก็สามารถมั่นใจได้ว่าในขณะที่ความแม่นยำจำนวนมากหายไปจากข้อผิดพลาดในการปัดเศษผลลัพธ์จะยังคงแสดงได้อย่างสมเหตุสมผลเท่ากับ 12.54 (และเป็น ' 12.9 หรือ 53.2) ฉันประหลาดใจที่ฉันไม่ได้เห็นการสนับสนุนประเภทดังกล่าวในภาษากระแสหลักใด ๆ โดยเฉพาะอย่างยิ่งเนื่องจากพวกเขาดูเหมือนจะเหมาะสมกับหน่วยคณิตศาสตร์ที่สามารถทำงานกับค่าหลายค่าในแบบคู่ขนาน

(*) ในทางปฏิบัติมักจะมีประโยชน์ในการใช้ค่าความแม่นยำสองเท่าเพื่อเก็บการคำนวณระดับกลางเมื่อทำงานกับตัวเลขที่มีความแม่นยำเดียวดังนั้นการใช้ typecast สำหรับการดำเนินการทั้งหมดอาจน่ารำคาญ ภาษาสามารถช่วยได้ด้วยการพิมพ์ "fuzzy double" ซึ่งจะทำการคำนวณเป็นสองเท่าและสามารถส่งไปและกลับจากเดี่ยวได้อย่างอิสระ สิ่งนี้จะเป็นประโยชน์อย่างยิ่งหากฟังก์ชันที่รับพารามิเตอร์ประเภทdoubleและการส่งคืนdoubleสามารถทำเครื่องหมายเพื่อให้พวกเขาสร้างโอเวอร์โหลดที่ยอมรับและส่งคืน "fuzzy double" โดยอัตโนมัติแทน


2

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

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


การระบุความยาวและความแม่นยำจะทำได้น้อยมากซึ่งมีประโยชน์ การมีฐานจุดคงที่ 10 จะเป็นประโยชน์สำหรับการประมวลผลทางการเงินซึ่งจะลบความประหลาดใจที่ผู้คนส่วนใหญ่ได้รับจากจุดลอยตัว
David Thornley

@ David - บางทีฉันหายไปบางอย่าง แต่ฐานข้อมูล 10 จุดคงที่แตกต่างจากที่ฉันเสนอที่นี่อย่างไร ตัวอย่าง Float (2) ของฉันจะมีทศนิยม 2 หลักคงที่และจะปัดเศษเป็นร้อยที่ใกล้ที่สุดโดยอัตโนมัติซึ่งเป็นสิ่งที่คุณควรใช้สำหรับการคำนวณทางการเงินอย่างง่าย การคำนวณที่ซับซ้อนมากขึ้นนั้นต้องการให้ผู้พัฒนาจัดสรรตัวเลขทศนิยมให้ใหญ่ขึ้น
Justin Cave

1
สิ่งที่คุณกำลังสนับสนุนคือประเภทข้อมูลพื้นฐาน 10 จุดที่มีความแม่นยำที่ระบุโดยโปรแกรมเมอร์ ฉันกำลังบอกว่าความแม่นยำที่โปรแกรมเมอร์กำหนดไว้นั้นไร้จุดหมายเป็นส่วนใหญ่และจะนำไปสู่ข้อผิดพลาดหลายประเภทที่ฉันเคยพบในโปรแกรมภาษาโคบอล (ตัวอย่างเช่นเมื่อคุณเปลี่ยนความแม่นยำของตัวแปรมันเป็นเรื่องง่ายที่จะพลาดตัวแปรตัวหนึ่งที่มีค่าไหลผ่านสำหรับอีกตัวหนึ่งมันจะต้องใช้ความคิดมากขึ้นเกี่ยวกับขนาดผลกลางที่ดีกว่าดีมาก)
David Thornley

4
Float(2)เหมือนที่คุณเสนอไม่ควรจะเรียกว่าFloatตั้งแต่มีอะไรลอยที่นี่ไม่แน่นอน "จุดทศนิยม"
Paŭlo Ebermann

1
  • ภาษามีการสนับสนุนประเภททศนิยม แน่นอนว่าสิ่งนี้ไม่ได้แก้ปัญหาจริงๆ แต่คุณก็ยังไม่มีตัวแทนที่แน่นอนและแน่นอนเช่น⅓;
  • ฐานข้อมูลและกรอบงานบางประเภทได้รับการสนับสนุนประเภทเงินซึ่งโดยทั่วไปจะเก็บจำนวนเซ็นต์เป็นจำนวนเต็ม
  • มีห้องสมุดบางแห่งสำหรับการสนับสนุนจำนวนตรรกยะ ที่แก้ปัญหาของ⅓ แต่ไม่ได้แก้ปัญหาตัวอย่างเช่น√2;

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


1

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

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

Finance.importEstimate(float value, Finance roundingStep)

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

บางสิ่งที่อาจช่วยได้:

  • ฉันจะสองคนที่ถามว่า "ทำไมการทดสอบความเท่าเทียมกันที่แน่นอนสำหรับจุดลอยตัวนั้นถูกกฎหมาย?"
  • ให้ใช้isNear()ฟังก์ชั่นแทน
  • จัดเตรียมและสนับสนุนการใช้วัตถุตัวสะสมแบบลอยตัว (ซึ่งเพิ่มลำดับของค่าจุดลอยตัวที่เสถียรมากกว่าเพียงแค่เพิ่มพวกมันทั้งหมดลงในตัวแปรจุดลอยตัวปกติ)

-1

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


1
C ไม่มีประเภททศนิยมเนื่องจากไม่ใช่แบบดั้งเดิมคอมพิวเตอร์จำนวนน้อยมากที่มีคำสั่งทศนิยมฮาร์ดแวร์ประเภทใดประเภทหนึ่ง คุณอาจถามว่าทำไม BASIC และ Pascal ถึงไม่มีเพราะมันไม่ได้ออกแบบมาให้สอดคล้องกับโลหะ COBOL และ PL / ฉันเป็นภาษาเดียวที่ฉันรู้ในเวลาที่มีอะไรอย่างนั้น
David Thornley

3
@JoelFan: งั้นคุณเขียน COB ในภาษาโคบอลได้อย่างไร? ทศนิยมไม่ได้แก้ปัญหาใด ๆ 10 ฐานเป็นเพียงที่ไม่ถูกต้องเป็นฐาน 2
vartec

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

1
และ FORTRAN ซึ่งมีมาก่อน C มากกว่าทศวรรษที่ผ่านมาไม่ได้รับการสนับสนุนทศนิยมมาตรฐานเช่นกัน
dan04

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