อะไรคือความแตกต่างระหว่าง NaN ที่เงียบและ NaN การส่งสัญญาณ?


108

ฉันได้อ่านเกี่ยวกับจุดลอยตัวและฉันเข้าใจว่า NaN อาจเป็นผลมาจากการดำเนินการ แต่ฉันไม่เข้าใจว่านี่คือแนวคิดอะไรกันแน่ อะไรคือความแตกต่างระหว่างพวกเขา?

สิ่งใดที่สามารถผลิตได้ในระหว่างการเขียนโปรแกรม C ++ ในฐานะโปรแกรมเมอร์ฉันสามารถเขียนโปรแกรมที่ทำให้เกิด sNaN ได้หรือไม่

คำตอบ:


73

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

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

ในระบบ POSIX / Unix ข้อยกเว้นจุดลอยจะถูกจับโดยปกติจะใช้จัดการสำหรับSIGFPE


37
การเพิ่มสิ่งนี้: โดยทั่วไปจุดประสงค์ของการส่งสัญญาณ NaN (sNaN) มีไว้สำหรับการดีบัก ตัวอย่างเช่นวัตถุลอยตัวอาจเริ่มต้นเป็น sNaN จากนั้นหากโปรแกรมล้มเหลวในค่าใดค่าหนึ่งก่อนที่จะใช้งานข้อยกเว้นจะเกิดขึ้นเมื่อโปรแกรมใช้ sNaN ในการคำนวณทางคณิตศาสตร์ โปรแกรมจะไม่สร้าง sNaN โดยไม่ได้ตั้งใจ ไม่มีการดำเนินการตามปกติที่สร้าง sNaNs พวกเขาถูกสร้างขึ้นโดยเฉพาะเพื่อจุดประสงค์ในการมี NaN การส่งสัญญาณไม่ใช่ผลจากการคำนวณ
Eric Postpischil

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

@wrdieter ขอบคุณแล้วความแตกต่างเพียงอย่างเดียวคือการสร้างความโกรธเคืองหรือไม่
JalalJaberi

@EricPostpischil ขอบคุณที่สนใจคำถามชิ้นที่สอง
JalalJaberi

@JalalJaberi ใช่ข้อยกเว้นคือความแตกต่างที่สำคัญ
wrdieter

43

qNaNs และ sNaNs มีลักษณะอย่างไรในการทดลอง?

ก่อนอื่นเรามาเรียนรู้วิธีระบุว่าเรามี sNaN หรือ qNaN

ฉันจะใช้ C ++ ในคำตอบนี้แทน C เพราะให้ความสะดวกstd::numeric_limits::quiet_NaNและstd::numeric_limits::signaling_NaNหาไม่ได้ใน C สะดวก

อย่างไรก็ตามฉันไม่พบฟังก์ชันที่จะจำแนกว่า NaN เป็น sNaN หรือ qNaN ดังนั้นลองพิมพ์ไบต์ดิบของ NaN:

main.cpp

#include <cassert>
#include <cstring>
#include <cmath> // nanf, isnan
#include <iostream>
#include <limits> // std::numeric_limits

#pragma STDC FENV_ACCESS ON

void print_float(float f) {
    std::uint32_t i;
    std::memcpy(&i, &f, sizeof f);
    std::cout << std::hex << i << std::endl;
}

