ผลลัพธ์ทศนิยมที่แตกต่างกันเมื่อเปิดใช้งานการเพิ่มประสิทธิภาพ - บั๊กคอมไพเลอร์?


109

โค้ดด้านล่างทำงานบน Visual Studio 2008 ที่มีและไม่มีการปรับให้เหมาะสม แต่ใช้ได้เฉพาะกับ g ++ โดยไม่มีการปรับให้เหมาะสม (O0)

#include <cstdlib>
#include <iostream>
#include <cmath>

double round(double v, double digit)
{
    double pow = std::pow(10.0, digit);
    double t = v * pow;
    //std::cout << "t:" << t << std::endl;
    double r = std::floor(t + 0.5);
    //std::cout << "r:" << r << std::endl;
    return r / pow;
}

int main(int argc, char *argv[])
{
    std::cout << round(4.45, 1) << std::endl;
    std::cout << round(4.55, 1) << std::endl;
}

ผลลัพธ์ควรเป็น:

4.5
4.6

แต่ g ++ ที่มีการปรับให้เหมาะสม ( O1- O3) จะส่งออก:

4.5
4.5

หากฉันเพิ่มvolatileคีย์เวิร์ดก่อน t มันก็ใช้ได้ดังนั้นอาจมีข้อบกพร่องในการเพิ่มประสิทธิภาพบางประเภทหรือไม่

ทดสอบกับ g ++ 4.1.2 และ 4.4.4

นี่คือผลลัพธ์ของ ideone: http://ideone.com/Rz937

และตัวเลือกที่ฉันทดสอบกับ g ++ นั้นง่ายมาก:

g++ -O2 round.cpp

ผลลัพธ์ที่น่าสนใจยิ่งขึ้นแม้ว่าฉันจะเปิด/fp:fastตัวเลือกบน Visual Studio 2008 ผลลัพธ์ก็ยังคงถูกต้อง

คำถามเพิ่มเติม:

ฉันสงสัยว่าฉันควรเปิด-ffloat-storeตัวเลือกนี้ตลอดเวลาหรือไม่?

เนื่องจากเวอร์ชัน g ++ ที่ฉันทดสอบนั้นมาพร้อมกับCentOS / Red Hat Linux 5 และ CentOS / Redhat 6

ฉันรวบรวมโปรแกรมจำนวนมากภายใต้แพลตฟอร์มเหล่านี้และฉันกังวลว่ามันจะทำให้เกิดข้อบกพร่องที่ไม่คาดคิดในโปรแกรมของฉัน ดูเหมือนจะยากเล็กน้อยในการตรวจสอบรหัส C ++ ทั้งหมดของฉันและไลบรารีที่ใช้ว่ามีปัญหาดังกล่าวหรือไม่ ข้อเสนอแนะใด ๆ ?

มีใครสนใจ/fp:fastไหมว่าทำไมถึงเปิด Visual Studio 2008 ก็ยังใช้งานได้ ดูเหมือนว่า Visual Studio 2008 จะเชื่อถือได้มากกว่าปัญหานี้มากกว่า g ++?


51
สำหรับผู้ใช้ SO ใหม่ทั้งหมด: นี่คือวิธีที่คุณถามคำถาม +1
tenfour

1
FWIW ฉันได้ผลลัพธ์ที่ถูกต้องด้วย g ++ 4.5.0 โดยใช้ MinGW
Steve Blackwell

2
ideone ใช้ 4.3.4 ideone.com/b8VXg
Daniel A. White

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

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

คำตอบ:


91

โปรเซสเซอร์ Intel x86 ใช้ความแม่นยำในการขยาย 80 บิตภายในในขณะที่doubleปกติกว้าง 64 บิต ระดับการเพิ่มประสิทธิภาพที่แตกต่างกันมีผลต่อความถี่ที่ค่าทศนิยมจาก CPU ถูกบันทึกลงในหน่วยความจำดังนั้นจึงปัดเศษจากความแม่นยำ 80 บิตเป็นความแม่นยำ 64 บิต

ใช้-ffloat-storeตัวเลือก gcc เพื่อให้ได้ผลลัพธ์ทศนิยมที่เหมือนกันโดยมีระดับการเพิ่มประสิทธิภาพที่แตกต่างกัน

หรืออีกวิธีหนึ่งคือใช้long doubleประเภทซึ่งโดยปกติจะกว้าง 80 บิตบน gcc เพื่อหลีกเลี่ยงการปัดเศษจากความแม่นยำ 80 บิตเป็น 64 บิต

