ทำไมการเปลี่ยน 0.1f ถึง 0 ทำให้ประสิทธิภาพลดลง 10 เท่า?


1527

ทำไมรหัสนี้

const float x[16] = {  1.1,   1.2,   1.3,     1.4,   1.5,   1.6,   1.7,   1.8,
                       1.9,   2.0,   2.1,     2.2,   2.3,   2.4,   2.5,   2.6};
const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812,
                     1.923, 2.034, 2.145,   2.256, 2.367, 2.478, 2.589, 2.690};
float y[16];
for (int i = 0; i < 16; i++)
{
    y[i] = x[i];
}

for (int j = 0; j < 9000000; j++)
{
    for (int i = 0; i < 16; i++)
    {
        y[i] *= x[i];
        y[i] /= z[i];
        y[i] = y[i] + 0.1f; // <--
        y[i] = y[i] - 0.1f; // <--
    }
}

ทำงานได้เร็วกว่าบิตต่อไปนี้มากกว่า 10 เท่า (เหมือนกันยกเว้นที่ระบุไว้)

const float x[16] = {  1.1,   1.2,   1.3,     1.4,   1.5,   1.6,   1.7,   1.8,
                       1.9,   2.0,   2.1,     2.2,   2.3,   2.4,   2.5,   2.6};
const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812,
                     1.923, 2.034, 2.145,   2.256, 2.367, 2.478, 2.589, 2.690};
float y[16];
for (int i = 0; i < 16; i++)
{
    y[i] = x[i];
}

for (int j = 0; j < 9000000; j++)
{
    for (int i = 0; i < 16; i++)
    {
        y[i] *= x[i];
        y[i] /= z[i];
        y[i] = y[i] + 0; // <--
        y[i] = y[i] - 0; // <--
    }
}

เมื่อรวบรวมกับ Visual Studio 2010 SP1 ระดับการเพิ่มประสิทธิภาพได้รับ-02กับsse2การเปิดใช้งาน ฉันยังไม่ได้ทดสอบกับคอมไพเลอร์อื่น ๆ


10
คุณวัดความแตกต่างได้อย่างไร และตัวเลือกใดที่คุณใช้เมื่อคุณคอมไพล์?
James Kanze

158
เหตุใดคอมไพเลอร์จึงไม่ทิ้ง +/- 0 ในกรณีนี้!?
Michael Dorgan

127
@ Zyx2000 คอมไพเลอร์ไม่ได้อยู่ใกล้กับที่โง่ แยกส่วนเป็นตัวอย่างเล็ก ๆ น้อย ๆ ในการแสดง LinqPad ว่ามันถ่มน้ำลายออกรหัสเดียวกันว่าคุณจะใช้0, 0f, 0dหรือแม้กระทั่ง(int)0ในบริบทที่doubleเป็นสิ่งจำเป็น
millimoose

14
ระดับการเพิ่มประสิทธิภาพคืออะไร?
Otto Allmendinger

คำตอบ:


1616

ยินดีต้อนรับสู่โลกแห่งจุดลอยตัวที่ผิดปกติ ! พวกเขาสามารถสร้างความหายนะต่อการแสดง !!!

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

หากคุณพิมพ์ตัวเลขหลังการวนซ้ำ 10,000 รอบคุณจะเห็นว่าตัวเลขเหล่านั้นแปรสภาพเป็นค่าที่แตกต่างกันขึ้นอยู่กับว่ามีการใช้0หรือ0.1ไม่

นี่คือรหัสทดสอบที่รวบรวมใน x64:

int main() {

    double start = omp_get_wtime();

    const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6};
    const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690};
    float y[16];
    for(int i=0;i<16;i++)
    {
        y[i]=x[i];
    }
    for(int j=0;j<9000000;j++)
    {
        for(int i=0;i<16;i++)
        {
            y[i]*=x[i];
            y[i]/=z[i];
#ifdef FLOATING
            y[i]=y[i]+0.1f;
            y[i]=y[i]-0.1f;
#else
            y[i]=y[i]+0;
            y[i]=y[i]-0;
#endif

            if (j > 10000)
                cout << y[i] << "  ";
        }
        if (j > 10000)
            cout << endl;
    }

    double end = omp_get_wtime();
    cout << end - start << endl;

    system("pause");
    return 0;
}