int main() {
    static_assert(std::numeric_limits<float>::has_quiet_NaN, "");
    static_assert(std::numeric_limits<float>::has_signaling_NaN, "");
    static_assert(std::numeric_limits<float>::has_infinity, "");

    // Generate them.
    float qnan = std::numeric_limits<float>::quiet_NaN();
    float snan = std::numeric_limits<float>::signaling_NaN();
    float inf = std::numeric_limits<float>::infinity();
    float nan0 = std::nanf("0");
    float nan1 = std::nanf("1");
    float nan2 = std::nanf("2");
    float div_0_0 = 0.0f / 0.0f;
    float sqrt_negative = std::sqrt(-1.0f);

    // Print their bytes.
    std::cout << "qnan "; print_float(qnan);
    std::cout << "snan "; print_float(snan);
    std::cout << " inf "; print_float(inf);
    std::cout << "-inf "; print_float(-inf);
    std::cout << "nan0 "; print_float(nan0);
    std::cout << "nan1 "; print_float(nan1);
    std::cout << "nan2 "; print_float(nan2);
    std::cout << " 0/0 "; print_float(div_0_0);
    std::cout << "sqrt "; print_float(sqrt_negative);

    // Assert if they are NaN or not.
    assert(std::isnan(qnan));
    assert(std::isnan(snan));
    assert(!std::isnan(inf));
    assert(!std::isnan(-inf));
    assert(std::isnan(nan0));
    assert(std::isnan(nan1));
    assert(std::isnan(nan2));
    assert(std::isnan(div_0_0));
    assert(std::isnan(sqrt_negative));
}

รวบรวมและเรียกใช้:

g++ -ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

เอาต์พุตบนเครื่อง x86_64 ของฉัน:

qnan 7fc00000
snan 7fa00000
 inf 7f800000
-inf ff800000
nan0 7fc00000
nan1 7fc00001
nan2 7fc00002
 0/0 ffc00000
sqrt ffc00000

นอกจากนี้เรายังสามารถรันโปรแกรมบน aarch64 ด้วยโหมดผู้ใช้ QEMU:

aarch64-linux-gnu-g++ -ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
qemu-aarch64 -L /usr/aarch64-linux-gnu/ main.out

และทำให้เกิดเอาต์พุตที่เหมือนกันโดยบอกว่าหลาย arch ใช้ IEEE 754 อย่างใกล้ชิด

ณ จุดนี้หากคุณไม่คุ้นเคยกับโครงสร้างของตัวเลขจุดลอยตัวของ IEEE 754 ลองดูที่: เลขทศนิยมตำแหน่งย่อยปกติคืออะไร?

ในไบนารีค่าบางค่าข้างต้นคือ:

     31
     |
     | 30    23 22                    0
     | |      | |                     |
-----+-+------+-+---------------------+
qnan 0 11111111 10000000000000000000000
snan 0 11111111 01000000000000000000000
 inf 0 11111111 00000000000000000000000
-inf 1 11111111 00000000000000000000000
-----+-+------+-+---------------------+
     | |      | |                     |
     | +------+ +---------------------+
     |    |               |
     |    v               v
     | exponent        fraction
     |
     v
     sign

จากการทดลองนี้เราสังเกตว่า:

  • qNaN และ sNaN ดูเหมือนจะแตกต่างกันโดยบิต 22: 1 หมายถึงเงียบและ 0 หมายถึงการส่งสัญญาณ

  • infinities ก็ค่อนข้างใกล้เคียงกับเลขชี้กำลัง == 0xFF แต่มีเศษส่วน == 0

    ด้วยเหตุนี้ NaN จึงต้องตั้งค่าบิต 21 ถึง 1 มิฉะนั้นจะไม่สามารถแยก sNaN ออกจากอินฟินิตี้ที่เป็นบวกได้!

  • nanf() สร้าง NaN ที่แตกต่างกันจำนวนมากดังนั้นจึงต้องมีการเข้ารหัสที่เป็นไปได้หลายรายการ:

    7fc00000
    7fc00001
    7fc00002
    

    เนื่องจากnan0เป็นเช่นเดียวกับstd::numeric_limits<float>::quiet_NaN()เราจึงสรุปได้ว่าพวกมันล้วนเป็น NaN ที่เงียบสงบต่างกัน

    C11 N1570 ร่างมาตรฐานยืนยันว่าnanf()สร้างแก่นแก้วที่เงียบสงบเพราะnanfส่งต่อไปstrtodและ 7.22.1.3 "การ strtod, strtof และฟังก์ชั่น strtold" พูดว่า:

    ลำดับอักขระ NAN หรือ NAN (ตัวเลือกลำดับ n-char) ถูกตีความว่าเป็น NaN แบบเงียบหากได้รับการสนับสนุนในประเภทการส่งคืนอื่นเช่นส่วนลำดับหัวเรื่องที่ไม่มีรูปแบบที่คาดไว้ ความหมายของลำดับ n-char คือการกำหนดการนำไปใช้งาน 293)

