ตรวจสอบว่า double (หรือ float) เป็น NaN ใน C ++


368

มีฟังก์ชั่น isnan () หรือไม่?

ป.ล. : ฉันอยู่ในMinGW (ถ้านั่นสร้างความแตกต่าง)

ฉันได้นี้แก้ไขได้โดยใช้ isNaN () จาก<math.h>ที่ไม่ได้อยู่ใน<cmath>ที่ฉันได้รับ#includeไอเอ็นจีในตอนแรก


2
ฉันไม่บริสุทธิ์คุณสามารถทำได้อย่างสะดวก ใครบอกว่า C ++ ต้องการ IEEE754
David Heffernan


เพียงแค่ทราบการป้องกัน 1 ออนซ์ดีกว่าการรักษา 1 ปอนด์ กล่าวอีกนัยหนึ่งการป้องกัน 0.f / 0.f จากที่เคยถูกประหารชีวิตนั้นดีกว่าการตรวจสอบย้อนหลังnanในโค้ดของคุณ nanสามารถทำลายโปรแกรมของคุณได้มากหากได้รับอนุญาตให้เพิ่มจำนวนมันสามารถแนะนำการค้นหาบั๊กได้ยาก นี่เป็นเพราะnanพิษ (5 * nan= nan), nanไม่เท่ากับอะไรเลย ( nan! = nan), nanไม่ยิ่งใหญ่กว่าอะไร ( nan!> 0), nanไม่น้อยไปกว่าสิ่งใด ( nan! <0)
bobobobo

1
@bobobobo: นั่นเป็นคุณสมบัติที่ช่วยให้ตรวจสอบข้อผิดพลาดจากส่วนกลาง เช่นเดียวกับข้อยกเว้นเทียบกับค่าส่งคืน
Ben Voigt

2
ทำไม <cmath> ไม่มี isnan () มันอยู่ใน std ::
frankliuao

คำตอบ:


349

ตามมาตรฐาน IEEE ค่า NaN มีคุณสมบัติแปลก ๆ ที่การเปรียบเทียบที่เกี่ยวข้องกับพวกเขาเป็นเท็จเสมอ นั่นคือสำหรับ f ลอยf != fจะเป็นจริงเฉพาะถ้า f เป็นน่าน

โปรดทราบว่าเนื่องจากความคิดเห็นบางส่วนด้านล่างชี้ให้เห็นว่าคอมไพเลอร์บางคนไม่เคารพสิ่งนี้เมื่อปรับรหัสให้เหมาะสม

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


4
คอมไพเลอร์ได้ดีกว่าอย่าลบสิ่งนี้หากทำงานในโหมด IEEE ตรวจสอบเอกสารสำหรับผู้แปลของคุณแน่นอน ...
dmckee --- อดีตผู้ดูแล kitten

38
-1 ใช้ได้กับทฤษฎีเท่านั้นไม่ใช่ในทางปฏิบัติ: คอมไพเลอร์เช่นสกรู g ++ (พร้อมกับ - fastmath) วิธีทั่วไปเท่านั้นจนถึง c ++ 0x คือทดสอบ bitpattern
ไชโยและ hth - Alf

66
@Alf: เอกสารประกอบสำหรับ-ffast-mathตัวเลือกระบุอย่างชัดเจนว่าสามารถทำให้เกิดผลลัพธ์ที่ไม่ถูกต้องสำหรับโปรแกรมซึ่งขึ้นอยู่กับการใช้งานที่ถูกต้องหากกฎหรือข้อกำหนดมาตรฐาน IEEE หรือ ISO สำหรับฟังก์ชันคณิตศาสตร์ หากไม่มีการเปิดใช้งานตัวเลือกนั้นการใช้x != xเป็นวิธีทดสอบ NaN ที่ถูกต้องและพกพาได้อย่างสมบูรณ์แบบ
Adam Rosenfield

7
@ อดัม: เอกสารอธิบายอย่างเปิดเผยว่ามันไม่สอดคล้องใช่ และใช่ฉันเคยเจอเรื่องนี้มาก่อนแล้วพูดถึงเรื่องนี้กับ Gabriel Dos Reis มันใช้กันทั่วไปเพื่อปกป้องการออกแบบในการโต้แย้งแบบวงกลม (ฉันไม่รู้ว่าคุณตั้งใจจะเชื่อมโยงกับสิ่งนั้นหรือไม่ ข้อสรุปของคุณx != xถูกต้องโดยไม่มีตัวเลือกนั้นไม่เป็นไปตามหลักเหตุผล มันอาจเป็นจริงสำหรับ g ++ รุ่นใดรุ่นหนึ่งโดยเฉพาะหรือไม่ อย่างไรก็ตามโดยทั่วไปคุณไม่มีวิธีรับประกันว่าจะไม่ใช้ตัวเลือก fastmath
ไชโยและ hth - Alf

7
@Alf: ไม่ฉันไม่ได้ตระหนักถึงการสนทนาของคุณกับ Gabriel Dos Reis Steve Jessop ได้จุดที่ดีในคำถามอื่น ๆ เกี่ยวกับการสมมติการเป็นตัวแทนของ IEEE หากคุณถือว่า IEEE 754 และคอมไพเลอร์ทำงานในลักษณะที่สอดคล้อง (เช่นไม่มี-ffast-mathตัวเลือก) แสดงว่าx != xเป็นโซลูชันที่ถูกต้องและพกพาได้ คุณสามารถทดสอบ-ffast-mathด้วยการทดสอบ__FAST_MATH__แมโครและสลับไปใช้การใช้งานที่แตกต่างกันในกรณีนั้น (เช่นใช้ยูเนี่ยนและการบิดสองบิตเล็กน้อย)
Adam Rosenfield

220

ไม่มีisnan()ฟังก์ชั่นที่มีอยู่ใน C ++ Standard Library ปัจจุบัน มันถูกนำมาใช้ในC99และกำหนดเป็นแมโครไม่ใช่ฟังก์ชั่น องค์ประกอบของไลบรารีมาตรฐานที่กำหนดโดย C99 ไม่ได้เป็นส่วนหนึ่งของ ISO / IEC 14882: 1998 ปัจจุบันมาตรฐาน C ++ ไม่ว่าจะเป็นการปรับปรุง ISO / IEC 14882: 2003

ในปี 2005 รายงานทางเทคนิค 1 ถูกเสนอ TR1 นำความเข้ากันได้กับ C99 ถึง C ++ แม้ว่าจะไม่ได้รับการรับรองอย่างเป็นทางการว่าเป็นมาตรฐาน C ++ การใช้งานหลายอย่าง ( GCC 4.0+หรือVisual C ++ 9.0+ C ++ จะให้คุณสมบัติ TR1 ทั้งหมดหรือเพียงบางส่วนเท่านั้น (Visual C ++ 9.0 ไม่มีฟังก์ชันคณิตศาสตร์ C99) .

หากมี TR1 ให้ใช้งานcmathรวมถึงองค์ประกอบ C99 เช่นisnan(), isfinite()ฯลฯ แต่มีการกำหนดเป็นฟังก์ชันไม่ใช่มาโครมักอยู่ในstd::tr1::เนมสเปซแม้ว่าจะมีการใช้งานหลายอย่าง (เช่น GCC 4+ บน Linux หรือใน XCode บน Mac OS X 10.5+) โดยตรงไปยังstd::เพื่อstd::isnanกำหนดไว้อย่างดี

นอกจากนี้การใช้งานบางอย่างของ C ++ ยังทำให้isnan()แมโครC99 พร้อมใช้งานสำหรับ C ++ (รวมถึงcmathหรือmath.h) สิ่งที่อาจทำให้เกิดความสับสนมากขึ้นและนักพัฒนาอาจคิดว่ามันเป็นพฤติกรรมมาตรฐาน

หมายเหตุเกี่ยวกับ Viusal C ++ ดังกล่าวข้างต้นก็ไม่ได้ให้std::isnanค่าstd::tr1::isnanแต่มันยังมีฟังก์ชั่นการขยายกำหนดเป็น_isnan()ที่ได้รับมีตั้งแต่Visual C ++ 6.0

บน XCode มีความสนุกสนานมากยิ่งขึ้น เป็นที่กล่าวถึง GCC 4+ std::isnanกำหนด สำหรับคอมไพเลอร์รุ่นเก่าและ XCode ในรูปแบบไลบรารีดูเหมือนว่า (นี่คือการสนทนาที่เกี่ยวข้อง ) ไม่มีโอกาสที่จะตรวจสอบตัวเอง) มีการกำหนดสองหน้าที่__inline_isnand()บน Intel และ__isnand()Power PC


21
ทุกคนต้องการฟังก์ชั่นเหล่านี้เช่น isNan หรือ isInfinity ทำไมคนที่รับผิดชอบไม่เพียง แต่รวมอยู่ในมาตรฐานของพวกเขา ???? - ฉันจะพยายามหาวิธีที่จะได้รับค่าใช้จ่ายและลงคะแนนให้กับสิ่งนี้ อย่างจริงจัง.
shuhalo

8
@shuhalo รับผิดชอบไหม?
Tomáš Zato - Reinstate Monica

11
คำตอบนี้ควรได้รับการอัปเดตเนื่องจากstd::isnanขณะนี้เป็นส่วนหนึ่งของมาตรฐาน C ++ 11 และการสนับสนุนกระจายออกไป std :: isnan ถูกนำมาใช้ใน Visual Studio โดยเริ่มจาก Visual Studio 2013 บางที @shuhalo มีหน้าที่ :-)
2559