man gcc พูดทั้งหมด:

   -ffloat-store
       Do not store floating point variables in registers, and inhibit
       other options that might change whether a floating point value is
       taken from a register or memory.

       This option prevents undesirable excess precision on machines such
       as the 68000 where the floating registers (of the 68881) keep more
       precision than a "double" is supposed to have.  Similarly for the
       x86 architecture.  For most programs, the excess precision does
       only good, but a few programs rely on the precise definition of
       IEEE floating point.  Use -ffloat-store for such programs, after
       modifying them to store all pertinent intermediate computations
       into variables.

ในคอมไพเลอร์บิลด์ x86_64 ใช้ SSE รีจิสเตอร์สำหรับfloatและdoubleตามค่าเริ่มต้นดังนั้นจึงไม่มีการใช้ความแม่นยำเพิ่มเติมและปัญหานี้จะไม่เกิดขึ้น

gccตัวเลือกคอมไพเลอร์-mfpmathควบคุมสิ่งนั้น


20
ผมคิดว่านี่คือคำตอบ ค่าคงที่ 4.55 จะถูกแปลงเป็น 4.54999999999999 ซึ่งเป็นการแสดงไบนารีที่ใกล้เคียงที่สุดใน 64 บิต คูณด้วย 10 และปัดอีกครั้งเป็น 64 บิตและคุณจะได้ 45.5 หากคุณข้ามขั้นตอนการปัดเศษโดยเก็บไว้ในการลงทะเบียน 80 บิตคุณจะจบลงด้วย 45.4999999999999
Mark Ransom

ขอบคุณฉันไม่รู้ตัวเลือกนี้ด้วยซ้ำ แต่ฉันสงสัยว่าฉันควรเปิดตัวเลือก -ffloat-store หรือไม่? เนื่องจากเวอร์ชัน g ++ ที่ฉันทดสอบนั้นมาพร้อมกับ CentOS / Redhat 5 และ CentOS / Redhat 6 ฉันรวบรวมโปรแกรมจำนวนมากภายใต้แพลตฟอร์มเหล่านี้ฉันจึงกังวลว่าจะทำให้เกิดข้อบกพร่องที่ไม่คาดคิดภายในโปรแกรมของฉัน
หมี

5
@Bear คำสั่ง debug อาจทำให้ค่าถูกล้างจากรีจิสเตอร์ลงในหน่วยความจำ
Mark Ransom

2
@Bear ปกติใบสมัครของคุณควรจะได้รับประโยชน์จากการขยายความแม่นยำเว้นแต่จะดำเนินการกับค่าขนาดเล็กมากหรือมากเมื่อลอย 64 infบิตที่คาดว่าจะต่ำกว่าหรือล้นและการผลิต ไม่มีกฎเกณฑ์ที่ดีการทดสอบหน่วยสามารถให้คำตอบที่ชัดเจนแก่คุณได้
Maxim Egorushkin

2
@bear ตามกฎทั่วไปหากคุณต้องการผลลัพธ์ที่สามารถคาดเดาได้อย่างสมบูรณ์แบบและ / หรือสิ่งที่มนุษย์จะได้รับผลรวมบนกระดาษคุณควรหลีกเลี่ยงจุดลอยตัว -ffloat-store ลบแหล่งที่มาของความไม่สามารถคาดเดาได้อย่างหนึ่ง แต่ไม่ใช่กระสุนวิเศษ
plugwash

10

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

ดังที่ Maxim Yegorushkin ได้กล่าวไว้ในคำตอบของเขาปัญหาส่วนหนึ่งคือภายในคอมพิวเตอร์ของคุณใช้การแสดงจุดลอยตัว 80 บิต นี่เป็นเพียงส่วนหนึ่งของปัญหาเท่านั้น พื้นฐานของปัญหาคือตัวเลขใด ๆ ของรูปแบบ n.nn5 ไม่มีการแทนค่าเลขฐานสองที่แน่นอน กรณีมุมเหล่านี้มักเป็นตัวเลขที่ไม่แน่นอน

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

ดูว่า Excel จะปัดเศษตัวเลขลอยตัวได้อย่างไรแม้ว่าจะไม่ชัดเจน สำหรับอัลกอริทึมดังกล่าว

หรือคุณสามารถอยู่กับความจริงที่ว่าบางครั้งมุมของกรณีที่ผิดพลาด


6