ดูสิ่งนี้ด้วย:

qNaNs และ sNaNs มีลักษณะอย่างไรในคู่มือ?

IEEE 754 2008แนะนำว่า (จำเป็นต้องทำหรือไม่บังคับ?):

  • อะไรก็ตามที่มีเลขชี้กำลัง == 0xFF และเศษส่วน! = 0 คือ NaN
  • และบิตเศษส่วนสูงสุดทำให้ qNaN แตกต่างจาก sNaN

แต่ดูเหมือนจะไม่ได้บอกว่าบิตใดที่ต้องการเพื่อแยกความแตกต่างของอินฟินิตี้จาก NaN

6.2.1 "การเข้ารหัส NaN ในรูปแบบไบนารี" กล่าวว่า:

อนุประโยคเพิ่มเติมระบุการเข้ารหัสของ NaN เป็นสตริงบิตเมื่อเป็นผลลัพธ์ของการดำเนินการ เมื่อเข้ารหัส NaN ทั้งหมดจะมีบิตเครื่องหมายและรูปแบบของบิตที่จำเป็นในการระบุการเข้ารหัสเป็น NaN และกำหนดชนิด (sNaN เทียบกับ qNaN) บิตที่เหลือซึ่งอยู่ในฟิลด์นัยสำคัญต่อท้ายจะเข้ารหัสเพย์โหลดซึ่งอาจเป็นข้อมูลการวินิจฉัย (ดูด้านบน) 34

สตริงบิต NaN ไบนารีทั้งหมดมีบิตทั้งหมดของฟิลด์เลขชี้กำลังเอนเอียง E ตั้งค่าเป็น 1 (ดู 3.4) สตริงบิต NaN ที่เงียบควรเข้ารหัสด้วยบิตแรก (d1) ของฟิลด์นัยสำคัญต่อท้าย T เป็น 1 สตริงบิตสัญญาณ NaN ควรเข้ารหัสด้วยบิตแรกของฟิลด์นัยสำคัญต่อท้ายเป็น 0 หากบิตแรกของ ฟิลด์นัยสำคัญต่อท้ายคือ 0 บิตอื่น ๆ ของฟิลด์นัยสำคัญต่อท้ายจะต้องไม่ใช่ศูนย์เพื่อแยกความแตกต่างของ NaN จากอินฟินิตี้ ในการเข้ารหัสที่ต้องการอธิบายไว้ NaN การส่งสัญญาณจะเงียบลงโดยการตั้งค่า d1 เป็น 1 โดยปล่อยให้บิตที่เหลือของ T ไม่เปลี่ยนแปลง สำหรับรูปแบบไบนารีน้ำหนักบรรทุกจะถูกเข้ารหัสในฟิลด์นัยสำคัญรองท้าย p − 2 บิตที่มีนัยสำคัญน้อยที่สุด

คู่มือการใช้งาน Intel 64 และ IA-32 สถาปัตยกรรมซอฟต์แวร์ของนักพัฒนา - เล่ม 1 พื้นฐานสถาปัตยกรรม - 253665-056US กันยายน 2015 4.8.3.4 "แก่นแก้ว" ยืนยันว่า x86 ดังนี้ IEEE 754 โดยแยกความแตกต่างน่านและสนานโดยบิตส่วนสูงสุด:

สถาปัตยกรรม IA-32 กำหนด NaN สองคลาส ได้แก่ NaN ที่เงียบ (QNaNs) และ NaN การส่งสัญญาณ (SNaNs) QNaN คือ NaN ที่มีบิตเศษส่วนที่สำคัญที่สุดกำหนดไว้ SNaN คือ NaN ที่มีเศษส่วนที่สำคัญที่สุดชัดเจน