170

วิธีแก้ปัญหาแรก: หากคุณใช้ C ++ 11

นับตั้งแต่มีการถามเรื่องนี้มีการพัฒนาใหม่ ๆ เล็กน้อย: สิ่งสำคัญคือต้องรู้ว่าstd::isnan()เป็นส่วนหนึ่งของ C ++ 11

สรุป

กำหนดไว้ในส่วนหัว <cmath>

bool isnan( float arg ); (since C++11)
bool isnan( double arg ); (since C++11)
bool isnan( long double arg ); (since C++11)

พิจารณาว่าจำนวนจุดลอยตัวที่กำหนดให้ไม่ใช่ -a-number ( NaN)

พารามิเตอร์

arg: ค่าจุดลอยตัว

ค่าส่งคืน

trueถ้าหาเรื่องคือNaN, falseมิฉะนั้น

การอ้างอิง

http://en.cppreference.com/w/cpp/numeric/math/isnan

โปรดทราบว่าสิ่งนี้ไม่สามารถใช้ได้กับ -fast-math หากคุณใช้ g ++ โปรดดูคำแนะนำอื่น ๆ ที่ด้านล่าง


โซลูชันอื่น ๆ : หากคุณใช้เครื่องมือที่ไม่รองรับ C ++ 11

สำหรับ C99 ใน C สิ่งนี้ถูกนำมาใช้เป็นมาโครisnan(c)ที่ส่งกลับค่า int ประเภทของการxลอยตัวจะเป็นสองเท่าหรือสองเท่า

isnan()ผู้ผลิตต่างๆอาจหรือไม่อาจรวมหรือไม่ได้ฟังก์ชั่น

วิธีแบบพกพาเพื่อที่จะตรวจสอบNaNคือการใช้ IEEE 754 ทรัพย์สินที่NaNไม่เท่ากับตัวเอง: คือx == xจะเป็นเท็จสำหรับความเป็นอยู่xNaN

อย่างไรก็ตามตัวเลือกสุดท้ายอาจใช้ไม่ได้กับคอมไพเลอร์และการตั้งค่าบางอย่าง (โดยเฉพาะการตั้งค่าการเพิ่มประสิทธิภาพ) ดังนั้นในที่สุดคุณสามารถตรวจสอบรูปแบบบิตได้เสมอ ...


8
สมควรที่จะได้รับคำตอบที่แน่นอนและสมควรได้รับ upvotes มากขึ้น ขอบคุณสำหรับเคล็ดลับ
LBes

3
−1 std::isnanยังคงเป็นคำแนะนำที่ไม่ดี ณ เดือนกุมภาพันธ์ 2017 เนื่องจากมันไม่สามารถทำงานได้กับการปรับจุดลอยตัวของ g ++
ไชโยและ hth - Alf

@ Cheersandhth. -Alf: ตัวเลือกนี้สอดคล้องกับ IEEE หรือไม่ แก้ไขคำตอบแล้ว
BlueTrin

@BlueTrin: ทั้งสองx != xและisnanจำเป็นต้องทำงานเพื่อให้สอดคล้องกับมาตรฐาน IEEE 754 เกี่ยวกับหลังมาตรฐาน IEEE 754-2008 ระบุว่า“ การใช้งานต้องจัดให้มีการดำเนินการที่ไม่คำนวณต่อไปนี้สำหรับรูปแบบคณิตศาสตร์ที่รองรับทั้งหมด” และ“ isNaN (x) เป็นจริงถ้า x เป็น NaN เท่านั้น” สำหรับการตรวจสอบความสอดคล้องตามมาตรฐานที่ต้องการis754version1985()และis754version2008()ที่ C ++ เสนอแทนstd::numeric_limits<Fp>::is_iec559()(IEC 559 เป็นมาตรฐานเดียวกัน) น่าเสียดายที่-ffast-mathการเพิ่มประสิทธิภาพเช่น g ++ อ้างถึงความสอดคล้อง แต่ไม่สอดคล้องกัน
ไชโยและ hth - Alf

1
คำเตือน: isnan (x) ไม่ทำงานกับตัวเลือก -ffinite-math-only ใน gcc และ clang
A Fog

82

นอกจากนี้ยังมีห้องสมุดส่วนหัวเท่านั้นที่มีอยู่ใน Boost ที่มีเครื่องมือที่เป็นระเบียบเพื่อจัดการกับประเภทข้อมูลจุดลอยตัว

#include <boost/math/special_functions/fpclassify.hpp>

คุณได้รับฟังก์ชั่นต่อไปนี้:

template <class T> bool isfinite(T z);
template <class T> bool isinf(T t);
template <class T> bool isnan(T t);
template <class T> bool isnormal(T t);

ถ้าคุณมีเวลาลองดูชุดเครื่องมือทางคณิตศาสตร์ทั้งหมดจาก Boost มันมีเครื่องมือที่มีประโยชน์มากมายและเติบโตอย่างรวดเร็ว

นอกจากนี้เมื่อต้องรับมือกับลอยและไม่ลอยจุดที่มันอาจจะเป็นความคิดที่ดีที่จะดูที่ตัวเลขการแปลง


1
ขอบคุณ! สิ่งที่ฉันกำลังมองหา
ดร. วัตสัน

มันถูกเพิ่มใน Boost 1.35 (ฉันเพิ่งพบว่าโปรแกรมของฉันไม่ได้คอมไพล์ในลินุกซ์ distro เก่า)
marcin

2
หากคุณรวบรวมกับตัวเลือก - เร็วคณิตศาสตร์แล้วฟังก์ชั่นนี้จะไม่ทำงานตามที่คาดไว้
Gaetano Mendola

43

มีสามวิธี "อย่างเป็นทางการ" คือ POSIX isnanแมโคร , C ++ 0x isnanฟังก์ชั่นแม่แบบหรือ Visual C ++ ฟังก์ชั่น_isnan

น่าเสียดายที่ค่อนข้างใช้การตรวจจับไม่ได้

