การเปรียบเทียบสัมพัทธ์ของตัวเลขจำนวนจุดลอยตัว


10

ฉันมีฟังก์ชั่นเชิงตัวเลขf(x, y)คืนค่าจำนวนจุดลอยตัวสองเท่าที่ใช้สูตรบางอย่างและฉันต้องการตรวจสอบว่ามันถูกต้องกับนิพจน์การวิเคราะห์สำหรับการรวมกันของพารามิเตอร์ทั้งหมดxและyฉันสนใจวิธีที่เหมาะสมในการเปรียบเทียบการคำนวณและ วิเคราะห์จำนวนจุดลอยตัว?

สมมติว่าทั้งสองตัวเลขและa bจนถึงตอนนี้ฉันแน่ใจแล้วว่าข้อผิดพลาดทั้งสองอย่าง ( abs(a-b) < eps) และญาติ ( abs(a-b)/max(abs(a), abs(b)) < eps) นั้นน้อยกว่า eps ด้วยวิธีนี้มันจะตรวจจับความไม่ถูกต้องของตัวเลขแม้ว่าตัวเลขจะบอกว่าประมาณ 1e-20

อย่างไรก็ตามวันนี้ฉันค้นพบปัญหาค่าตัวเลขaและค่าการวิเคราะห์bคือ:

In [47]: a                                                                     
Out[47]: 5.9781943146790832e-322

In [48]: b                                                                     
Out[48]: 6.0276008792632078e-322

In [50]: abs(a-b)                                                              
Out[50]: 4.9406564584124654e-324

In [52]: abs(a-b) / max(a, b)                                                  
Out[52]: 0.0081967213114754103

ดังนั้นข้อผิดพลาดแบบสัมบูรณ์ [50] จึงเล็ก (ชัด) แต่ข้อผิดพลาดสัมพัทธ์ [52] นั้นใหญ่ ดังนั้นฉันคิดว่าฉันมีข้อบกพร่องในโปรแกรมของฉัน โดยการแก้จุดบกพร่องฉันตระหนักว่าตัวเลขเหล่านี้จะdenormal ดังนั้นฉันจึงเขียนขั้นตอนต่อไปนี้เพื่อทำการเปรียบเทียบที่เหมาะสม:

real(dp) elemental function rel_error(a, b) result(r)
real(dp), intent(in) :: a, b
real(dp) :: m, d
d = abs(a-b)
m = max(abs(a), abs(b))
if (d < tiny(1._dp)) then
    r = 0
else
    r = d / m
end if
end function

โดยที่tiny(1._dp)ผลตอบแทน 2.22507385850720138E-308 บนคอมพิวเตอร์ของฉัน ตอนนี้ทุกอย่างทำงานได้และฉันได้ 0 เป็นข้อผิดพลาดสัมพัทธ์และทั้งหมดก็โอเค โดยเฉพาะอย่างยิ่งข้อผิดพลาดสัมพัทธ์ข้างต้น [52] นั้นผิดมันเกิดจากความถูกต้องของตัวเลขที่ไม่เพียงพอ การใช้งานrel_errorฟังก์ชั่นของฉันถูกต้องหรือไม่? ฉันควรตรวจสอบว่าabs(a-b)น้อยกว่าจิ๋ว (= denormal) และส่งคืน 0 หรือไม่ หรือฉันควรตรวจสอบชุดค่าผสมอื่น ๆ เช่น max(abs(a), abs(b))?

ฉันแค่อยากจะรู้ว่าสิ่งที่ "เหมาะสม" คืออะไร

คำตอบ:


11

คุณสามารถตรวจสอบ denormals ที่ใช้isnormal()โดยตรงจากmath.h(C99 หรือใหม่กว่า, POSIX.1 หรือใหม่กว่า) ใน Fortran, ถ้าโมดูลสามารถใช้ได้คุณสามารถใช้ieee_arithmetic ieee_is_normal()เพื่อให้แม่นยำมากขึ้นเกี่ยวกับความเท่าเทียมกันแบบฟัซซี่คุณต้องพิจารณาการนำเสนอจุดลอยตัวของ denormals และตัดสินใจว่าคุณหมายถึงอะไรเพื่อผลลัพธ์ที่ดีพอ

ยิ่งไปกว่านั้นเพื่อเชื่อว่าผลลัพธ์ใดถูกต้องคุณต้องแน่ใจว่าคุณไม่ได้สูญเสียตัวเลขมากเกินไปในขั้นตอนกลาง การคำนวณด้วย denormals โดยทั่วไปไม่น่าเชื่อถือและควรหลีกเลี่ยงโดยให้อัลกอริธึมของคุณจำหน่ายซ้ำภายใน เพื่อให้แน่ใจว่าการปรับขนาดภายในของคุณประสบความสำเร็จฉันขอแนะนำให้เปิดใช้งานข้อยกเว้นจุดลอยตัวโดยใช้feenableexcept()กับ C99 หรือieee_arithmeticโมดูลใน Fortran

แม้ว่าคุณจะสามารถให้แอปพลิเคชันของคุณจับสัญญาณที่เพิ่มขึ้นในข้อยกเว้นจุดลอยได้เมล็ดทั้งหมดที่ฉันลองรีเซ็ตการตั้งค่าสถานะฮาร์ดแวร์จึงfetestexcept()ไม่ส่งคืนผลลัพธ์ที่เป็นประโยชน์ เมื่อทำงานด้วย-fp_trapโปรแกรม PETSc จะเริ่มพิมพ์การติดตามสแต็กเมื่อเกิดข้อผิดพลาดจุดลอยตัว แต่จะไม่ระบุบรรทัดที่ละเมิด ถ้าคุณเรียกใช้ในการดีบักเกอร์การดีบักจะเก็บค่าสถานะฮาร์ดแวร์และแบ่งการแสดงออกที่ละเมิด คุณสามารถตรวจสอบเหตุผลได้อย่างแม่นยำโดยการเรียกfetestexceptจากดีบักเกอร์ซึ่งผลลัพธ์เป็นบิตหรือค่าสถานะต่อไปนี้ (ค่าอาจแตกต่างกันไปตามเครื่องดูfenv.hค่าเหล่านี้สำหรับ x86-64 พร้อม glibc)

  • FE_INVALID = 0x1
  • FE_DIVBYZERO = 0x4
  • FE_OVERFLOW = 0x8
  • FE_UNDERFLOW = 0x10
  • FE_INEXACT = 0x20