เอาท์พุท:

#define FLOATING
1.78814e-007  1.3411e-007  1.04308e-007  0  7.45058e-008  6.70552e-008  6.70552e-008  5.58794e-007  3.05474e-007  2.16067e-007  1.71363e-007  1.49012e-007  1.2666e-007  1.11759e-007  1.04308e-007  1.04308e-007
1.78814e-007  1.3411e-007  1.04308e-007  0  7.45058e-008  6.70552e-008  6.70552e-008  5.58794e-007  3.05474e-007  2.16067e-007  1.71363e-007  1.49012e-007  1.2666e-007  1.11759e-007  1.04308e-007  1.04308e-007

//#define FLOATING
6.30584e-044  3.92364e-044  3.08286e-044  0  1.82169e-044  1.54143e-044  2.10195e-044  2.46842e-029  7.56701e-044  4.06377e-044  3.92364e-044  3.22299e-044  3.08286e-044  2.66247e-044  2.66247e-044  2.24208e-044
6.30584e-044  3.92364e-044  3.08286e-044  0  1.82169e-044  1.54143e-044  2.10195e-044  2.45208e-029  7.56701e-044  4.06377e-044  3.92364e-044  3.22299e-044  3.08286e-044  2.66247e-044  2.66247e-044  2.24208e-044

สังเกตว่าในการรันครั้งที่สองตัวเลขจะเข้าใกล้ศูนย์มากแค่ไหน

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


เพื่อแสดงให้เห็นว่าสิ่งนี้มีทุกอย่างเกี่ยวกับตัวเลขที่เป็นค่าปกติหากเราล้างค่า denormals ให้เป็นศูนย์ด้วยการเพิ่มค่านี้ในส่วนเริ่มต้นของรหัส:

_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

จากนั้นเวอร์ชันที่มี0จะไม่ช้าลง 10 เท่าและเร็วขึ้นจริง (ต้องมีการรวบรวมรหัสด้วยการเปิดใช้งาน SSE)

ซึ่งหมายความว่าแทนที่จะใช้ค่าความแม่นยำที่ต่ำกว่าประหลาดเหล่านี้เราเพียงปัดเศษเป็นศูนย์แทน

การจับเวลา: Core i7 920 @ 3.5 GHz:

//  Don't flush denormals to zero.
0.1f: 0.564067
0   : 26.7669

//  Flush denormals to zero.
0.1f: 0.587117
0   : 0.341406

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


100
ฉันยังคงพบว่ามันแปลกเล็กน้อยที่ "+ 0" ไม่ได้รับการปรับปรุงโดยคอมไพเลอร์โดยค่าเริ่มต้น สิ่งนี้จะเกิดขึ้นไหมถ้าเขาใส่ "+ 0.0f"?
s73v3r

51
@ s73v3r นั่นเป็นคำถามที่ดีมาก ตอนนี้ฉันดูชุดประกอบไม่ได้+ 0.0fรับการปรับให้เหมาะสม ถ้าฉันต้องเดามันอาจเป็นไปได้ว่า+ 0.0fจะมีผลข้างเคียงหากy[i]เกิดขึ้นเป็นสัญญาณNaNหรือบางสิ่งบางอย่าง ... ฉันอาจจะผิด
Mysticial