และน่าเสียดายที่ไม่มีวิธีที่เชื่อถือได้ในการตรวจสอบว่าคุณมี IEEE 754 ที่เป็นตัวแทนของ NaN หรือไม่ ห้องสมุดมาตรฐานเสนอวิธีดังกล่าวอย่างเป็นทางการ ( numeric_limits<double>::is_iec559) แต่ในทางปฏิบัติคอมไพเลอร์เช่นสกรู g ++ ขึ้นไป

ในทางทฤษฎีเราสามารถใช้งานได้ง่ายx != xๆ แต่คอมไพเลอร์เช่น g ++ และ visual c ++ screw ขึ้นไป

ดังนั้นในที่สุดทดสอบสำหรับNaN bitpatternsโดยเฉพาะ(และหวังว่าจะมีการบังคับใช้ในบางจุด!) การแสดงเฉพาะเช่น IEEE 754


แก้ไข : เป็นตัวอย่างของ "คอมไพเลอร์เช่น g ++ …ไขที่ขึ้น" พิจารณา

#include <limits>
#include <assert.h>

void foo( double a, double b )
{
    assert( a != b );
}

int main()
{
    typedef std::numeric_limits<double> Info;
    double const nan1 = Info::quiet_NaN();
    double const nan2 = Info::quiet_NaN();
    foo( nan1, nan2 );
}

รวบรวมด้วย g ++ (TDM-2 mingw32) 4.4.1:

C: \ test> พิมพ์ "C: \ Program Files \ @commands \ gnuc.bat"
@rem -finput-charset = windows-1252
@ g ++ -O -pedantic -std = c ++ 98 - ทั้งหมด - เขียน - สตริง% * - ยาวไม่ยาว

C: \ test> gnuc x.cpp

C: \ test> a&& echo ใช้งานได้ ... || echo! ล้มเหลว
ทำงาน ...

C: \ test> gnuc x.cpp - เร็ว - คณิตศาสตร์

C: \ test> a&& echo ใช้งานได้ ... || echo! ล้มเหลว
การยืนยันไม่สำเร็จ: a! = b, ไฟล์ x.cpp, บรรทัด 6

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

C: \ test> _

4
@Alf: ตัวอย่างของคุณทำงานตามที่คาดไว้สำหรับฉันทั้ง Mac OS X และ Linux บน g ++ รุ่นต่างๆระหว่าง 4.0 และ 4.5 เอกสารสำหรับ-ffast-mathตัวเลือกบอกอย่างชัดเจนว่ามันอาจส่งผลให้เกิดการแสดงผลที่ไม่ถูกต้องสำหรับโปรแกรมซึ่งขึ้นอยู่กับการใช้งานที่ถูกต้องหากกฎ / ข้อกำหนดของ IEEE หรือ ISO สำหรับฟังก์ชันคณิตศาสตร์ หากไม่มีการเปิดใช้งานตัวเลือกนั้นการใช้x != xเป็นวิธีทดสอบ NaN ที่ถูกต้องและพกพาได้อย่างสมบูรณ์แบบ
Adam Rosenfield

6
@ อดัม: สิ่งที่คุณขาดไปก็คือมาตรฐาน C ++ นั้นไม่จำเป็นต้องใช้การแทน IEEE หรือคณิตศาสตร์สำหรับการลอยตัว เท่าที่หน้า man บอกคุณgcc -ffast-mathยังคงใช้งาน C ++ ที่สอดคล้องกัน (ถ้าสมมติว่ามันถูกnumeric_limits::is_iec559ต้องมันคือถึงแม้ว่า Alf แนะนำข้างต้นว่าไม่ได้): รหัส C ++ ที่อาศัย IEEE ไม่ใช่พกพา C ++ และไม่มีสิทธิ์ เพื่อคาดหวังการใช้งานเพื่อให้มัน
Steve Jessop

5
และขวาทดสอบอย่างรวดเร็ว Alf ใน GCC 4.3.4 และเป็นจริงกับis_iec559 -ffast-mathดังนั้นปัญหาที่นี่คือเอกสารของ GCC -ffast-mathเพียงบอกว่าไม่ใช่ IEEE / ISO สำหรับฟังก์ชันทางคณิตศาสตร์ในขณะที่พวกเขาควรจะบอกว่ามันไม่ใช่ C ++ เพราะการใช้งานของnumeric_limitsมันเป็น borked ฉันเดาว่า GCC ไม่สามารถบอกได้ตลอดเวลาว่ามีการกำหนดเทมเพลตหรือไม่ไม่ว่าแบ็กเอนด์ในที่สุดจะมีการทำโฟลทและไม่พยายาม IIRC มีปัญหาที่คล้ายกันในรายการบั๊กที่โดดเด่นสำหรับความสอดคล้อง C99 ของ GCC
Steve Jessop

1
@Alf, @Steve ฉันไม่รู้ว่ามาตรฐาน C ++ ไม่มีข้อมูลจำเพาะเกี่ยวกับค่าทศนิยม มันค่อนข้างน่าตกใจสำหรับฉัน ดูดีกว่าการจัดการ IEEE 754 และ NaN เป็นส่วนขยายเฉพาะแพลตฟอร์มแทนมาตรฐาน ไม่ใช่เหรอ และฉันสามารถคาดหวังชนิดใด ๆ ของ isnan () หรือ IEEE754 เพิ่มใน C ++ 0x?
Eonil

3
@Eonil: C ++ 0x ยังคงมีตัวอย่างเช่น "การแสดงค่าของประเภท point oating-point คือการนำไปใช้งาน" ทั้ง C และ C ++ มีจุดประสงค์เพื่อสนับสนุนการใช้งานบนเครื่องที่ไม่มีฮาร์ดแวร์แบบ floating-point และ IEEE 754 float ที่เหมาะสมสามารถทำการจำลองได้ช้ากว่าทางเลือกที่ถูกต้องตามสมควร ทฤษฏีคือคุณสามารถยืนยันได้is_iec559ถ้าคุณต้องการ IEEE ในทางปฏิบัติที่ดูเหมือนจะไม่ทำงานบน GCC C ++ 0x มีisnanฟังก์ชั่น แต่เนื่องจาก GCC ไม่ได้ใช้งานอย่างถูกต้องis_iec559ตอนนี้ฉันเดาว่ามันจะไม่อยู่ใน C ++ 0x เช่นกันและ-ffast-mathอาจแตกหักisnanได้
Steve Jessop

39

มี std :: isnan ถ้าคอมไพเลอร์ของคุณรองรับส่วนขยาย c99 แต่ฉันไม่แน่ใจว่า mingw ทำได้หรือไม่

นี่คือฟังก์ชั่นขนาดเล็กซึ่งควรทำงานหากคอมไพเลอร์ของคุณไม่มีฟังก์ชั่นมาตรฐาน:

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}

6
ทำไมไม่เพียงแค่ var! = var?
46490 Brian R. Bondy

8
เมื่อทำเช่นนั้นโอกาสของพวกเขาคือคอมไพเลอร์จะปรับการเปรียบเทียบให้ได้ผลจริงเสมอ
CTT

23
ไม่มีเลย คอมไพเลอร์ที่ทำสิ่งนั้นแตก คุณอาจบอกว่ามีโอกาสที่ไลบรารีมาตรฐานisnanส่งคืนผลลัพธ์ที่ผิด ความจริงทางเทคนิคคอมไพเลอร์อาจเป็นรถ แต่ในทางปฏิบัติไม่ใช่จะเกิดขึ้น var != varเช่นเดียวกับ มันทำงานได้เพราะนั่นคือวิธีการกำหนดค่าจุดลอยตัวของ IEEE
jalf

29
หากตั้งค่า -ffast-math ไว้ isnan () จะไม่สามารถส่งคืนผลลัพธ์ที่ถูกต้องสำหรับ gcc ของหลักสูตรการเพิ่มประสิทธิภาพนี้เป็นเอกสารที่เป็นมาตรฐาน IEEE หมดความหมาย ...
แมทธิวมานน์

