การวิเคราะห์ข้อผิดพลาดเชิงตัวเลขในฟังก์ชัน C ++


20

สมมติว่าฉันมีฟังก์ชั่นที่ใช้เป็นค่าหลายค่าจุดลอยตัว (เดี่ยวหรือสองครั้ง) ทำการคำนวณบางอย่างและสร้างค่าจุดลอยตัวเอาท์พุท (เช่นเดียวหรือสองครั้ง) ฉันทำงานกับ MSVC 2008 เป็นหลัก แต่ยังวางแผนที่จะทำงานกับ MinGW / GCC ฉันกำลังเขียนโปรแกรมใน C ++

อะไรคือวิธีการทั่วไปในการวัดโดยทางโปรแกรมว่ามีข้อผิดพลาดเท่าใดในผลลัพธ์ สมมติว่าฉันต้องใช้ไลบรารี่ที่มีความแม่นยำตามอำเภอใจ: อะไรคือไลบราที่ดีที่สุดถ้าฉันไม่สนใจความเร็ว?

คำตอบ:


17

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

ฉันไม่สามารถหาข้อมูลอ้างอิงออนไลน์ได้ดี แต่ทั้งหมดอธิบายไว้ในมาตรา 3.3 ของหนังสือของ Nick Higham "ความแม่นยำและความเสถียรของอัลกอริธึมเชิงตัวเลข" ความคิดนั้นค่อนข้างง่าย:

  1. ใส่รหัสของคุณอีกครั้งเพื่อให้คุณมีการมอบหมายการดำเนินการทางคณิตศาสตร์เพียงครั้งเดียวในแต่ละบรรทัด
  2. สำหรับตัวแปรแต่ละตัวเช่นxสร้างตัวแปรx_errที่กำหนดค่าเริ่มต้นเป็นศูนย์เมื่อxกำหนดค่าคงที่
  3. สำหรับการดำเนินงานในแต่ละเช่นz = x * yอัปเดตตัวแปรz_errโดยใช้รูปแบบมาตรฐานของการลอยเลขคณิตจุดและส่งผลให้เกิดzความผิดพลาดและการเรียกใช้และx_erry_err
  4. ค่าที่ส่งคืนของฟังก์ชันของคุณควรมี_errค่าที่เกี่ยวข้องแนบมาด้วย นี่เป็นข้อมูลที่ถูกผูกไว้กับข้อผิดพลาด roundoff โดยรวมของคุณ

ส่วนที่ยุ่งยากคือขั้นตอนที่ 3 สำหรับการดำเนินการทางคณิตศาสตร์ที่ง่ายที่สุดคุณสามารถใช้กฎต่อไปนี้:

  • z = x + y -> z_err = u*abs(z) + x_err + y_err
  • z = x - y -> z_err = u*abs(z) + x_err + y_err
  • z = x * y -> z_err = u*abs(z) + x_err*abs(y) + y_err*abs(x)
  • z = x / y -> z_err = u*abs(z) + (x_err*abs(y) + y_err*abs(x))/y^2
  • z = sqrt(x) -> z_err = u*abs(z) + x_err/(2*abs(z))

ที่u = eps/2เป็น roundoff หน่วย ใช่กฎสำหรับ+และ-เหมือนกัน กฎสำหรับการดำเนินงานอื่น ๆ ที่สามารถสกัดได้อย่างง่ายดายโดยใช้การขยายตัวของซีรีส์เทย์เลอร์ของผลที่ได้นำไปใช้op(x) op(x + x_err)หรือคุณสามารถลองใช้ Google หรือใช้หนังสือของ Nick Higham

ยกตัวอย่างเช่นพิจารณา Matlab / Octave code ต่อไปนี้ซึ่งประเมินพหุนามในค่าสัมประสิทธิ์aณ จุดที่xใช้รูปแบบ Horner:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        s = a(k) + x*s;
    end

สำหรับขั้นตอนแรกเราแยกการดำเนินการทั้งสองในs = a(k) + x*s:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        z = x*s;
        s = a(k) + z;
    end