คอมไพเลอร์ที่แตกต่างกันมีการตั้งค่าการเพิ่มประสิทธิภาพที่แตกต่างกัน บางส่วนของผู้ที่ตั้งค่าได้เร็วขึ้นการเพิ่มประสิทธิภาพไม่รักษากฎจุดลอยตัวอย่างเข้มงวดตามมาตรฐาน IEEE 754 Visual Studio มีการตั้งค่าเฉพาะ/fp:strict, /fp:precise, /fp:fastที่/fp:fastละเมิดมาตรฐานในสิ่งที่สามารถทำได้ คุณอาจพบว่าแฟล็กนี้เป็นสิ่งที่ควบคุมการปรับให้เหมาะสมในการตั้งค่าดังกล่าว คุณอาจพบการตั้งค่าที่คล้ายกันใน GCC ซึ่งจะเปลี่ยนลักษณะการทำงาน

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


4
มี -ffast-mathสวิตช์สำหรับ GCC ที่และไม่ได้เปิดใช้งานโดย-Oระดับการเพิ่มประสิทธิภาพใด ๆเนื่องจากมีคำพูด: "อาจส่งผลให้ผลลัพธ์ของโปรแกรมไม่ถูกต้องซึ่งขึ้นอยู่กับการใช้ IEEE หรือ ISO กฎ / ข้อกำหนดสำหรับฟังก์ชันทางคณิตศาสตร์อย่างถูกต้อง"
จ้า

@ แมท: ฉันได้ลอง-ffast-mathทำสิ่งอื่น ๆ กับฉันg++ 4.4.3แล้วและฉันก็ยังไม่สามารถทำซ้ำปัญหาได้
NPE

นีซ: กับ-ffast-mathฉันจะได้รับในทั้งสองกรณีการเพิ่มประสิทธิภาพในระดับที่สูงกว่า4.5 0
Kerrek SB

: (แก้ไขฉันจะได้รับ4.5ด้วย-O1และ-O2แต่ไม่ได้มี-O0และ-O3ใน GCC 4.4.3 แต่มี-O1,2,3ใน GCC 4.6.1.)
Kerrek SB

4

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

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

คำถามเพิ่มเติม:

ฉันสงสัยว่าฉันควรเปิด-ffloat-storeตัวเลือกเสมอหรือไม่?

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

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

คุณอาจต้องการดูวิธีแก้ปัญหาอื่น ๆที่คุณกำลังพยายามแก้ไข (น่าเสียดายที่ Koenig ไม่ได้ชี้ไปที่กระดาษจริงและฉันไม่สามารถหาตำแหน่ง "บัญญัติ" ที่ชัดเจนได้ดังนั้นฉันจึง จะต้องส่งคุณไปที่Google )


หากคุณไม่ได้ปัดเศษเพื่อจุดประสงค์ในการส่งออกฉันอาจดูที่std::modf()(in cmath) และstd::numeric_limits<double>::epsilon()(in limits) เมื่อนึกถึงround()ฟังก์ชันดั้งเดิมฉันเชื่อว่าการแทนที่การโทรstd::floor(d + .5)ด้วยการเรียกฟังก์ชันนี้จะดีกว่า:

// this still has the same problems as the original rounding function
int round_up(double d)
{
    // return value will be coerced to int, and truncated as expected
    // you can then assign the int to a double, if desired
    return d + 0.5;
}

ฉันคิดว่านั่นเป็นการแนะนำการปรับปรุงต่อไปนี้:

// this won't work for negative d ...
// this may still round some numbers up when they should be rounded down
int round_up(double d)
{
    double floor;
    d = std::modf(d, &floor);
    return floor + (d + .5 + std::numeric_limits<double>::epsilon());
}

หมายเหตุง่ายๆ: std::numeric_limits<T>::epsilon()ถูกกำหนดให้เป็น "จำนวนที่น้อยที่สุดที่เพิ่มใน 1 ซึ่งสร้างจำนวนที่ไม่เท่ากับ 1" โดยปกติคุณจะต้องใช้ epsilon แบบสัมพัทธ์ (เช่นสเกล epsilon อย่างใดอย่างหนึ่งเพื่อพิจารณาว่าคุณกำลังทำงานกับตัวเลขอื่นที่ไม่ใช่ "1") ผลรวมของd, .5และstd::numeric_limits<double>::epsilon()ควรจะอยู่ใกล้ 1 ดังนั้นการจัดกลุ่มว่านอกจากนี้หมายความว่าstd::numeric_limits<double>::epsilon()จะเกี่ยวกับขนาดที่เหมาะสมสำหรับสิ่งที่เรากำลังทำ หากมีสิ่งใดstd::numeric_limits<double>::epsilon()จะมากเกินไป (เมื่อผลรวมของทั้งสามมีค่าน้อยกว่าหนึ่ง) และอาจทำให้เราปัดเศษตัวเลขขึ้นเมื่อเราไม่ควร