และคู่มืออ้างอิงสถาปัตยกรรม ARM - ARMv8 สำหรับโปรไฟล์สถาปัตยกรรม ARMv8-A - DDI 0487C.a A1.4.3 "รูปแบบจุดลอยตัวความแม่นยำเดียว":

fraction != 0: ค่านี้เป็น NaN และเป็น NaN ที่เงียบหรือ NaN การส่งสัญญาณ NaN ทั้งสองประเภทมีความแตกต่างกันด้วยบิตเศษส่วนที่สำคัญที่สุดบิต [22]:

  • bit[22] == 0: NaN คือสัญญาณ NaN บิตเครื่องหมายสามารถรับค่าใด ๆ ก็ได้และเศษส่วนบิตที่เหลือสามารถรับค่าใดก็ได้ยกเว้นเลขศูนย์ทั้งหมด
  • bit[22] == 1: NaN คือ NaN ที่เงียบสงบ บิตเครื่องหมายและเศษส่วนที่เหลือสามารถรับค่าใดก็ได้

qNanS และ sNaNs สร้างขึ้นได้อย่างไร

ความแตกต่างที่สำคัญอย่างหนึ่งระหว่าง qNaNs และ sNaNs คือ:

  • qNaN ถูกสร้างขึ้นโดยการคำนวณทางคณิตศาสตร์ในตัว (ซอฟต์แวร์หรือฮาร์ดแวร์) ปกติที่มีค่าแปลก ๆ
  • sNaN ไม่เคยถูกสร้างขึ้นโดยการดำเนินการในตัวมันสามารถเพิ่มได้อย่างชัดเจนโดยโปรแกรมเมอร์เช่นกับ std::numeric_limits::signaling_NaN

ฉันไม่พบคำพูด IEEE 754 หรือ C11 ที่ชัดเจนสำหรับสิ่งนั้น แต่ฉันไม่พบการดำเนินการในตัวที่สร้าง sNaNs ;-)

คู่มือ Intel ระบุหลักการนี้ไว้อย่างชัดเจนอย่างไรก็ตามที่ 4.8.3.4 "NaNs":

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

สิ่งนี้สามารถเห็นได้จากตัวอย่างของเราโดยที่ทั้งสอง:

float div_0_0 = 0.0f / 0.0f;
float sqrt_negative = std::sqrt(-1.0f);

ผลิตบิตเดียวกันกับstd::numeric_limits<float>::quiet_NaN().

การดำเนินการทั้งสองคอมไพล์เป็นคำสั่งแอสเซมบลี x86 เดียวที่สร้าง qNaN โดยตรงในฮาร์ดแวร์ (สิ่งที่ต้องทำยืนยันกับ GDB)

qNaNs และ sNaNs ต่างกันอย่างไร

ตอนนี้เรารู้แล้วว่า qNaN และ sNaN มีลักษณะอย่างไรและจะจัดการอย่างไรในที่สุดเราก็พร้อมที่จะลองและทำให้ sNaN ทำในสิ่งที่พวกเขาทำและระเบิดบางโปรแกรม!

ดังนั้นโดยไม่ต้องกังวลใจเพิ่มเติม:

blow_up.cpp

#include <cassert>
#include <cfenv>
#include <cmath> // isnan
#include <iostream>
#include <limits> // std::numeric_limits
#include <unistd.h>

#pragma STDC FENV_ACCESS ON