หากตั้งค่า -ffast-math ไว้คอมไพเลอร์จะเป็นรถม้าชนิดเล็ก หรือหากตั้ง - -fast-math การเดิมพันทั้งหมดจะปิดและคุณไม่สามารถไว้วางใจ NaNs ได้
Adrian Ratnapala

25

คุณสามารถใช้numeric_limits<float>::quiet_NaN( )กำหนดไว้ในlimitsไลบรารีมาตรฐานเพื่อทดสอบด้วย doubleมีค่าคงที่แยกต่างหากกำหนดไว้สำหรับเป็น

#include <iostream>
#include <math.h>
#include <limits>

using namespace std;

int main( )
{
   cout << "The quiet NaN for type float is:  "
        << numeric_limits<float>::quiet_NaN( )
        << endl;

   float f_nan = numeric_limits<float>::quiet_NaN();

   if( isnan(f_nan) )
   {
       cout << "Float was Not a Number: " << f_nan << endl;
   }

   return 0;
}

ฉันไม่รู้ว่ามันใช้ได้กับทุกแพลตฟอร์มหรือไม่เพราะฉันทดสอบด้วย g ++ บน Linux เท่านั้น


2
ระวังแม้ว่า - ดูเหมือนว่าจะมีข้อบกพร่องใน numeric_limits ใน GCC เวอร์ชัน 3.2.3 เนื่องจากมันส่งคืน 0.0 สำหรับ quiet_NaN GCC รุ่นที่ใหม่กว่านั้นใช้ได้ในประสบการณ์ของฉัน
นาธานคิทเช่น

@ นาธาน: ดีที่รู้ ฉันใช้เวอร์ชั่น 4.3.2 ดังนั้นฉันก็เลยออกมาจากป่า
Bill the Lizard

18

คุณสามารถใช้isnan()ฟังก์ชั่น แต่คุณต้องรวมห้องสมุดคณิตศาสตร์ C

#include <cmath>

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

inline bool isnan(double x) {
    return x != x;
}

ฉันใช้ <cmath> และไม่มี isnan อยู่ในนั้น! บังเอิญผมพบว่ามีเป็นisnanใน <math.h>
Hasen

1
อย่างที่ฉันพูดนี่เป็นส่วนหนึ่งของ C99 เนื่องจาก C99 ไม่ได้เป็นส่วนหนึ่งของมาตรฐาน C ++ ปัจจุบันฉันจึงให้ทางเลือกอื่น แต่เนื่องจากมีแนวโน้มว่า isnan () จะรวมอยู่ในมาตรฐาน C ++ ที่กำลังจะมาถึงฉันจึงวางคำสั่ง #ifndef ไว้รอบ ๆ
raimue

12

รหัสต่อไปนี้ใช้นิยามของ NAN (ชุดเลขชี้กำลังทั้งหมดชุดบิตเศษส่วนอย่างน้อยหนึ่งชุด) และถือว่าขนาดของ (int) = ขนาดของ (ลอย) = 4 คุณสามารถค้นหา NAN ในวิกิพีเดียเพื่อดูรายละเอียด

bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }


ฉันเชื่อว่านี่จะทำงานบนแพลตฟอร์ม endian ขนาดใหญ่ ตัวอักษร0x7fffffffก็จะนั่งในความทรงff ff ff 7fจำเป็น valueมีการเรียงลำดับเหมือนกัน0x7f800000ดังนั้นการดำเนินการทั้งหมดจะเรียงกัน (ไม่มีการสลับไบต์) ฉันสนใจถ้ามีคนสามารถทดสอบสิ่งนี้บนแพลตฟอร์ม endian ขนาดใหญ่
ไบรอันว. วชิรแว็กเนอร์

0x7fff1234ยังเป็นน่าน ก็เช่นกัน0xffffffff
Steve Hollasch

12

การป้องกันน่าน

คำตอบของฉันคำถามนี้ไม่ได้ใช้การตรวจสอบย้อนหลัง nanใช้การตรวจสอบเชิงป้องกันสำหรับหน่วยงานของแบบฟอร์ม0.0/0.0แทน

#include <float.h>
float x=0.f ;             // I'm gonna divide by x!
if( !x )                  // Wait! Let me check if x is 0
  x = FLT_MIN ;           // oh, since x was 0, i'll just make it really small instead.
float y = 0.f / x ;       // whew, `nan` didn't appear.

nanผลจากการดำเนินงานหรือ 0.f/0.f เป็นตัวซวยที่แย่มากต่อเสถียรภาพของโค้ดที่จะต้องตรวจจับและป้องกันอย่างระมัดระวัง1 . คุณสมบัติของที่แตกต่างจากหมายเลขปกติ:0.0/0.0nannan

  • nanเป็นพิษ (5 * nan= nan)
  • nanไม่เท่ากับสิ่งใด ๆ แม้แต่ตัวเอง ( nan! = nan)
  • nanไม่ยิ่งใหญ่กว่าสิ่งใด ( nan!> 0)
  • nanไม่น้อยกว่าอะไรเลย ( nan! <0)

คุณสมบัติ 2 รายการสุดท้ายนั้นเป็นแบบตอบโต้แบบลอจิคัลและจะส่งผลให้เกิดพฤติกรรมแปลก ๆ ของรหัสที่อาศัยการเปรียบเทียบกับnanตัวเลข (คุณสมบัติที่ 3 เป็นคี่เกินไป แต่คุณอาจไม่เคยเห็นx != x ?รหัสของคุณเลย (เว้นแต่คุณกำลังตรวจสอบอยู่ สำหรับน่าน (เหลือทน)))

ในรหัสของฉันฉันสังเกตเห็นว่าnanค่านิยมมักจะสร้างข้อบกพร่องที่หายาก (โปรดสังเกตว่านี่ไม่ใช่กรณีของinfหรือ-inf(. -inf<0) ส่งกลับTRUE, (0 < inf) ส่งกลับ TRUE และแม้ ( -inf< inf) ส่งกลับ TRUE ดังนั้นในประสบการณ์ของฉันพฤติกรรมของรหัสมักจะยังคงเป็นที่ต้องการ)

จะทำอย่างไรภายใต้น่าน

สิ่งที่คุณต้องการให้เกิดขึ้นภายใต้0.0/0.0 ต้องจัดการเป็นกรณีพิเศษแต่สิ่งที่คุณต้องขึ้นอยู่กับจำนวนที่คุณคาดว่าจะออกมาจากรหัส

ในตัวอย่างด้านบนผลลัพธ์ของ ( 0.f/FLT_MIN) จะเป็น0พื้น คุณอาจต้องการ0.0/0.0สร้างHUGEแทน ดังนั้น,

float x=0.f, y=0.f, z;
if( !x && !y )    // 0.f/0.f case
  z = FLT_MAX ;   // biggest float possible
else
  z = y/x ;       // regular division.

ดังนั้นในข้างต้นถ้า x เป็น0.fเช่นinfนั้นจะส่งผลให้ (ซึ่งมีพฤติกรรมที่ดี / ไม่ทำลายตามที่กล่าวไว้ข้างต้นจริง)

โปรดจำไว้ว่าจำนวนเต็มหารด้วย 0 สาเหตุข้อยกเว้นรันไทม์ ดังนั้นคุณต้องตรวจสอบการหารจำนวนเต็มด้วย 0 เสมอเพราะการ0.0/0.0ประเมินอย่างเงียบ ๆnanไม่ได้หมายความว่าคุณขี้เกียจและไม่ตรวจสอบ0.0/0.0ก่อนที่มันจะเกิดขึ้น

1 การตรวจสอบหาnanผ่านx != xไม่น่าเชื่อถือในบางครั้ง ( x != xถูกถอดออกโดยคอมไพเลอร์เพิ่มประสิทธิภาพบางตัวที่ละเมิดมาตรฐาน IEEE โดยเฉพาะเมื่อ-ffast-mathเปิดใช้งานสวิตช์)


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