std::nearbyint()ปัจจุบันคุณควรพิจารณา


"epsilon สัมพัทธ์" เรียกว่า 1 ulp (1 หน่วยในตำแหน่งสุดท้าย) x - nextafter(x, INFINITY)เกี่ยวข้องกับ 1 ulp สำหรับ x (แต่อย่าใช้อย่างนั้นฉันแน่ใจว่ามีกรณีมุมและฉันเพิ่งสร้างมันขึ้นมา) ตัวอย่าง cppreference สำหรับมีตัวอย่างของการปรับให้ได้รับข้อผิดพลาดญาติepsilon() ULP-เบส
Peter Cordes

2
BTW คำตอบของปี 2016 -ffloat-storeคืออย่าใช้ x87 ตั้งแต่แรก ใช้คณิตศาสตร์ SSE2 (ไบนารี 64 บิตหรือ-mfpmath=sse -msse2สำหรับการสร้างไบนารี 32 บิตแบบเก่า) เนื่องจาก SSE / SSE2 มีจังหวะที่ไม่มีความแม่นยำเพิ่มเติม doubleและfloatvars ในการลงทะเบียน XMM นั้นอยู่ในรูปแบบ IEEE 64 บิตหรือ 32 บิต (ไม่เหมือนกับ x87 ที่รีจิสเตอร์จะเป็น 80 บิตเสมอและจัดเก็บลงในหน่วยความจำเป็น 32 หรือ 64 บิต)
Peter Cordes

3

คำตอบที่ยอมรับนั้นถูกต้องหากคุณกำลังคอมไพล์ไปยังเป้าหมาย x86 ที่ไม่มี SSE2 โปรเซสเซอร์ x86 ที่ทันสมัยทั้งหมดรองรับ SSE2 ดังนั้นหากคุณสามารถใช้ประโยชน์จากมันได้คุณควร:

-mfpmath=sse -msse2 -ffp-contract=off

มาทำลายสิ่งนี้กัน

-mfpmath=sse -msse2. การดำเนินการนี้จะทำการปัดเศษโดยใช้ SSE2 register ซึ่งเร็วกว่าการจัดเก็บผลลัพธ์ระดับกลางทั้งหมดไว้ในหน่วยความจำ โปรดทราบว่านี่เป็นค่าเริ่มต้นของ GCC สำหรับ x86-64 อยู่แล้ว จากGCC wiki :

สำหรับโปรเซสเซอร์ x86 ที่ทันสมัยกว่าซึ่งรองรับ SSE2 การระบุอ็อพชันคอมไพเลอร์-mfpmath=sse -msse2ทำให้มั่นใจได้ว่าการดำเนินการแบบลอยและแบบคู่ทั้งหมดจะดำเนินการในรีจิสเตอร์ SSE และปัดเศษได้อย่างถูกต้อง ตัวเลือกเหล่านี้ไม่มีผลต่อ ABI ดังนั้นจึงควรใช้เมื่อใดก็ตามที่เป็นไปได้สำหรับผลลัพธ์ที่เป็นตัวเลขที่คาดเดาได้

-ffp-contract=off. อย่างไรก็ตามการควบคุมการปัดเศษไม่เพียงพอสำหรับการจับคู่แบบตรงทั้งหมด คำแนะนำ FMA (ผสมเพิ่มทวีคูณ) สามารถเปลี่ยนพฤติกรรมการปัดเศษเทียบกับคู่ที่ไม่หลอมรวมได้ดังนั้นเราจึงจำเป็นต้องปิดใช้งาน นี่เป็นค่าเริ่มต้นของเสียงดังไม่ใช่ GCC ตามที่อธิบายโดยคำตอบนี้ :

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

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


1

ฉันขุดคุ้ยปัญหานี้มากขึ้นและฉันสามารถนำข้อยุติได้มากขึ้น ประการแรกการแทนค่าที่แน่นอนของ 4.45 และ 4.55 ตาม gcc บน x84_64 มีดังต่อไปนี้ (ด้วย libquadmath เพื่อพิมพ์ความแม่นยำสุดท้าย):