int main() {
    float snan = std::numeric_limits<float>::signaling_NaN();
    float qnan = std::numeric_limits<float>::quiet_NaN();
    float f;

    // No exceptions.
    assert(std::fetestexcept(FE_ALL_EXCEPT) == 0);

    // Still no exceptions because qNaN.
    f = qnan + 1.0f;
    assert(std::isnan(f));
    if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID)
        std::cout << "FE_ALL_EXCEPT qnan + 1.0f" << std::endl;

    // Now we can get an exception because sNaN, but signals are disabled.
    f = snan + 1.0f;
    assert(std::isnan(f));
    if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID)
        std::cout << "FE_ALL_EXCEPT snan + 1.0f" << std::endl;
    feclearexcept(FE_ALL_EXCEPT);

    // And now we enable signals and blow up with SIGFPE! >:-)
    feenableexcept(FE_INVALID);
    f = qnan + 1.0f;
    std::cout << "feenableexcept qnan + 1.0f" << std::endl;
    f = snan + 1.0f;
    std::cout << "feenableexcept snan + 1.0f" << std::endl;
}

รวบรวมเรียกใช้และรับสถานะการออก:

g++ -ggdb3 -O0 -Wall -Wextra -pthread -std=c++11 -pedantic-errors -o blow_up.out blow_up.cpp -lm -lrt
./blow_up.out
echo $?

เอาท์พุต:

FE_ALL_EXCEPT snan + 1.0f
feenableexcept qnan + 1.0f
Floating point exception (core dumped)
136

โปรดทราบว่าพฤติกรรมนี้เกิดขึ้นกับ-O0ใน GCC 8.2 เท่านั้น: ด้วย-O3GCC จะคำนวณล่วงหน้าและเพิ่มประสิทธิภาพการดำเนินการ sNaN ทั้งหมดของเราทันที! ฉันไม่แน่ใจว่ามีวิธีป้องกันที่เป็นไปตามมาตรฐานหรือไม่

ดังนั้นเราจึงสรุปได้จากตัวอย่างนี้ว่า:

  • snan + 1.0สาเหตุFE_INVALIDแต่qnan + 1.0ไม่ได้

  • Linux จะสร้างสัญญาณหากเปิดใช้งานด้วยfeenableexeptไฟล์.

    นี่เป็นส่วนขยาย glibc ฉันไม่สามารถหาวิธีทำได้ในมาตรฐานใด ๆ

เมื่อสัญญาณเกิดขึ้นเนื่องจากฮาร์ดแวร์ของ CPU ทำให้เกิดข้อยกเว้นซึ่งเคอร์เนล Linux จัดการและแจ้งแอปพลิเคชันผ่านสัญญาณ

ผลลัพธ์คือทุบตีพิมพ์Floating point exception (core dumped)ออกมาและสถานะการออกคือ136ซึ่งสอดคล้องกับสัญญาณ136 - 128 == 8ซึ่งเป็นไปตาม:

man 7 signal

คือSIGFPE.

สังเกตว่าSIGFPEเป็นสัญญาณเดียวกับที่เราได้รับหากเราพยายามหารจำนวนเต็มด้วย 0:

int main() {
    int i = 1 / 0;
}

แม้ว่าสำหรับจำนวนเต็ม:

  • การหารอะไรด้วยศูนย์จะทำให้สัญญาณเพิ่มขึ้นเนื่องจากไม่มีการแทนค่าอินฟินิตี้ในจำนวนเต็ม
  • สัญญาณจะเกิดขึ้นตามค่าเริ่มต้นโดยไม่จำเป็นต้องใช้ feenableexcept

วิธีจัดการ SIGFPE

หากคุณเพิ่งสร้างตัวจัดการที่ส่งคืนตามปกติมันจะนำไปสู่การวนซ้ำที่ไม่มีที่สิ้นสุดเพราะหลังจากที่ตัวจัดการกลับมาการหารจะเกิดขึ้นอีกครั้ง! สิ่งนี้สามารถตรวจสอบได้ด้วย GDB

วิธีเดียวคือใช้setjmpและlongjmpกระโดดไปที่อื่นดังที่แสดงไว้ที่: C จัดการสัญญาณ SIGFPE และดำเนินการต่อ

แอปพลิเคชัน sNaN ในโลกแห่งความเป็นจริงมีอะไรบ้าง