14
คู่จะยังคงพบปัญหาเดียวกันในหลาย ๆ กรณีที่ขนาดตัวเลขแตกต่างกัน Flush-to-zero นั้นใช้ได้กับแอปพลิเคชั่นเสียง (และอื่น ๆ ที่คุณสามารถเสีย 1e-38 ที่นี่และที่นั่น) แต่ฉันเชื่อว่าใช้ไม่ได้กับ x87 หากไม่มี FTZ การแก้ไขปกติสำหรับแอปพลิเคชั่นเสียงคือการฉีดสัญญาณ DC หรือสัญญาณคลื่นสี่เหลี่ยมที่ต่ำมาก (ไม่ได้ยินเสียง) ไปยังหมายเลขที่กระวนกระวายใจ
Russell Borogove

16
@Isaac เพราะเมื่อ y [i] น้อยกว่า 0.1 อย่างมีนัยสำคัญจะส่งผลให้สูญเสียความแม่นยำเนื่องจากตัวเลขที่สำคัญที่สุดในจำนวนจะสูงขึ้น
Dan Is Fiddling โดย Firelight

167
@ s73v3r: ไม่สามารถปรับให้เหมาะกับ + 0.f ได้เนื่องจาก floating-point มีค่าเป็นลบ 0 และผลลัพธ์ของการเพิ่ม +0f ถึง -.0f คือ +0f ดังนั้นการเพิ่ม 0.f ไม่ใช่การดำเนินการระบุตัวตนและไม่สามารถปรับให้เหมาะสมได้
Eric Postpischil

415

การใช้gccและการใช้ความแตกต่างกับแอสเซมบลีที่สร้างจะทำให้เกิดความแตกต่างนี้เท่านั้น:

73c68,69
<   movss   LCPI1_0(%rip), %xmm1
---
>   movabsq $0, %rcx
>   cvtsi2ssq   %rcx, %xmm1
81d76
<   subss   %xmm1, %xmm0

cvtsi2ssqหนึ่งเป็น 10 ครั้งช้าแน่นอน

เห็นได้ชัดว่าfloatรุ่นใช้การลงทะเบียนXMM ที่โหลดจากหน่วยความจำในขณะที่intรุ่นแปลงintค่าจริง0 เพื่อfloatใช้cvtsi2ssqคำแนะนำใช้เวลานาน ผ่าน-O3ไปยัง gcc ไม่ได้ช่วย (gcc เวอร์ชั่น 4.2.1.)

(การใช้doubleแทนที่จะfloatไม่สำคัญยกเว้นว่าจะเปลี่ยนcvtsi2ssqเป็น a cvtsi2sdq.)

ปรับปรุง

การทดสอบพิเศษบางอย่างแสดงให้เห็นว่ามันไม่จำเป็นต้องเป็นcvtsi2ssqคำสั่ง เมื่อตัดออก (ใช้ a int ai=0;float a=ai;และใช้aแทน0) ความต่างความเร็วยังคงอยู่ ดังนั้น @ Mysticial นั้นถูกต้อง นี้สามารถเห็นได้โดยการทดสอบค่าระหว่างและ0 0.1fจุดเปลี่ยนในรหัสด้านบนอยู่ที่ประมาณ0.00000000000000000000000000000001เมื่อลูปนั้นใช้เวลานานถึง 10 เท่า

ปรับปรุง << 1

การสร้างภาพปรากฏการณ์ที่น่าสนใจนี้เล็กน้อย:

  • คอลัมน์ 1: ทุ่นหารด้วย 2 สำหรับการวนซ้ำทุกครั้ง
  • คอลัมน์ 2: การแทนเลขฐานสองของการลอยนี้
  • คอลัมน์ 3: เวลาที่ใช้ในการหาผลรวมโฟลตนี้ 1e7 ครั้ง

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