จากนั้นเราจะแนะนำ_errตัวแปร โปรดทราบว่าอินพุตaและxสันนิษฐานว่าเป็นที่แน่นอน แต่เราสามารถกำหนดให้ผู้ใช้ส่งค่าที่สอดคล้องกันสำหรับa_errและx_err:

function [ s , s_err ] = horner ( a , x )
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = ...;
        s = a(k) + z;
        s_err = ...;
    end

สุดท้ายเราใช้กฎที่อธิบายไว้ข้างต้นเพื่อรับข้อกำหนดข้อผิดพลาด:

function [ s , s_err ] = horner ( a , x )
    u = eps/2;
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = u*abs(z) + s_err*abs(x);
        s = a(k) + z;
        s_err = u*abs(s) + z_err;
    end

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

และอื่น ๆ ! ตอนนี้เรามีรูปแบบของฮอร์เนอร์ซึ่งส่งกลับค่าประมาณการข้อผิดพลาดขึ้นอยู่กับข้อมูล (หมายเหตุ: นี่คือขอบเขตบนของข้อผิดพลาด) พร้อมกับผลลัพธ์

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

±ยูx(1±ยู)


1
+1 สำหรับการวิเคราะห์นี้เพราะมันน่าสนใจ ฉันชอบงานของ Higham สิ่งที่ฉันกังวลคือการกำหนดให้ผู้ใช้เขียนโค้ดพิเศษด้วยมือ (แทนที่จะเป็นกึ่งอัตโนมัติเช่นการคำนวณช่วงเวลา) อาจเกิดข้อผิดพลาดได้ง่ายเนื่องจากจำนวนการดำเนินการเชิงตัวเลขมีจำนวนมาก
Geoff Oxberry

1
@GeoffOxberry: ฉันเห็นด้วยกับปัญหาความซับซ้อนอย่างสมบูรณ์ สำหรับรหัสขนาดใหญ่ฉันขอแนะนำให้เขียน class / datatype ซึ่ง overloads การดำเนินการในสองเท่าเช่นจะต้องใช้งานแต่ละการดำเนินการอย่างถูกต้องเพียงครั้งเดียว ฉันค่อนข้างประหลาดใจที่ดูเหมือนว่าจะไม่เป็นเช่นนี้สำหรับ Matlab / Octave
Pedro

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

8

ไลบรารีแบบพกพาและโอเพ่นซอร์สที่ดีสำหรับการคำนวณเลขทศนิยมที่มีความแม่นยำตามอำเภอใจ (และอื่น ๆ อีกมากมาย) คือNTL ของ Victor Shoupซึ่งมีอยู่ในรูปแบบซอร์ส C ++

ในระดับที่ต่ำกว่าคือห้องสมุด Bignum GNU Multiple Precision (GMP)ซึ่งเป็นแพ็คเกจโอเพ่นซอร์ส

NTL สามารถใช้งานกับ GMP ได้ แต่ต้องการประสิทธิภาพที่เร็วกว่า แต่ NTL จะมีฐานการทำงานเป็นของตัวเองซึ่งสามารถใช้งานได้อย่างแน่นอนหากคุณ "ไม่สนใจความเร็ว" GMP อ้างว่าเป็น "ห้องสมุด bignum ที่เร็วที่สุด" GMP ส่วนใหญ่เขียนใน C แต่มีอินเตอร์เฟส C ++

เพิ่ม:ในขณะที่การคำนวณช่วงเวลาสามารถให้ขอบเขตบนและล่างบนคำตอบที่แน่นอนในวิธีอัตโนมัติ แต่ไม่ได้วัดข้อผิดพลาดอย่างแม่นยำในการคำนวณความแม่นยำ "มาตรฐาน" เพราะขนาดช่วงเวลาโดยทั่วไปจะเติบโตขึ้นกับการดำเนินการแต่ละอย่าง ความรู้สึกผิดพลาดแน่นอน)

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

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

ข้อมูลโค้ด C

    float fa,fb;
    double da,db,err;
    fa = 4.0;
    fb = 3.0;
    fa = fa/fb;
    fa -= 1.0;

    da = 4.0;
    db = 3.0;
    da = da/db;
    da -= 1.0;

    err = fa - da;
    printf("Single precision error wrt double precision value\n");
    printf("Error in getting 1/3rd is %e\n",err);
    return 0;