พูดตามตรงว่าฉันยังไม่เข้าใจกรณีการใช้งานที่มีประโยชน์อย่างยิ่งสำหรับ sNaN ซึ่งถูกถามที่: ประโยชน์ของการส่งสัญญาณ NaN?

sNaN รู้สึกไร้ประโยชน์อย่างยิ่งเพราะเราสามารถตรวจพบการดำเนินการที่ไม่ถูกต้องเริ่มต้น ( 0.0f/0.0f) ที่สร้าง qNaN ด้วยfeenableexcept: ดูเหมือนว่าsnanจะทำให้เกิดข้อผิดพลาดสำหรับการดำเนินการเพิ่มเติมซึ่งqnanไม่ได้เพิ่มขึ้นเช่น ( qnan + 1.0f)

เช่น:

main.c

#define _GNU_SOURCE
#include <fenv.h>
#include <stdio.h>

int main(int argc, char **argv) {
    (void)argv;
    float f0 = 0.0;

    if (argc == 1) {
        feenableexcept(FE_INVALID);
    }
    float f1 = 0.0 / f0;
    printf("f1 %f\n", f1);

    feenableexcept(FE_INVALID);
    float f2 = f1 + 1.0;
    printf("f2 %f\n", f2);
}

รวบรวม:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -lm

แล้ว:

./main.out

ให้:

Floating point exception (core dumped)

และ:

./main.out  1

ให้:

f1 -nan
f2 -nan

ดูเพิ่มเติม: วิธีติดตาม NaN ใน C ++

ธงสัญญาณคืออะไรและมีการจัดการอย่างไร?

ทุกอย่างถูกนำไปใช้ในฮาร์ดแวร์ของ CPU

แฟล็กอาศัยอยู่ในรีจิสเตอร์บางตัวและบิตที่ระบุว่าควรเพิ่มข้อยกเว้น / สัญญาณ

การลงทะเบียนเหล่านั้นสามารถเข้าถึงได้จาก userlandจาก archs ส่วนใหญ่

โค้ด glibc 2.29 ส่วนนี้เข้าใจง่ายมาก!

ตัวอย่างเช่นfetestexceptใช้สำหรับ x86_86 ที่sysdeps / x86_64 / fpu / ftestexcept.c :

#include <fenv.h>

int
fetestexcept (int excepts)
{
  int temp;
  unsigned int mxscr;

  /* Get current exceptions.  */
  __asm__ ("fnstsw %0\n"
       "stmxcsr %1" : "=m" (*&temp), "=m" (*&mxscr));

  return (temp | mxscr) & excepts & FE_ALL_EXCEPT;
}
libm_hidden_def (fetestexcept)

ดังนั้นเราจึงเห็นได้ทันทีว่าคำแนะนำในการใช้งานนั้นstmxcsrย่อมาจาก "Store MXCSR Register State"

และfeenableexceptถูกนำไปใช้ที่sysdeps / x86_64 / fpu / feenablxcpt.c :

#include <fenv.h>

int
feenableexcept (int excepts)
{
  unsigned short int new_exc, old_exc;
  unsigned int new;

  excepts &= FE_ALL_EXCEPT;

  /* Get the current control word of the x87 FPU.  */
  __asm__ ("fstcw %0" : "=m" (*&new_exc));

  old_exc = (~new_exc) & FE_ALL_EXCEPT;

  new_exc &= ~excepts;
  __asm__ ("fldcw %0" : : "m" (*&new_exc));

  /* And now the same for the SSE MXCSR register.  */
  __asm__ ("stmxcsr %0" : "=m" (*&new));

  /* The SSE exception masks are shifted by 7 bits.  */
  new &= ~(excepts << 7);
  __asm__ ("ldmxcsr %0" : : "m" (*&new));

  return old_exc;
}

มาตรฐาน C พูดเกี่ยวกับ qNaN vs sNaN อย่างไร

ร่างมาตรฐาน C11 N1570อย่างชัดเจนกล่าวว่ามาตรฐานไม่แตกต่างระหว่างพวกเขาที่ F.2.1 "อนันต์, ศูนย์มีเครื่องหมายและแก่นแก้ว":