0.000000000000000000000000000000000100000004670110: 10111100001101110010000011100000 45 ms
0.000000000000000000000000000000000050000002335055: 10111100001101110010000101100000 43 ms
0.000000000000000000000000000000000025000001167528: 10111100001101110010000001100000 43 ms
0.000000000000000000000000000000000012500000583764: 10111100001101110010000110100000 42 ms
0.000000000000000000000000000000000006250000291882: 10111100001101110010000010100000 48 ms
0.000000000000000000000000000000000003125000145941: 10111100001101110010000100100000 43 ms
0.000000000000000000000000000000000001562500072970: 10111100001101110010000000100000 42 ms
0.000000000000000000000000000000000000781250036485: 10111100001101110010000111000000 42 ms
0.000000000000000000000000000000000000390625018243: 10111100001101110010000011000000 42 ms
0.000000000000000000000000000000000000195312509121: 10111100001101110010000101000000 43 ms
0.000000000000000000000000000000000000097656254561: 10111100001101110010000001000000 42 ms
0.000000000000000000000000000000000000048828127280: 10111100001101110010000110000000 44 ms
0.000000000000000000000000000000000000024414063640: 10111100001101110010000010000000 42 ms
0.000000000000000000000000000000000000012207031820: 10111100001101110010000100000000 42 ms
0.000000000000000000000000000000000000006103515209: 01111000011011100100001000000000 789 ms
0.000000000000000000000000000000000000003051757605: 11110000110111001000010000000000 788 ms
0.000000000000000000000000000000000000001525879503: 00010001101110010000100000000000 788 ms
0.000000000000000000000000000000000000000762939751: 00100011011100100001000000000000 795 ms
0.000000000000000000000000000000000000000381469876: 01000110111001000010000000000000 896 ms
0.000000000000000000000000000000000000000190734938: 10001101110010000100000000000000 813 ms
0.000000000000000000000000000000000000000095366768: 00011011100100001000000000000000 798 ms
0.000000000000000000000000000000000000000047683384: 00110111001000010000000000000000 791 ms
0.000000000000000000000000000000000000000023841692: 01101110010000100000000000000000 802 ms
0.000000000000000000000000000000000000000011920846: 11011100100001000000000000000000 809 ms
0.000000000000000000000000000000000000000005961124: 01111001000010000000000000000000 795 ms
0.000000000000000000000000000000000000000002980562: 11110010000100000000000000000000 835 ms
0.000000000000000000000000000000000000000001490982: 00010100001000000000000000000000 864 ms
0.000000000000000000000000000000000000000000745491: 00101000010000000000000000000000 915 ms
0.000000000000000000000000000000000000000000372745: 01010000100000000000000000000000 918 ms
0.000000000000000000000000000000000000000000186373: 10100001000000000000000000000000 881 ms
0.000000000000000000000000000000000000000000092486: 01000010000000000000000000000000 857 ms
0.000000000000000000000000000000000000000000046243: 10000100000000000000000000000000 861 ms
0.000000000000000000000000000000000000000000022421: 00001000000000000000000000000000 855 ms
0.000000000000000000000000000000000000000000011210: 00010000000000000000000000000000 887 ms
0.000000000000000000000000000000000000000000005605: 00100000000000000000000000000000 799 ms
0.000000000000000000000000000000000000000000002803: 01000000000000000000000000000000 828 ms
0.000000000000000000000000000000000000000000001401: 10000000000000000000000000000000 815 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 44 ms

การสนทนาที่เท่าเทียมกันเกี่ยวกับ ARM สามารถพบได้ในคำถาม Stack Overflow Denormalized floating point ใน Objective-C .


27
-Oไม่ต้องซ่อม แต่-ffast-mathทำ (ฉันใช้สิ่งนั้นตลอดเวลา IMO กรณีมุมที่ทำให้เกิดปัญหาความแม่นยำไม่ควรเปิดในโปรแกรมที่ออกแบบมาอย่างเหมาะสมแล้ว)
leftaroundabout

ไม่มีการแปลงในระดับการเพิ่มประสิทธิภาพเชิงบวกใด ๆ ด้วย gcc-4.6
Jed