float 32:   4.44999980926513671875
double 64:  4.45000000000000017763568394002504646778106689453125
doublex 80: 4.449999999999999999826527652402319290558807551860809326171875
quad 128:   4.45000000000000000000000000000000015407439555097886824447823540679418548304813185723105561919510364532470703125

float 32:   4.55000019073486328125
double 64:  4.54999999999999982236431605997495353221893310546875
doublex 80: 4.550000000000000000173472347597680709441192448139190673828125
quad 128:   4.54999999999999999999999999999999984592560444902113175552176459320581451695186814276894438080489635467529296875

ดังที่Maximกล่าวไว้ข้างต้นปัญหาเกิดจากขนาด 80 บิตของการลงทะเบียน FPU

แต่ทำไมปัญหาไม่เคยเกิดขึ้นบน Windows? ใน IA-32 ที่ x87 FPU มีการกำหนดค่าการใช้ความแม่นยำภายในสำหรับ mantissa 53 บิต (เทียบเท่ากับขนาดรวมของ 64 บิต: double) สำหรับ Linux และ Mac OS, ความแม่นยำเริ่มต้นของ 64 บิตที่ใช้ (เทียบเท่ากับขนาดรวมของ 80 บิต: long double) ดังนั้นปัญหาควรจะเป็นไปได้หรือไม่บนแพลตฟอร์มที่แตกต่างกันเหล่านี้โดยการเปลี่ยนคำควบคุมของ FPU (สมมติว่าลำดับของคำสั่งจะทำให้เกิดข้อผิดพลาด) ปัญหาได้รับการรายงานไปยัง gcc เป็นข้อผิดพลาด 323 (อ่านอย่างน้อยความคิดเห็น 92!)

เพื่อแสดงความแม่นยำของแมนทิสซาบน Windows คุณสามารถรวบรวมสิ่งนี้ใน 32 บิตด้วย VC ++:

#include "stdafx.h"
#include <stdio.h>  
#include <float.h>  

int main(void)
{
    char t[] = { 64, 53, 24, -1 };
    unsigned int cw = _control87(0, 0);
    printf("mantissa is %d bits\n", t[(cw >> 16) & 3]);
}

และบน Linux / Cygwin:

#include <stdio.h>

int main(int argc, char **argv)
{
    char t[] = { 24, -1, 53, 64 };
    unsigned int cw = 0;
    __asm__ __volatile__ ("fnstcw %0" : "=m" (*&cw));
    printf("mantissa is %d bits\n", t[(cw >> 8) & 3]);
}

โปรดทราบว่าด้วย gcc คุณสามารถตั้งค่าความแม่นยำของ FPU ด้วยไฟล์ -mpc32/64/80ได้แม้ว่าจะถูกละเว้นใน Cygwin ก็ตาม แต่จำไว้ว่ามันจะปรับเปลี่ยนขนาดของแมนทิสซา แต่ไม่ใช่เลขชี้กำลังเพื่อให้ประตูเปิดไปสู่พฤติกรรมอื่น ๆ

บนสถาปัตยกรรม x86_64 SSE ถูกใช้ตามที่กล่าวโดยtmandryดังนั้นปัญหาจะไม่เกิดขึ้นเว้นแต่คุณจะบังคับ x87 FPU เก่าสำหรับการประมวลผล FP ด้วย-mfpmath=387หรือเว้นแต่คุณจะคอมไพล์ในโหมด 32 บิตด้วย-m32(คุณจะต้องใช้แพ็คเกจมัลติลิบ) ฉันสามารถสร้างปัญหาซ้ำบน Linux โดยใช้แฟล็กและเวอร์ชันต่างๆของ gcc:

g++-5 -m32 floating.cpp -O1
g++-8 -mfpmath=387 floating.cpp -O1

ฉันลองใช้ชุดค่าผสมสองสามอย่างบน Windows หรือ Cygwin กับ VC ++ / gcc / tcc แต่ข้อผิดพลาดไม่ปรากฏขึ้น ฉันคิดว่าลำดับของคำสั่งที่สร้างขึ้นไม่เหมือนกัน

สุดท้ายโปรดทราบว่าวิธีที่แปลกใหม่ในการป้องกันปัญหานี้ด้วย 4.45 หรือ 4.55 คือการใช้_Decimal32/64/128แต่การสนับสนุนนั้นหายากจริงๆ ... ฉันใช้เวลาส่วนใหญ่เพื่อให้สามารถทำ printf ด้วยlibdfp!


0

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

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