Làm thế nào để sử dụng nan và inf trong C?


89

Tôi có một phương pháp số có thể trả về nan hoặc inf nếu có lỗi và để thử nghiệm có mục đích, tôi muốn tạm thời buộc nó trả về nan hoặc inf để đảm bảo tình huống được xử lý chính xác. Có cách nào đáng tin cậy, không phụ thuộc vào trình biên dịch để tạo giá trị nan và inf trong C không?

Sau khi googling khoảng 10 phút, tôi chỉ có thể tìm thấy các giải pháp phụ thuộc vào trình biên dịch.


phao không được xác định bởi tiêu chuẩn C. Vì vậy, không có cách nào độc lập với trình biên dịch để làm những gì bạn muốn.
Johan Kotlinski

Câu trả lời:


86

Bạn có thể kiểm tra xem triển khai của mình có nó không:

#include <math.h>
#ifdef NAN
/* NAN is supported */
#endif
#ifdef INFINITY
/* INFINITY is supported */
#endif

Sự tồn tại của INFINITYđược đảm bảo bởi C99 (hoặc ít nhất là bản nháp mới nhất) và "mở rộng thành một biểu thức không đổi của kiểu float đại diện cho vô cùng dương hoặc không dấu, nếu có; khác với một hằng số dương của kiểu float tràn tại thời điểm dịch."

NAN có thể được xác định hoặc không, và "được xác định nếu và chỉ khi việc triển khai hỗ trợ các NaN yên tĩnh cho kiểu float. Nó mở rộng thành một biểu thức không đổi của kiểu float đại diện cho một NaN yên tĩnh."

Lưu ý rằng nếu bạn đang so sánh các giá trị dấu phẩy động và thực hiện:

a = NAN;

thậm chí sau đó,

a == NAN;

là sai. Một cách để kiểm tra NaN sẽ là:

#include <math.h>
if (isnan(a)) { ... }

Bạn cũng có thể làm: a != ađể kiểm tra xem acó phải là NaN không.

Ngoài ra còn có isfinite(), isinf(), isnormal(), và signbit()macro trong math.htrong C99.

C99 cũng có các nanchức năng:

#include <math.h>
double nan(const char *tagp);
float nanf(const char *tagp);
long double nanl(const char *tagp);

(Tham khảo: n1256).

Tài liệu INFINITY Tài liệu NAN


2
Câu trả lời xuất sắc. Tài liệu tham khảo cho các macro NAN và INFINITY là C99 §7.12 đoạn 4 và 5. Ngoài (isNaN (a)) bạn cũng có thể kiểm tra NaN sử dụng (a = a!) Trên một thực hiện phù hợp của C.
Stephen Canon

23
Đối với tình yêu của khả năng đọc, KHÔNG BAO GIỜa != a nên được sử dụng.
Chris Kerekes 20/09/12