@leftaroundabout: รวบรวมการปฏิบัติการ (ไม่ใช่ไลบรารี่) ด้วยการ-ffast-mathเชื่อมโยงโค้ดเริ่มต้นพิเศษบางอย่างที่ตั้งค่า FTZ (เปี่ยมไปที่ศูนย์) และ DAZ (denormal เป็นศูนย์) ใน MXCSR ดังนั้น CPU ไม่จำเป็นต้องใช้ไมโครโค้ดช้าสำหรับการถอดรหัส
ปีเตอร์

34

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

  • อาจไม่ทำงานในบางสภาพแวดล้อมของ GCC:

    // Requires #include <fenv.h>
    fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
  • อาจไม่ทำงานในบางสภาพแวดล้อม Visual Studio: 1

    // Requires #include <xmmintrin.h>
    _mm_setcsr( _mm_getcsr() | (1<<15) | (1<<6) );
    // Does both FTZ and DAZ bits. You can also use just hex value 0x8040 to do both.
    // You might also want to use the underflow mask (1<<11)
  • ปรากฏว่าทำงานทั้งใน GCC และ Visual Studio:

    // Requires #include <xmmintrin.h>
    // Requires #include <pmmintrin.h>
    _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
    _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
  • คอมไพเลอร์ของ Intel มีตัวเลือกในการปิดใช้งาน denormals ตามค่าเริ่มต้นบน CPU ของ Intel ที่ทันสมัย รายละเอียดเพิ่มเติมที่นี่

  • คอมไพเลอร์สวิทช์ -ffast-math, -msseหรือ-mfpmath=sseจะปิด denormals และทำให้สิ่งอื่น ๆ น้อยเร็วขึ้น แต่น่าเสียดายที่ยังทำจำนวนมากใกล้เคียงอื่น ๆ ที่อาจทำลายรหัสของคุณ ทดสอบอย่างระมัดระวัง! เทียบเท่าของ fast-math สำหรับคอมไพเลอร์ Visual Studio คือ/fp:fastแต่ฉันไม่สามารถยืนยันได้ว่านี่เป็นการปิดการทำงานของ denormals หรือไม่ 1


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

Windows X64 ผ่านการตั้งค่าอย่างกระทันหันอันเดอร์อันเดอร์เมื่อเปิดตัว. exe ในขณะที่ Windows 32 บิตและ Linux ไม่ทำเช่นนั้น บน linux, gcc -ffast-math ควรตั้งค่า underflow อย่างกระทันหัน (แต่ฉันคิดว่าไม่ใช่บน Windows) คอมไพเลอร์ของ Intel ควรจะเตรียมใช้งานใน main () เพื่อให้ความแตกต่างของระบบปฏิบัติการเหล่านี้ไม่ผ่าน แต่ฉันถูกกัดและต้องตั้งค่าไว้อย่างชัดเจนในโปรแกรม Intel CPU ที่ขึ้นต้นด้วย Sandy Bridge ควรจะจัดการกับ subnormals ที่เกิดขึ้นในการเพิ่ม / ลบ (แต่ไม่หาร / คูณ) อย่างมีประสิทธิภาพดังนั้นจึงมีกรณีสำหรับการใช้อันเดอร์ที่ค่อยเป็นค่อยไป
tim18

1
Microsoft / fp: เร็ว (ไม่ใช่ค่าเริ่มต้น) ไม่ได้ทำสิ่งก้าวร้าวใด ๆ ใน gcc -ffast-math หรือ ICL (ค่าเริ่มต้น) / fp: fast มันเหมือน ICL / fp: source มากขึ้น ดังนั้นคุณต้องตั้งค่า / fp: (และในบางกรณีโหมดอันเดอร์โฟล์) อย่างชัดเจนหากคุณต้องการเปรียบเทียบคอมไพเลอร์เหล่านี้
tim18

18

ใน gcc คุณสามารถเปิดใช้งาน FTZ และ DAZ ด้วยสิ่งนี้:

#include <xmmintrin.h>

#define FTZ 1
#define DAZ 1   