ขอบคุณสำหรับคำตอบที่ยอดเยี่ยม นิพจน์การวิเคราะห์ที่ฉันเปรียบเทียบกับในระบอบ asymptotic exp(log_gamma(m+0.5_dp) - (m+0.5_dp)*log(t)) / 2สำหรับ m = 234, t = 2000 mมันจะไปให้เป็นศูนย์ได้อย่างรวดเร็วที่สุดเท่าที่ฉันเพิ่มขึ้น ทั้งหมดที่ฉันต้องการเพื่อให้แน่ใจว่าตัวเลขประจำของฉันส่งกลับตัวเลข "ถูกต้อง" (เพื่อกลับศูนย์ก็ดีเหมือนกัน) เป็นอย่างน้อย 12 หลักที่สำคัญ ดังนั้นหากการคำนวณส่งกลับตัวเลข denormal แล้วมันก็เป็นศูนย์และไม่น่ามีปัญหา ดังนั้นเพียงแค่การเปรียบเทียบประจำจะต้องแข็งแกร่งต่อนี้
OndřejČertík

5

Donald Knuth มีข้อเสนอสำหรับอัลกอริทึมการเปรียบเทียบจุดลอยตัวในเล่ม 2 "อัลกอริธึม Seminumerical" ของ "The Art of Computer Programming" มันถูกนำมาใช้ใน C โดย Th Belding (ดูแพคเกจ fcmp ) และสามารถใช้ได้ในGSL


2
นี่คือการใช้ Fortran ของฉัน: gist.github.com/3776847สังเกตว่าฉันต้องจัดการกับตัวเลขที่ผิดปกติอย่างชัดเจนอยู่แล้ว มิฉะนั้นฉันคิดว่ามันค่อนข้างเทียบเท่ากับข้อผิดพลาดสัมพัทธ์ความแตกต่างเพียงอย่างเดียวก็คือแทนที่จะทำabs(a-b)/max(a, b) < epsเราทำabs(a-b)/2**exponent(max(a, b)) < epsซึ่งมันก็แค่หยด mantissa ลงไปmax(a, b)ดังนั้นฉันคิดว่าความแตกต่างนั้นเล็กน้อยมาก
OndřejČertík

5

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

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

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

yx|yx|absacc+relaccmax(|x|,|y|)

จากนั้นผู้ใช้รหัสของคุณจะรู้ว่าพวกเขามีความแม่นยำเท่าใด


คุณแน่ใจหรือว่าไม่มีความหมายในการคำนวณข้อผิดพลาดสัมพัทธ์ใกล้กับศูนย์? ฉันคิดว่ามันไร้ความหมายก็ต่อเมื่อมีการสูญเสียความแม่นยำ (ไม่ว่าด้วยเหตุผลใดก็ตาม) หากตัวอย่างมีการสูญเสียความถูกต้องสำหรับ x <1e-150 เนื่องจากปัญหาเชิงตัวเลขบางอย่าง (เช่นการลบสองตัวเลขขนาดใหญ่) แสดงว่าคุณพูดถูก ในกรณีของฉัน แต่ตัวเลขดูเหมือนจะถูกต้องตลอดจนลดลงถึงศูนย์ยกเว้นเมื่อมันกระทบกับตัวเลขที่ผิดปกติ ดังนั้นในกรณีของฉัน absacc = 1e-320 หรือมากกว่านั้นและฉันสามารถตรวจสอบได้abs(a-b) < tiny(1._dp)ตามที่ฉันทำข้างต้น
OndřejČertík

@ OndřejČertík: ในกรณีนั้นให้แทนที่ 1e-150 โดย 1e-300 หรืออะไรก็ตามที่คุณสามารถตรวจสอบได้ ในกรณีใด ๆ ที่ใกล้เคียงกับศูนย์มากที่สุดคุณจะได้รับข้อผิดพลาดอย่างสมบูรณ์และการอ้างสิทธิ์ข้อผิดพลาดของคุณควรสะท้อนถึงสิ่งนี้มากกว่าที่จะประกาศว่าข้อผิดพลาดสัมพัทธ์เป็นศูนย์
Arnold Neumaier

ฉันเห็น. ฉันสามารถตรวจสอบได้ว่าทั้งหมดทำงานสำหรับตัวเลขที่สูงกว่าtiny(1._dp)=2.22507385850720138E-308(ฉันทำผิดพลาดในความคิดเห็นก่อนหน้าของฉันมันเป็น 2e-308 ไม่ใช่ 1e-320) นี่คือข้อผิดพลาดที่แน่นอนของฉัน จากนั้นฉันต้องเปรียบเทียบข้อผิดพลาดสัมพัทธ์ ฉันเห็นประเด็นของคุณฉันคิดว่าคุณพูดถูก ขอบคุณ!
OndřejČertík

1
@ OndřejČertíkเมื่อต้องการค้นหาความผิดพลาดเพิ่มเติมให้ absacc ตรวจสอบสูงสุดของ|)} |yx|absaccmax(|x|,|y|)
อาร์โนลด์ Neumaier
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.