4
โปรดทราบว่า 0.0 / 0.0 ไม่ได้เป็นเพียงการดำเนินการที่อาจส่งผลให้ NaN รากที่สองของจำนวนลบส่งกลับ NaN โคไซน์ของ + อินฟินิตี้ส่งคืน NaN เช่นกัน acos การดำเนินงาน (x) โดยที่ x ไม่อยู่ในช่วง [0, pi] ยังสามารถส่งผลให้ NaN โดยสรุปจะต้องระมัดระวังเป็นพิเศษในการดูการดำเนินการที่อาจมีความเสี่ยงเหล่านี้ไม่เพียงแค่ 0.0 / 0.0
Boris Dalstein

เห็นด้วยอย่างยิ่งกับ Boris จากประสบการณ์ของฉัน NaN มักมาจากบางสิ่งบางอย่างเช่น sqrt (-1.302e-53) นั่นคือผลลัพธ์การคำนวณกลางที่ใกล้เคียงกับศูนย์จะถูกป้อนเข้าสู่ sqrt โดยไม่ตรวจสอบการปฏิเสธ
hans_meine

1
"การป้องกัน NaNs" หมายความว่าคุณต้องเข้าสู่การดำเนินการทางคณิตศาสตร์ขั้นพื้นฐานทั้งหมดไม่ใช่แค่การหาร คุณจะต้องระวัง∞ / ∞, 0 * ∞, ∞% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0, ท่ามกลางคนอื่น ๆ การเป็น "เชิงป้องกัน" ด้วยการดำเนินการทางคณิตศาสตร์ขั้นพื้นฐานดังกล่าวหมายความว่าคุณจะต้องฝึกฝนประสิทธิภาพของคุณอย่างสมบูรณ์
Steve Hollasch

11

ในฐานะของ C ++ 14 มีหลายวิธีที่จะทดสอบว่าหมายเลขทศนิยมvalueเป็น NaN

ด้วยวิธีการเหล่านี้เพียงตรวจสอบบิตของการแสดงตัวเลขทำงานได้อย่างน่าเชื่อถือดังที่ระบุไว้ในคำตอบดั้งเดิมของฉัน โดยเฉพาะอย่างยิ่งstd::isnanและการตรวจสอบที่เสนอบ่อย ๆv != vไม่ทำงานอย่างน่าเชื่อถือและไม่ควรใช้เกรงว่ารหัสของคุณจะหยุดทำงานอย่างถูกต้องเมื่อมีคนตัดสินใจว่าจำเป็นต้องปรับจุดลอยตัวให้เหมาะสมและขอให้คอมไพเลอร์ทำเช่นนั้น สถานการณ์นี้สามารถเปลี่ยนแปลงได้คอมไพเลอร์สามารถปรับตัวได้มากขึ้น แต่สำหรับปัญหานี้ที่ไม่ได้เกิดขึ้นใน 6 ปีนับจากคำตอบเดิม

ประมาณ 6 ปีคำตอบเดิมของฉันคือคำตอบที่เลือกสำหรับคำถามนี้ซึ่งก็โอเค แต่เมื่อเร็ว ๆ นี้มีคำตอบที่v != vได้รับการโหวตสูงแนะนำให้ทำการทดสอบที่ไม่น่าเชื่อถือ ดังนั้นคำตอบที่เป็นปัจจุบันเพิ่มเติมนี้เพิ่มเติม (ตอนนี้เรามีมาตรฐาน C ++ 11 และ C ++ 14 และ C ++ 17 บนขอบฟ้า)


วิธีหลักในการตรวจสอบ NaN-ness ตั้งแต่ C ++ 14 ได้แก่ :

  • std::isnan(value) )
    เป็นวิธีไลบรารีมาตรฐานที่ตั้งใจไว้ตั้งแต่ C ++ 11 isnanเห็นได้ชัดว่าขัดแย้งกับแมโคร Posix ในชื่อเดียวกัน แต่ในทางปฏิบัติไม่มีปัญหา ปัญหาหลักคือว่าเมื่อลอยจุดเพิ่มประสิทธิภาพทางคณิตศาสตร์มีการร้องขอแล้วกับคอมไพเลอร์หลักอย่างน้อยหนึ่งคือกรัม ++ std::isnan ผลตอบแทนfalseสำหรับอาร์กิวเมนต์น่าน

  • (fpclassify(value) == FP_NAN) )
    ความทุกข์จากปัญหาเดียวกับที่std::isnanกล่าวคือไม่น่าเชื่อถือ

  • (value != value) )
    แนะนำในคำตอบ SO หลายข้อ ความทุกข์จากปัญหาเดียวกับที่std::isnanกล่าวคือไม่น่าเชื่อถือ

  • (value == Fp_info::quiet_NaN()) )
    นี่คือการทดสอบที่มีพฤติกรรมมาตรฐานไม่ควรตรวจพบ NaNs แต่ด้วยพฤติกรรมที่ได้รับการปรับให้เหมาะสมที่สุดอาจตรวจพบ NaNs (เนื่องจากรหัสที่ได้รับการปรับปรุงเพียงเปรียบเทียบการเป็นตัวแทนระดับบิตโดยตรง) และอาจรวมกับอีกวิธีหนึ่ง สามารถตรวจจับ NaN ได้อย่างน่าเชื่อถือ น่าเสียดายที่มันกลับกลายเป็นว่าไม่สามารถทำงานได้อย่างน่าเชื่อถือ

  • (ilogb(value) == FP_ILOGBNAN) )
    ความทุกข์จากปัญหาเดียวกับที่std::isnanกล่าวคือไม่น่าเชื่อถือ

  • isunordered(1.2345, value) )
    ความทุกข์จากปัญหาเดียวกับที่std::isnanกล่าวคือไม่น่าเชื่อถือ

  • is_ieee754_nan( value ) )
    นี่ไม่ใช่ฟังก์ชั่นมาตรฐาน มันตรวจสอบบิตตามมาตรฐาน IEEE 754 มันน่าเชื่อถืออย่างสมบูรณ์แต่รหัสค่อนข้างขึ้นอยู่กับระบบ


ในรหัสการทดสอบที่สมบูรณ์ "ความสำเร็จ" ดังต่อไปนี้ไม่ว่าจะเป็นนิพจน์รายงานว่าไม่มีค่าของนิพจน์ สำหรับการแสดงออกส่วนใหญ่การวัดนี้ของความสำเร็จเป้าหมายของการตรวจสอบ NaNs และ NaNs เท่านั้นที่สอดคล้องกับความหมายมาตรฐานของพวกเขา อย่างไรก็ตามสำหรับการ(value == Fp_info::quiet_NaN()) )แสดงออกพฤติกรรมมาตรฐานคือมันไม่ได้ทำงานเป็นเครื่องตรวจจับ NaN

#include <cmath>        // std::isnan, std::fpclassify
#include <iostream>
#include <iomanip>      // std::setw
#include <limits>
#include <limits.h>     // CHAR_BIT
#include <sstream>
#include <stdint.h>     // uint64_t
using namespace std;

#define TEST( x, expr, expected ) \
    [&](){ \
        const auto value = x; \
        const bool result = expr; \
        ostringstream stream; \
        stream << boolalpha << #x " = " << x << ", (" #expr ") = " << result; \
        cout \
            << setw( 60 ) << stream.str() << "  " \
            << (result == expected? "Success" : "FAILED") \
            << endl; \
    }()

#define TEST_ALL_VARIABLES( expression ) \
    TEST( v, expression, true ); \
    TEST( u, expression, false ); \
    TEST( w, expression, false )

using Fp_info = numeric_limits<double>;