1
@ChrisKerekes: thật đáng buồn, một số người trong chúng ta có NAN nhưng không có isnan (). Vâng, đây là năm 2017. :(
eff

C không yêu cầu, khi akhông phải là số, a == NANtrả về false. IEEE yêu cầu nó. Ngay cả các triển khai tuân thủ IEEE, hầu hết đều làm như vậy . Khi isnan()không được thực hiện, vẫn tốt hơn để bọc thử nghiệm hơn là mã trực tiếp a == NAN.
chux - Phục hồi Monica

34

Không có cách nào độc lập với trình biên dịch để làm điều này, vì cả tiêu chuẩn C (hoặc C ++) đều không nói rằng các kiểu toán dấu phẩy động phải hỗ trợ NAN hoặc INF.

Chỉnh sửa: Tôi vừa kiểm tra từ ngữ của tiêu chuẩn C ++ và nó nói rằng các hàm này (thành viên của lớp mẫu số_limits):

quiet_NaN() 
signalling_NaN()

wiill trả về các đại diện NAN "nếu có". Nó không mở rộng về "nếu có" nghĩa là gì, nhưng có lẽ là một cái gì đó như "nếu đại diện FP của triển khai hỗ trợ họ". Tương tự, có một chức năng:

infinity() 

trả về một đại diện INF tích cực "nếu có".

Cả hai đều được định nghĩa trong <limits>tiêu đề - Tôi đoán rằng tiêu chuẩn C có một cái gì đó tương tự (có thể cũng là "nếu có") nhưng tôi không có bản sao của tiêu chuẩn C99 hiện tại.


Điều đó thật đáng thất vọng và đáng ngạc nhiên. C và C ++ không tuân theo các số dấu phẩy động IEEE, có biểu diễn tiêu chuẩn cho nan và inf?
Đồ họa Noob

14
Trong C99, header C <math.h>định nghĩa nan(), nanf()nanl()đó trở lại cơ quan đại diện khác nhau của NaN (như là một double, floatinttương ứng), và vô cực (nếu có sẵn) có thể được trả lại bằng cách tạo ra một với log(0)hoặc một cái gì đó. Không có cách tiêu chuẩn nào để kiểm tra chúng, ngay cả trong C99. Các <float.h>tiêu đề ( <limits.h>là đối với các loại không thể thiếu) là tiếc im lặng về infnangiá trị.
Chris Lutz

Ồ, đó là một sự kết hợp lớn. nanl()trả về a long double, không phải intnhư bình luận của tôi nói. Tôi không biết tại sao tôi không nhận ra điều đó khi tôi đang gõ nó.
Chris Lutz

@Chris, xem câu trả lời của tôi cho C99.
Alok Singhal

2
@IngeHenriksen - Khá chắc chắn Microsoft đã tuyên bố rằng họ không có ý định VC ++ hỗ trợ C99.
Chris Lutz

24

Điều này hoạt động cho cả floatdouble:

double NAN = 0.0/0.0;
double POS_INF = 1.0 /0.0;
double NEG_INF = -1.0/0.0;

Chỉnh sửa: Như ai đó đã nói, tiêu chuẩn IEEE cũ nói rằng các giá trị như vậy sẽ gây ra bẫy. Nhưng các trình biên dịch mới hầu như luôn tắt bẫy và trả về các giá trị đã cho vì bẫy cản trở việc xử lý lỗi.


Bẫy là một tùy chọn để xử lý lỗi được cho phép dưới thời 754-1985. Hành vi được sử dụng bởi hầu hết các phần cứng / trình biên dịch hiện đại cũng được cho phép (và là hành vi được ưa thích đối với nhiều thành viên của ủy ban). Nhiều người thực hiện đã giả định không chính xác rằng cần phải có bẫy do không may sử dụng thuật ngữ "ngoại lệ" trong tiêu chuẩn. Điều này đã được làm rõ rất nhiều trong bản sửa đổi 754-2008.
Stephen Canon

Xin chào, Stephen, bạn nói đúng, nhưng tiêu chuẩn cũng cho biết: "Người dùng có thể yêu cầu một cái bẫy đối với bất kỳ trường hợp ngoại lệ nào trong số năm trường hợp ngoại lệ bằng cách chỉ định một trình xử lý cho nó. Anh ta có thể yêu cầu tắt trình xử lý hiện có , được lưu hoặc khôi phục. Anh ta cũng có thể xác định xem trình xử lý bẫy cụ thể cho một ngoại lệ được chỉ định đã được kích hoạt hay chưa. " "should" như được định nghĩa (2. Các định nghĩa) có nghĩa là "được khuyến khích mạnh mẽ" và việc triển khai nó chỉ nên được bỏ qua nếu kiến ​​trúc, v.v. làm cho nó không thực tế. 80x86 hỗ trợ đầy đủ tiêu chuẩn, vì vậy không có lý do gì để C không hỗ trợ nó.
Thorsten S.

Tôi đồng ý rằng C nên yêu cầu dấu phẩy động 754 (2008), nhưng có những lý do chính đáng để không cần; cụ thể, C được sử dụng trong tất cả các loại môi trường ngoài x86 - bao gồm các thiết bị nhúng không có dấu phẩy động phần cứng và các thiết bị xử lý tín hiệu mà các lập trình viên thậm chí không muốn sử dụng dấu phẩy động. Đúng hay sai, những cách sử dụng đó chiếm rất nhiều quán tính trong đặc tả ngôn ngữ.
Stephen Canon

Tôi không biết tại sao câu trả lời hàng đầu lại xuất hiện ở đó. Nó không đưa ra bất kỳ cách nào để tạo ra các giá trị được yêu cầu. Câu trả lời này không.
drysdam

#define is_nan(x) ((x) != (x))có thể hữu ích như một bài kiểm tra đơn giản, di động cho NAN.
Bob Stein

21

Một cách độc lập với trình biên dịch, nhưng không phải là cách độc lập với bộ xử lý để có được những điều này:

int inf = 0x7F800000;
return *(float*)&inf;

int nan = 0x7F800001;
return *(float*)&nan;

Điều này sẽ hoạt động trên bất kỳ bộ xử lý nào sử dụng định dạng dấu chấm động IEEE 754 (mà x86 thực hiện).

CẬP NHẬT: Đã kiểm tra và cập nhật.


2
@WaffleMatt - tại sao cổng này không nằm giữa 32/64 bit? IEEE 754 đơn chính xác float là 32 bit bất kể kích thước địa chỉ của bộ xử lý bên dưới.
Aaron

6
Truyền tới (float &)? Điều đó không giống C đối với tôi. Bạn cầnint i = 0x7F800000; return *(float *)&i;
Chris Lutz 17/12/09

6
Lưu ý rằng đó 0x7f800001là cái gọi là NaN báo hiệu trong tiêu chuẩn IEEE-754. Mặc dù hầu hết các thư viện và phần cứng không hỗ trợ NaN báo hiệu, nhưng có khả năng tốt hơn là trả về một NaN yên tĩnh 0x7fc00000.
Stephen Canon

6
Cảnh báo: điều này có thể kích hoạt Hành vi không xác định do vi phạm các quy tắc về răng cưa nghiêm ngặt . Cách được khuyến nghị (và được hỗ trợ tốt nhất trong các trình biên dịch) để thực hiện thao tác đánh kiểu là thông qua các thành viên liên hiệp .
ulidtko

2
Ngoài vấn đề về bí danh nghiêm ngặt mà @ulidtko đã chỉ ra, điều này giả định rằng mục tiêu sử dụng cùng một giá trị cuối cho các số nguyên dưới dạng dấu phẩy động, điều này chắc chắn không phải lúc nào cũng đúng.
mr.stobbe

15
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);