1 ข้อกำหนดนี้ไม่ได้กำหนดลักษณะการทำงานของการส่งสัญญาณ NaN โดยทั่วไปใช้คำว่า NaN เพื่อแสดงถึง NaN ที่เงียบสงบ มาโคร NAN และ INFINITY และฟังก์ชัน nan ในการ<math.h>กำหนดสำหรับ IEC 60559 NaNs และ infinities

ทดสอบใน Ubuntu 18.10, GCC 8.2 GitHub อัปสตรีม:


en.wikipedia.org/wiki/IEEE_754#Interchange_formatsชี้ให้เห็นว่า IEEE-754 เป็นเพียงการแนะนำว่า 0 สำหรับการส่งสัญญาณ NaN เป็นทางเลือกในการใช้งานที่ดีเพื่อให้ NaN เงียบโดยไม่ต้องเสี่ยงกับการทำให้เป็นอินฟินิตี้ (นัยสำคัญ = 0) เห็นได้ชัดว่ามันไม่ได้มาตรฐานแม้ว่าจะเป็นสิ่งที่ x86 ทำก็ตาม (และความจริงที่ว่ามันเป็น MSB ของนัยสำคัญที่กำหนด qNaN เทียบกับ sNaN เป็นมาตรฐาน) en.wikipedia.org/wiki/Single-precision_floating-point_formatกล่าวว่า x86 และ ARM เหมือนกัน แต่ PA-RISC เป็นตัวเลือกที่ตรงกันข้าม
Peter Cordes

@PeterCordes ใช่ฉันไม่แน่ใจว่า "ควร" == "ต้อง" หรือ "เป็นที่ต้องการ" ใน IEEE 754 20at "สตริงบิตสัญญาณ NaN ควรเข้ารหัสด้วยบิตแรกของฟิลด์นัยสำคัญต่อท้ายเป็น 0"
Ciro Santilli 郝海东冠状病六四事件法轮功

re: แต่ดูเหมือนจะไม่ระบุว่าควรใช้บิตใดเพื่อแยกความแตกต่างของอินฟินิตี้จาก NaN คุณเขียนว่าเหมือนที่คุณคาดไว้ว่าจะมีบิตเฉพาะที่มาตรฐานแนะนำให้ตั้งค่าเพื่อแยกความแตกต่างของ sNaN จากอินฟินิตี้ IDK ทำไมคุณถึงคาดหวังว่าจะมีบิตเช่นนี้ ทางเลือกที่ไม่ใช่ศูนย์ก็ใช้ได้ เพียงแค่เลือกสิ่งที่ระบุในภายหลังว่า sNaN มาจากไหน IDK ฟังดูแปลก ๆ และความประทับใจแรกของฉันเมื่ออ่านก็คือคุณบอกว่าหน้าเว็บไม่ได้อธิบายถึงสิ่งที่แตกต่างจาก NaN ในการเข้ารหัส (นัยสำคัญทั้งหมดเป็นศูนย์)
Peter Cordes

ก่อนปี 2008 IEEE 754 กล่าวว่าเป็นบิตการส่งสัญญาณ / เงียบ (บิต 22) แต่ไม่ได้ระบุค่าใด โปรเซสเซอร์ส่วนใหญ่มาบรรจบกันที่ 1 = เงียบซึ่งเป็นส่วนหนึ่งของมาตรฐานในฉบับปี 2008 ข้อความระบุว่า "ควร" มากกว่า "ต้อง" เพื่อหลีกเลี่ยงการใช้งานแบบเก่าที่ทำให้ตัวเลือกเดียวกันไม่เป็นไปตามข้อกำหนด โดยทั่วไปแล้ว "ควร" ในวิธีมาตรฐาน "ต้อง" เว้นแต่คุณจะมีเหตุผลที่น่าสนใจมาก (และควรมีเอกสารที่ดี) ในการไม่ปฏิบัติตาม
John Cowan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.