inline auto is_ieee754_nan( double const x )
    -> bool
{
    static constexpr bool   is_claimed_ieee754  = Fp_info::is_iec559;
    static constexpr int    n_bits_per_byte     = CHAR_BIT;
    using Byte = unsigned char;

    static_assert( is_claimed_ieee754, "!" );
    static_assert( n_bits_per_byte == 8, "!" );
    static_assert( sizeof( x ) == sizeof( uint64_t ), "!" );

    #ifdef _MSC_VER
        uint64_t const bits = reinterpret_cast<uint64_t const&>( x );
    #else
        Byte bytes[sizeof(x)];
        memcpy( bytes, &x, sizeof( x ) );
        uint64_t int_value;
        memcpy( &int_value, bytes, sizeof( x ) );
        uint64_t const& bits = int_value;
    #endif

    static constexpr uint64_t   sign_mask       = 0x8000000000000000;
    static constexpr uint64_t   exp_mask        = 0x7FF0000000000000;
    static constexpr uint64_t   mantissa_mask   = 0x000FFFFFFFFFFFFF;

    (void) sign_mask;
    return (bits & exp_mask) == exp_mask and (bits & mantissa_mask) != 0;
}

auto main()
    -> int
{
    double const v = Fp_info::quiet_NaN();
    double const u = 3.14;
    double const w = Fp_info::infinity();

    cout << boolalpha << left;
    cout << "Compiler claims IEEE 754 = " << Fp_info::is_iec559 << endl;
    cout << endl;;
    TEST_ALL_VARIABLES( std::isnan(value) );                    cout << endl;
    TEST_ALL_VARIABLES( (fpclassify(value) == FP_NAN) );        cout << endl;
    TEST_ALL_VARIABLES( (value != value) );                     cout << endl;
    TEST_ALL_VARIABLES( (value == Fp_info::quiet_NaN()) );      cout << endl;
    TEST_ALL_VARIABLES( (ilogb(value) == FP_ILOGBNAN) );        cout << endl;
    TEST_ALL_VARIABLES( isunordered(1.2345, value) );           cout << endl;
    TEST_ALL_VARIABLES( is_ieee754_nan( value ) );
}

ผลลัพธ์ด้วย g ++ (โปรดทราบอีกครั้งว่าพฤติกรรมมาตรฐานของ(value == Fp_info::quiet_NaN())มันไม่ทำงานในฐานะเครื่องตรวจจับ NaN เป็นเพียงความสนใจในทางปฏิบัติที่นี่)

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
>  g ++ --version | ค้นหา "++"
g ++ (x86_64-win32-sjlj-rev1 สร้างโดยโครงการ MinGW-W64) 6.3.0

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
> g ++ foo.cpp && a
คอมไพเลอร์อ้างสิทธิ์ IEEE 754 = จริง

v = nan, (std :: isnan (value)) = true สำเร็จ
u = 3.14, (std :: isnan (value)) = false สำเร็จ
w = inf, (std :: isnan (value)) = false สำเร็จ

v = nan, ((fpclassify (ค่า) == 0x0100)) = ความสำเร็จที่แท้จริง
u = 3.14, ((fpclassify (ค่า) == 0x0100)) = false สำเร็จ
w = inf, ((fpclassify (ค่า) == 0x0100)) = ความสำเร็จที่ผิดพลาด

v = nan, ((value! = value)) = true สำเร็จ
u = 3.14, ((ค่า! = ค่า)) = false สำเร็จ
w = inf, ((ค่า! = ค่า)) = false สำเร็จ

v = nan, ((ค่า == Fp_info :: quiet_NaN ())) = false ล้มเหลว
u = 3.14, ((ค่า == Fp_info :: quiet_NaN ())) = false สำเร็จ
w = inf, ((ค่า == Fp_info :: quiet_NaN ())) = false สำเร็จ

v = nan, ((ilogb (value) == ((int) 0x80000000))) = ความสำเร็จที่แท้จริง
u = 3.14, ((ilogb (ค่า) == ((int) 0x80000000))) = false สำเร็จ
w = inf, ((ilogb (ค่า) == ((int) 0x80000000))) = false สำเร็จ

v = nan, (isunordered (1.2345, ค่า)) = true Success
u = 3.14, (isunordered (1.2345, ค่า)) = false สำเร็จ
w = inf, (isunordered (1.2345, ค่า)) = false สำเร็จ

v = nan, (is_ieee754_nan (ค่า)) = true สำเร็จ
u = 3.14, (is_ieee754_nan (ค่า)) = false สำเร็จ
w = inf, (is_ieee754_nan (ค่า)) = false สำเร็จ

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
> g ++ foo.cpp -ffast-math && a
คอมไพเลอร์อ้างสิทธิ์ IEEE 754 = จริง

v = nan, (std :: isnan (ค่า)) = false FAILED
u = 3.14, (std :: isnan (value)) = false สำเร็จ
w = inf, (std :: isnan (value)) = false สำเร็จ

v = nan, ((fpclassify (ค่า) == 0x0100)) = false FAILED
u = 3.14, ((fpclassify (ค่า) == 0x0100)) = false สำเร็จ
w = inf, ((fpclassify (ค่า) == 0x0100)) = ความสำเร็จที่ผิดพลาด

v = nan, ((value! = value)) = false FAILED
u = 3.14, ((ค่า! = ค่า)) = false สำเร็จ
w = inf, ((ค่า! = ค่า)) = false สำเร็จ

v = nan, ((ค่า == Fp_info :: quiet_NaN ())) = ความสำเร็จที่แท้จริง
u = 3.14, ((ค่า == Fp_info :: quiet_NaN ())) = จริงล้มเหลว
w = inf, ((ค่า == Fp_info :: quiet_NaN ())) = true ล้มเหลว

v = nan, ((ilogb (value) == ((int) 0x80000000))) = ความสำเร็จที่แท้จริง
u = 3.14, ((ilogb (ค่า) == ((int) 0x80000000))) = false สำเร็จ
w = inf, ((ilogb (ค่า) == ((int) 0x80000000))) = false สำเร็จ

v = nan, (isunordered (1.2345, ค่า)) = false FAILED
u = 3.14, (isunordered (1.2345, ค่า)) = false สำเร็จ
w = inf, (isunordered (1.2345, ค่า)) = false สำเร็จ

v = nan, (is_ieee754_nan (ค่า)) = true สำเร็จ
u = 3.14, (is_ieee754_nan (ค่า)) = false สำเร็จ
w = inf, (is_ieee754_nan (ค่า)) = false สำเร็จ

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
> _

ผลลัพธ์ด้วย Visual C ++:

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
> cl / nologo- 2> & 1 | ค้นหา "++"
Microsoft (R) C / C ++ การปรับแต่งคอมไพเลอร์เวอร์ชั่น 19.00.23725 สำหรับ x86

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
> cl foo.cpp / Feb && b
foo.cpp
คอมไพเลอร์อ้างสิทธิ์ IEEE 754 = จริง

v = nan, (std :: isnan (value)) = true สำเร็จ
u = 3.14, (std :: isnan (value)) = false สำเร็จ
w = inf, (std :: isnan (value)) = false สำเร็จ

v = nan, ((fpclassify (value) == 2)) = ความสำเร็จที่แท้จริง
u = 3.14, ((fpclassify (ค่า) == 2)) = false สำเร็จ
w = inf, ((fpclassify (ค่า) == 2)) = false สำเร็จ

v = nan, ((value! = value)) = true สำเร็จ
u = 3.14, ((ค่า! = ค่า)) = false สำเร็จ
w = inf, ((ค่า! = ค่า)) = false สำเร็จ

v = nan, ((ค่า == Fp_info :: quiet_NaN ())) = false ล้มเหลว
u = 3.14, ((ค่า == Fp_info :: quiet_NaN ())) = false สำเร็จ
w = inf, ((ค่า == Fp_info :: quiet_NaN ())) = false สำเร็จ

v = nan, ((ilogb (value) == 0x7fffffff)) = ความสำเร็จที่แท้จริง
u = 3.14, ((ilogb (value) == 0x7fffffff)) = false สำเร็จ
w = inf, ((ilogb (ค่า) == 0x7fffffff)) = true ล้มเหลว