4
Đây là một giải pháp di động thông minh! C99 yêu cầu strtodvà chuyển đổi NaN's và Inf's.
ulidtko

1
Không phải là có một nhược điểm với giải pháp này; chúng không phải là hằng số. Ví dụ, bạn không thể sử dụng những giá trị này để khởi tạo một biến toàn cục (hoặc để khởi tạo một mảng).
Marc

1
@Marc. Bạn luôn có thể có một hàm khởi tạo gọi chúng một lần và đặt chúng trong không gian tên chung. Đó là một nhược điểm rất khả thi.
Mad Physicist

3
<inf.h>

/* IEEE positive infinity.  */

#if __GNUC_PREREQ(3,3)
# define INFINITY   (__builtin_inff())
#else
# define INFINITY   HUGE_VALF
#endif

<bits/nan.h>
#ifndef _MATH_H
# error "Never use <bits/nan.h> directly; include <math.h> instead."
#endif


/* IEEE Not A Number.  */

#if __GNUC_PREREQ(3,3)

# define NAN    (__builtin_nanf (""))

#elif defined __GNUC__

# define NAN \
  (__extension__                                  \
   ((union { unsigned __l __attribute__ ((__mode__ (__SI__))); float __d; })  \
    { __l: 0x7fc00000UL }).__d)

#else

# include <endian.h>

# if __BYTE_ORDER == __BIG_ENDIAN
#  define __nan_bytes       { 0x7f, 0xc0, 0, 0 }
# endif
# if __BYTE_ORDER == __LITTLE_ENDIAN
#  define __nan_bytes       { 0, 0, 0xc0, 0x7f }
# endif

static union { unsigned char __c[4]; float __d; } __nan_union
    __attribute_used__ = { __nan_bytes };
# define NAN    (__nan_union.__d)

#endif  /* GCC.  */

0

Tôi cũng ngạc nhiên vì đây không phải là hằng số thời gian biên dịch. Nhưng tôi cho rằng bạn có thể tạo các giá trị này đủ dễ dàng bằng cách đơn giản thực hiện một lệnh trả về kết quả không hợp lệ như vậy. Chia cho 0, log cho 0, tan cho 90, đại loại là vậy.


0

Tôi thường sử dụng

#define INFINITY (1e999)

hoặc là

const double INFINITY = 1e999

hoạt động ít nhất trong ngữ cảnh IEEE 754 vì giá trị kép cao nhất có thể biểu diễn là gần đúng 1e308. 1e309cũng sẽ hoạt động tốt 1e99999, nhưng ba số chín là đủ và đáng nhớ. Vì đây là một ký tự kép (trong #definetrường hợp) hoặc một Infgiá trị thực , nó sẽ vẫn là vô hạn ngay cả khi bạn đang sử dụng phao 128 bit (“dài đôi”).


1
Điều này rất nguy hiểm, theo tôi. Hãy tưởng tượng cách ai đó di chuyển mã của bạn sang 128 bit trôi nổi trong 20 năm hoặc lâu hơn (sau khi mã của bạn trải qua một quá trình tiến hóa phức tạp đến khó tin, không có giai đoạn nào mà bạn có thể dự đoán ngày hôm nay). Đột nhiên, phạm vi số mũ tăng lên đáng kể và tất cả các 1e999chữ của bạn không còn làm tròn nữa +Infinity. Theo định luật Murphy, điều này phá vỡ một thuật toán. Tệ hơn nữa: một lập trình viên con người thực hiện bản dựng "128-bit" sẽ không có khả năng phát hiện ra lỗi đó trước. Tức là rất có thể sẽ quá muộn khi lỗi này được phát hiện và nhận ra. Rất nguy hiểm.
ulidtko

1
Tất nhiên, trường hợp xấu nhất ở trên có thể xa thực tế. Nhưng vẫn còn, hãy xem xét các lựa chọn thay thế! Tốt hơn hết là hãy ở bên an toàn.
ulidtko

2
"Trong 20 năm nữa", heh. Nào. Câu trả lời này không phải xấu.
alecov

@ulidtko Tôi cũng không thích điều này, nhưng thực sự thì sao?
Iharob Al Asimi

0

Đây là một cách đơn giản để xác định các hằng số đó và tôi khá chắc chắn rằng nó có thể di động:

const double inf = 1.0/0.0;
const double nan = 0.0/0.0;

Khi tôi chạy mã này:

printf("inf  = %f\n", inf);
printf("-inf = %f\n", -inf);
printf("nan  = %f\n", nan);
printf("-nan = %f\n", -nan);

Tôi có:

inf  = inf
-inf = -inf
nan  = -nan
-nan = nan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.