ผลลัพธ์จากด้านบน (Cygwin / MinGW32 GCC tool chain):

Single precision error wrt double precision value
Error in getting 1/3rd is 3.973643e-08

ดังนั้นข้อผิดพลาดเกี่ยวกับสิ่งที่เราคาดหวังในการปัดเศษ 1 ใน 3 ไปยังความแม่นยำเดี่ยว หนึ่งจะไม่ (ฉันสงสัยว่า) สนใจเกี่ยวกับการรับมากกว่าสองตำแหน่งทศนิยมในข้อผิดพลาดที่ถูกต้องเนื่องจากการวัดข้อผิดพลาดนั้นมีขนาดและไม่ใช่ความแน่นอน


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

นี่คือวิธีการที่ฉันจินตนาการว่าฉันจะใช้ ฉันอาจลองใช้เทคนิคต่าง ๆ เหล่านี้เพื่อดูว่าเหมาะสมที่สุดสำหรับใบสมัครของฉัน การอัปเดตตัวอย่างโค้ดเป็นที่นิยมอย่างมาก!
user_123abc

7

GMP (เช่นไลบรารี GNU Multiple Precision) เป็นห้องสมุดที่มีความแม่นยำที่สุดที่ฉันรู้จัก

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


อ่าฉันเพิ่งสังเกตุเลขคณิตของช่วงเวลาที่กล่าวถึงในคำตอบของคุณ ... โหวตขึ้น!
Ali

2
ริชาร์ดแฮร์ริสเขียนชุดที่ดีของบทความในACCUวารสารเกินเกี่ยวกับการลอยตัวบลูส์พอยต์ บทความของเขาเกี่ยวกับการคำนวณช่วงเวลาอยู่ในOverload 103 ( pdf , p19-24)
Mark Booth

6

อย่างเข้มงวดและการประมาณค่าโดยอัตโนมัติข้อผิดพลาดสามารถทำได้โดยการวิเคราะห์ช่วงเวลา คุณทำงานกับช่วงเวลาแทนตัวเลข ตัวอย่างเช่นการเพิ่ม:

[a,b] + [c,d] = [min(a+c, a+d, b+c, b+d), max (a+c, a+d, b+c, b+d)] = [a+c, b+d]

ปัดเศษยังสามารถได้รับการจัดการอย่างจริงจังดูเลขคณิตช่วงโค้งมน

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

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

มันขึ้นอยู่กับปัญหาของคุณในมือว่าการคำนวณช่วงเวลาสามารถตอบสนองความต้องการของคุณได้หรือไม่


4

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

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


ฉันเห็นด้วย; การรวมเชิงตัวเลขเป็นกรณีที่สามารถคำนวณช่วงเวลาไร้เดียงสาได้อย่างแน่นอน อย่างไรก็ตามถึงแม้นางแบบเทย์เลอร์จะใช้การคำนวณช่วงเวลา ฉันคุ้นเคยกับงานของ Makino และ Berz และฉันเชื่อว่าพวกเขาใช้โมเดลของ Taylor ในแง่ของ RE Moore แม้ว่าพวกเขาจะใช้กลวิธีที่เกี่ยวข้องกับสิ่งที่พวกเขาเรียกว่า "อนุพันธ์พีชคณิต"
Geoff Oxberry

@GeoffOxberry: ใช่ - ฉันคิดว่าพีชคณิตเชิงอนุพันธ์นี้เป็นสิ่งที่จะได้รับข้อผิดพลาดในขั้นตอนการรวม
Erik P.

0

มีคนบอกฉันว่า MPIR เป็นห้องสมุดที่ดีที่จะใช้ถ้าคุณทำงานกับ Visual Studio:

http://mpir.org/


ยินดีต้อนรับสู่ SciComp.SE! คุณสามารถเพิ่มรายละเอียดเกี่ยวกับวิธีการใช้ไลบรารีนี้เพื่อวัดข้อผิดพลาดของการคำนวณจุดลอยตัวได้หรือไม่?
Christian Clason

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