v = nan, (isunordered (1.2345, ค่า)) = true Success
u = 3.14, (isunordered (1.2345, ค่า)) = false สำเร็จ
w = inf, (isunordered (1.2345, ค่า)) = false สำเร็จ

v = nan, (is_ieee754_nan (ค่า)) = true สำเร็จ
u = 3.14, (is_ieee754_nan (ค่า)) = false สำเร็จ
w = inf, (is_ieee754_nan (ค่า)) = false สำเร็จ

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
> cl foo.cpp / Feb / fp: fast && b
foo.cpp
คอมไพเลอร์อ้างสิทธิ์ IEEE 754 = จริง

v = nan, (std :: isnan (value)) = true สำเร็จ
u = 3.14, (std :: isnan (value)) = false สำเร็จ
w = inf, (std :: isnan (value)) = false สำเร็จ

v = nan, ((fpclassify (value) == 2)) = ความสำเร็จที่แท้จริง
u = 3.14, ((fpclassify (ค่า) == 2)) = false สำเร็จ
w = inf, ((fpclassify (ค่า) == 2)) = false สำเร็จ

v = nan, ((value! = value)) = true สำเร็จ
u = 3.14, ((ค่า! = ค่า)) = false สำเร็จ
w = inf, ((ค่า! = ค่า)) = false สำเร็จ

v = nan, ((ค่า == Fp_info :: quiet_NaN ())) = false ล้มเหลว
u = 3.14, ((ค่า == Fp_info :: quiet_NaN ())) = false สำเร็จ
w = inf, ((ค่า == Fp_info :: quiet_NaN ())) = false สำเร็จ

v = nan, ((ilogb (value) == 0x7fffffff)) = ความสำเร็จที่แท้จริง
u = 3.14, ((ilogb (value) == 0x7fffffff)) = false สำเร็จ
w = inf, ((ilogb (ค่า) == 0x7fffffff)) = true ล้มเหลว

v = nan, (isunordered (1.2345, ค่า)) = true Success
u = 3.14, (isunordered (1.2345, ค่า)) = false สำเร็จ
w = inf, (isunordered (1.2345, ค่า)) = false สำเร็จ

v = nan, (is_ieee754_nan (ค่า)) = true สำเร็จ
u = 3.14, (is_ieee754_nan (ค่า)) = false สำเร็จ
w = inf, (is_ieee754_nan (ค่า)) = false สำเร็จ

[C: \ my \ forum \ so \ 282 (ตรวจสอบ NaN)]
> _

สรุปผลข้างต้นเพียงการทดสอบโดยตรงของการเป็นตัวแทนระดับบิตโดยใช้is_ieee754_nanฟังก์ชั่นที่กำหนดไว้ในโปรแกรมทดสอบนี้ทำงานได้อย่างน่าเชื่อถือในทุกกรณีที่มีทั้ง g ++ และ Visual C ++


ภาคผนวก:
หลังจากโพสต์ดังกล่าวข้างต้นที่ผมเริ่มตระหนักถึงความเป็นไปได้อีกในการทดสอบสำหรับน่านกล่าวถึงในคำตอบอื่น((value < 0) == (value >= 0))ที่นี่คือ ที่เปิดใช้งานได้ดีกับ Visual C ++ แต่ล้มเหลวด้วย-ffast-mathตัวเลือกของ g ++ การทดสอบบิตเทอร์เทิร์นโดยตรงเท่านั้นทำงานได้อย่างน่าเชื่อถือ


7
inline bool IsNan(float f)
{
    const uint32 u = *(uint32*)&f;
    return (u&0x7F800000) == 0x7F800000 && (u&0x7FFFFF);    // Both NaN and qNan.
}

inline bool IsNan(double d)
{
    const uint64 u = *(uint64*)&d;
    return (u&0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && (u&0xFFFFFFFFFFFFFULL);
}

วิธีนี้ใช้ได้ผลถ้าsizeof(int)4 และsizeof(long long)8

ในช่วงเวลาทำงานเป็นการเปรียบเทียบเท่านั้นการหล่อไม่ใช้เวลาใด ๆ มันแค่เปลี่ยนการตั้งค่าสถานะการเปรียบเทียบเพื่อตรวจสอบความเท่าเทียมกัน


นอกจากนี้โปรดทราบว่ามัน จำกัด อยู่เพียงการแสดง IEEE 754
ไชโยและ hth - Alf

โปรดทราบว่าการร่ายนี้ทำลายกฎนามแฝงที่เข้มงวดของ g ++ และคอมไพเลอร์นั้นเป็นที่รู้จักกันว่า Unmentionable Things ™เมื่อตรวจพบ UB อย่างเป็นทางการ แทนที่จะปลดเปลื้องที่มีประสิทธิภาพด้วย g ++ คุณต้องใช้memcpyผ่านอาร์เรย์ไบต์เพื่อให้แน่ใจ สำหรับว่าในของฉัน # 2 คำตอบ
ไชโยและ hth - Alf

4

ทางออกที่เป็นไปได้ที่จะไม่ขึ้นอยู่กับการเป็นตัวแทน IEEE เฉพาะสำหรับ NaN ที่ใช้จะเป็นดังต่อไปนี้:

template<class T>
bool isnan( T f ) {
    T _nan =  (T)0.0/(T)0.0;
    return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}

จุดลอยตัวที่มีความแม่นยำเดียวมีมากกว่า 8 ล้านบิตที่ถูกต้องตามกฎหมายและการตอบสนองบิตที่แตกต่างกันสำหรับ NaN ดังนั้นคุณจะต้องเพิ่มการเปรียบเทียบเพิ่มเติม :)
Steve Hollasch

4

พิจารณาว่า (x! = x) ไม่รับประกันเสมอว่า NaN (เช่นถ้าใช้ตัวเลือก -ffast-math) ฉันเคยใช้:

#define IS_NAN(x) (((x) < 0) == ((x) >= 0))

ตัวเลขต้องไม่เป็นทั้ง <0 และ> = 0 ดังนั้นจริงๆแล้วการตรวจสอบนี้จะผ่านก็ต่อเมื่อตัวเลขนั้นไม่น้อยกว่าหรือมากกว่าหรือเท่ากับศูนย์ ซึ่งโดยทั่วไปแล้วไม่มีตัวเลขเลยหรือ NaN

คุณสามารถใช้สิ่งนี้หากคุณต้องการ:

#define IS_NAN(x) (!((x)<0) && !((x)>=0)

ฉันไม่แน่ใจว่าสิ่งนี้ได้รับผลกระทบจาก -ffast-math อย่างไรดังนั้นระยะของคุณอาจแตกต่างกันไป


นี่คือข้อบกพร่องที่แท้จริงเช่นเดียวกับที่f != fมีข้อบกพร่องเกินไป ฉันได้เห็น llvm เพิ่มประสิทธิภาพโค้ดที่เกือบเหมือนกัน เครื่องมือเพิ่มประสิทธิภาพสามารถเผยแพร่ข้อมูลเกี่ยวกับการเปรียบเทียบครั้งแรกและเข้าใจว่าการเปรียบเทียบครั้งที่สองนั้นอาจไม่เป็นจริงหากมีการเปรียบเทียบครั้งแรก (ถ้าคอมไพเลอร์ปฏิบัติตามกฎ IEEE อย่างเคร่งครัดf != fก็ง่ายกว่ามาก)
Markus

ใช้งานไม่ได้กับ-ffast-mathตัวเลือกg ++ ทำงานร่วมกับ Visual C ++ ดู ( stackoverflow.com/a/42138465/464581 )
ไชโยและ hth - Alf

3

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

#ifndef isnan
  #define isnan(a) (a != a)
#endif