void enableFtzDaz()
{
    int mxcsr = _mm_getcsr ();

    if (FTZ) {
            mxcsr |= (1<<15) | (1<<11);
    }

    if (DAZ) {
            mxcsr |= (1<<6);
    }

    _mm_setcsr (mxcsr);
}

นอกจากนี้ยังใช้สวิตช์ gcc: -msse -mfpmath = sse

(เครดิตที่เกี่ยวข้องกับ Carl Hetherington [1])

[1] http://carlh.net/plugins/denormals.php


ดูได้fesetround()จากfenv.h(กำหนดสำหรับ C99) สำหรับวิธีการปัดเศษแบบพกพา ( linux.die.net/man/3/fesetround )จะมีผลกับการปฏิบัติการ FP ทั้งหมดไม่ใช่แค่ subnormals )
German Garcia

คุณแน่ใจหรือไม่ว่าคุณต้องการ 1 << 15 และ 1 << 11 สำหรับ FTZ? ฉันเห็นเพียง 1 << 15 ที่ยกมาที่อื่น ...
รูปที่

@ รูป: 1 << 11 ใช้สำหรับ Underflow Mask ข้อมูลเพิ่มเติมที่นี่: softpixel.com/~cwright/programming/simd/sse.php
German Garcia

@GermanGarcia นี้ไม่ตอบคำถาม OPs; คำถามคือ "ทำไมรหัสนี้ถึงน้อยกว่ารันเร็วกว่า 10 เท่า ... " คุณควรพยายามตอบก่อนที่จะให้วิธีแก้ปัญหานี้หรือให้ความคิดเห็นนี้

9

ความคิดเห็นของ Dan Neelyควรขยายออกเป็นคำตอบ:

มันไม่ได้เป็นค่าคง0.0fที่เป็นศูนย์ที่ถูกทำให้เป็นปกติหรือทำให้ช้าลงมันเป็นค่าที่เข้าหาศูนย์แต่ละการวนซ้ำของการวนซ้ำ เมื่อพวกเขาเข้าใกล้ศูนย์มากขึ้นพวกเขาต้องการความแม่นยำมากขึ้นในการเป็นตัวแทน นี่คือy[i]ค่า (พวกเขาเข้าใกล้ศูนย์เพราะx[i]/z[i]น้อยกว่า 1.0 สำหรับทุกคนi)

y[i] = y[i] + 0.1f;ความแตกต่างที่สำคัญระหว่างรุ่นช้าและเร็วของรหัสที่เป็นคำสั่ง ทันทีที่บรรทัดนี้ถูกประมวลผลการวนซ้ำแต่ละรอบความแม่นยำพิเศษในการลอยจะหายไปและการปรับสภาพที่ต้องการเพื่อแสดงความแม่นยำนั้นไม่จำเป็นอีกต่อไป หลังจากนั้นการดำเนินการจุดลอยตัวy[i]จะยังคงอยู่อย่างรวดเร็วเนื่องจากมันไม่ได้ถูกทำให้เป็นมาตรฐาน

ทำไมความแม่นยำเป็นพิเศษหายไปเมื่อคุณเพิ่ม0.1f? เพราะตัวเลขจุดลอยตัวมีเพียงตัวเลขที่สำคัญจำนวนมาก สมมติว่าคุณมีการจัดเก็บข้อมูลเพียงพอสำหรับตัวเลขสามหลักอย่างมีนัยสำคัญแล้ว0.00001 = 1e-5และอย่างน้อยสำหรับรูปแบบการลอยตัวอย่างเช่นเพราะมันไม่ได้มีห้องพักเพื่อเก็บบิตอย่างมีนัยสำคัญน้อยใน0.00001 + 0.1 = 0.10.10001

กล่าวโดยย่อy[i]=y[i]+0.1f; y[i]=y[i]-0.1f;คือไม่มีตัวเลือกที่คุณอาจคิดว่าเป็น

ลึกลับกล่าวเช่นนี้ : เนื้อหาของเรื่องลอยไม่ใช่แค่รหัสแอสเซมบลี

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