นี่คือหนึ่งในคำตอบที่ดีที่สุดสำหรับคำถามนี้! ขอบคุณสำหรับการแชร์.
Henri Menke

2
คำตอบอื่น ๆ ระบุว่าสิ่งนี้อาจล้มเหลวด้วยชุดตัวเลือก -ffast-math
Technophile

3

งานนี้:

#include <iostream>
#include <math.h>
using namespace std;

int main ()
{
  char ch='a';
  double val = nan(&ch);
  if(isnan(val))
     cout << "isnan" << endl;

  return 0;
}

เอาท์พุท: isnan


1

สำหรับฉันแล้วดูเหมือนว่าวิธีการข้ามแพลตฟอร์มที่ดีที่สุดคือการใช้สหภาพและทดสอบรูปแบบบิตของคู่เพื่อตรวจสอบ NaNs

ฉันยังไม่ได้ทดสอบโซลูชันนี้อย่างละเอียดและอาจมีวิธีที่มีประสิทธิภาพมากขึ้นในการทำงานกับรูปแบบบิต แต่ฉันคิดว่ามันควรจะทำงานได้

#include <stdint.h>
#include <stdio.h>

union NaN
{
    uint64_t bits;
    double num;
};

int main()
{
    //Test if a double is NaN
    double d = 0.0 / 0.0;
    union NaN n;
    n.num = d;
    if((n.bits | 0x800FFFFFFFFFFFFF) == 0xFFFFFFFFFFFFFFFF)
    {
        printf("NaN: %f", d);
    }

    return 0;
}

โปรดทราบว่า "เป็นพฤติกรรมที่ไม่ได้กำหนดเพื่ออ่านจากสมาชิกของสหภาพที่ไม่ได้เขียนล่าสุด" ดังนั้นการใช้ a union-type-pun ระหว่างสองประเภทนี้อาจไม่ทำงานตามที่ต้องการ (: sad_panda :) วิธีที่ถูกต้อง (ถึงแม้ว่าจะไม่ค่อยพกพาเท่าที่ต้องการ) ก็คือการหลีกเลี่ยงการรวมกันและทำ memcpy จากdoubleเป็นuint64_tตัวแปรอื่นจากนั้นทำการทดสอบโดยใช้ตัวแปรตัวช่วย
Eljay

0

บน x86-64 คุณสามารถมีวิธีที่รวดเร็วที่สุดในการตรวจสอบ NaN และอินฟินิตี้ซึ่งทำงานได้โดยไม่คำนึงถึง-ffast-mathตัวเลือกคอมไพเลอร์ ( f != f, std::isnan, std::isinfเสมอผลผลิตfalseด้วย-ffast-math)


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

static inline uint64_t load_ieee754_rep(double a) {
    uint64_t r;
    static_assert(sizeof r == sizeof a, "Unexpected sizes.");
    std::memcpy(&r, &a, sizeof a); // Generates movq instruction.
    return r;
}

static inline uint32_t load_ieee754_rep(float a) {
    uint32_t r;
    static_assert(sizeof r == sizeof a, "Unexpected sizes.");
    std::memcpy(&r, &a, sizeof a); // Generates movd instruction.
    return r;
}

constexpr uint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);
constexpr uint32_t inf_float_shl1 = UINT32_C(0xff000000);

// The shift left removes the sign bit. The exponent moves into the topmost bits,
// so that plain unsigned comparison is enough.
static inline bool isnan2(double a)    { return load_ieee754_rep(a) << 1  > inf_double_shl1; }
static inline bool isinf2(double a)    { return load_ieee754_rep(a) << 1 == inf_double_shl1; }
static inline bool isfinite2(double a) { return load_ieee754_rep(a) << 1  < inf_double_shl1; }
static inline bool isnan2(float a)     { return load_ieee754_rep(a) << 1  > inf_float_shl1; }
static inline bool isinf2(float a)     { return load_ieee754_rep(a) << 1 == inf_float_shl1; }
static inline bool isfinite2(float a)  { return load_ieee754_rep(a) << 1  < inf_float_shl1; }

stdรุ่นisinfและisfiniteโหลด 2 double/floatคงที่จาก.dataเซ็กเมนต์และในกรณีที่เลวร้ายที่สุดที่พวกเขาสามารถทำให้เกิด 2 miss แคชข้อมูล เวอร์ชั่นด้านบนไม่โหลดข้อมูลใด ๆinf_double_shl1และinf_float_shl1ค่าคงที่จะถูกเข้ารหัสเป็นตัวถูกดำเนินการทันทีในคำแนะนำการประกอบ


เร็วขึ้นisnan2เป็นเพียง 2 คำแนะนำการประกอบ:

bool isnan2(double a) {
    bool r;
    asm(".intel_syntax noprefix"
        "\n\t ucomisd %1, %1"
        "\n\t setp %b0"
        "\n\t .att_syntax prefix"
        : "=g" (r)
        : "x" (a)
        : "cc"
        );
    return r;
}

ใช้ข้อเท็จจริงที่ว่าucomisdคำสั่งตั้งค่าแฟริตีถ้าอาร์กิวเมนต์ใด ๆ เป็น NaN นี่คือวิธีการstd::isnanทำงานเมื่อไม่มีการ-ffast-mathระบุตัวเลือก


-1

มาตรฐาน IEEE กล่าวว่าเมื่อเลขชี้กำลังคือทั้งหมด1และ mantissa NaNไม่ได้เป็นศูนย์ตัวเลขคือ Double คือ1sign bit, 11exponent bits และ52mantissa bits ตรวจสอบเล็กน้อย


-3

เนื่องจากความคิดเห็นข้างต้นระบุว่า a! = a จะไม่ทำงานใน g ++ และคอมไพเลอร์อื่น ๆ แต่เคล็ดลับนี้ควรใช้ อาจไม่มีประสิทธิภาพ แต่ก็ยังมีวิธี:

bool IsNan(float a)
{
    char s[4];
    sprintf(s, "%.3f", a);
    if (s[0]=='n') return true;
    else return false;
}

โดยพื้นฐานแล้วใน g ++ (ฉันไม่แน่ใจเกี่ยวกับคนอื่น ๆ ) printf พิมพ์ 'nan' ในรูปแบบ% d หรือ% .f หากตัวแปรไม่ใช่จำนวนเต็ม / ทศนิยมที่ถูกต้อง ดังนั้นรหัสนี้กำลังตรวจสอบอักขระตัวแรกของสตริงที่จะ 'n' (ดังใน "nan")


2
นั่นจะไม่ทำให้บัฟเฟอร์ล้นหาก a = 234324.0f
Mazyod

ใช่ t'will หรือ340282346638528859811704183484516925440.000ถ้าFLT_MAX= เขาจะต้องใช้char s[7]; sprintf(s, "%.0g", a);ซึ่งจะเป็น 6 ชั่วโมงถ้าa=-FLT_MAXหรือ-3e+38
bobobobo

-3

สิ่งนี้จะตรวจจับอินฟินิตี้และ NaN ใน Visual Studio ด้วยการตรวจสอบว่าอยู่ภายในขีด จำกัด สองเท่า:

//#include <float.h>
double x, y = -1.1; x = sqrt(y);
if (x >= DBL_MIN && x <= DBL_MAX )
    cout << "DETECTOR-2 of errors FAILS" << endl;
else
    cout << "DETECTOR-2 of errors OK" << endl;

ตรวจสอบความหมายของFLT_MIN, DBL_MINและLDBL_MINอย่างระมัดระวังมากขึ้น สิ่งเหล่านี้ถูกกำหนดให้เป็นค่าปกติที่เล็กที่สุดสำหรับแต่ละประเภท ตัวอย่างเช่นความแม่นยำเดียวมีมากกว่า 8 ล้านค่า denorm ที่ถูกต้องซึ่งมากกว่าศูนย์และน้อยกว่าFLT_MIN(และไม่ใช่ NaN)
Steve Hollasch
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.