By 佐川 直弘 | Naohiro Sagawa (運営者) ※記事内に広告を含みます。Amazonアソシエイトとして適格販売により収入を得ています。
目次 1 1. 前言1.1 C語言中 NaN 的意義與重要性1.2 為什麼需要了解 NaN?1.3 本文的目的2 2. NaN是什麼?2.1 NaN(Not a Number)的基本定義2.2 IEEE 754 中 NaN 的定位2.3 NaN 的傳播特性3 3. C語言中產生 NaN 的方法3.1
#include
int main() {
double a = 0.0 / 0.0; // NaN
double b = a + 100.0; // 也是 NaN
printf("%f
", b); // 顯示結果: nan
return 0;
}如此一來,一旦 NaN 發生,會連鎖傳播,因此 NaN 的偵測與處理變得相當重要。 3. C語言中產生 NaN 的方法
#include
int main() {
double x = nan("");
printf("%f
", x); // 輸出: nan
return 0;
}tagp 參數的意義 傳遞給這些函式的 tagp 參數是用來指定附加在 NaN 上的標籤資訊,可能用於除錯或診斷用途。但實際行為與是否支援取決於實作(編譯器或函式庫),因此通常會指定空字串 ""。double y = nan("payload");即使如此指定,輸出的 NaN 幾乎沒有差異,且標籤會明顯顯示的情況很少見。若重視可移植性,使用 "" 是較安全的做法。除函式呼叫外產生 NaN 的方法 在某些情況下,也可以利用明確回傳 NaN 的運算式來產生 NaN。例如如下,將 0.0 除以 0.0 會產生 NaN。double nan_val = 0.0 / 0.0;但此類方法可能導致執行時錯誤或未定義行為,實務上建議使用 nan("") 等函式明確產生。編譯器依賴的注意事項 NaN 的產生與行為雖遵循 C 語言標準,但依環境可能會有些微差異。特別是在嵌入式開發等浮點運算被特殊處理的環境中,NaN 可能無法正確處理,因此在測試環境中確認其行為相當重要。 4. NaN的種類與特徵quiet NaN(qNaN)與 signaling NaN(sNaN)的差異 NaN 有 quiet NaN(qNaN) 與 signaling NaN(sNaN) 兩種。兩者皆是表示「非數」的特殊值,但其用途與行為有明顯的差別。quiet NaN(qNaN) quiet NaN 是在 C 語言中最常使用的 NaN 形式。顧名思義是「靜默的 NaN」,在程式執行時不會發出任何通知,悄悄地繼續運算處理。 例如,以下程式碼產生的 NaN 為 qNaN:#include
double x = nan("");此 qNaN 會以數值方式顯示發生異常運算的事實,但不會拋出例外或停止程式。signaling NaN(sNaN) signaling NaN 是在運算時可能引發 例外(Floating-Point Exception) 的 NaN。原本設計用於除錯或開發階段的異常偵測。 然而,在 C 標準函式庫以及多數實作(如 GCC、Clang 等)中,sNaN 的明確產生或處理往往不被支援,或會被當作 qNaN 處理。sNaN 主要與硬體層面的控制相關,會在組合語言或低階浮點運算控制的情況下出現。NaN 的位元模式與辨識 符合 IEEE 754 的 C 語言環境中,NaN 以特定的位元模式表示。例如,在 64 位元的double 型別中,指數部全為 1 且尾數部非零時,即被視為 NaN。符號位元 1bit | 指數部 11bit(全部為1) | 尾數部 52bit(非零)透過此位元結構,編譯器或 CPU 能判斷是否為 NaN,並執行適當的行為(傳播、迴避、警告)。NaN 的行為:不可預測的比較與運算 NaN 具有與數值不同的獨特性質,特別是在比較運算時需特別留意。double x = nan("");
if (x == x) {
printf("相等
");
} else {
printf("不相等
");
}
// 結果:不相等如上所示,NaN 甚至不等於自身,因此在一般比較運算中難以處理。此特性在偵測 NaN 存在時相當有用,但同時也可能成為錯誤的根源。 5. NaN的判定方法NaN在一般比較中無法判定 NaN最特異的性質之一是「NaN與任何事物都不相等」這一點。這表示,甚至與自身的比較也不成立的極端性質,使用一般的==運算子也無法判定NaN。#include
#include
int main() {
double x = nan("");
if (x == x) {
// 若是一般數值則為 true,
// 若為 NaN 則為 false
}
}因此,若在檢查數值同一性的程式碼中混入 NaN,容易產生意外的分支。特別是在複雜的 if 語句或迴圈中使用時,可能成為 bug 的溫床。isnan() 函式的使用 在 C 語言中,為了判定浮點數是否為 NaN,使用在標準函式庫
#include
int>int main() {
double x = 0.0 / 0.0; // NaN
if (isnan(x)) {
printf("x是 NaN\n");
} else {
printf("x是數值\n");
}
return 0;
}此函式若參數為 NaN,則回傳真(非零),否則回傳偽(0)。各資料型別的對應 isnan() 是針對 double 型的函式,但亦提供以下相同功能的巨集(視環境而定可能無法使用,需注意):isnanf() : 支援 float 型isnanl() : 支援 long double 型判定時的注意事項 NaN 常成為「看不見的 bug」的原因,特別需要注意以下情況:未初始化的變數在進行浮點運算時,可能會變成 NaN外部函式庫或輸入輸出處理中可能混入 NaN當比較處理出現預期外結果時,首先懷疑是否為 NaNNaN 檢測的最佳實踐在複雜運算處理之後,使用 isnan() 進行確認除錯時使用 printf("%f", x) 等顯示時,檢查是否輸出「nan」對於存放資料的變數,明確設定初始值,即使在有意使用 NaN 的情況下,也保留註解說明 6. NaN的應用範例作為異常值與缺失值的表示的 NaN C語言用於數值計算時,NaN 作為異常值或缺失值的「旗標」運作。例如,在感測器資料或使用者輸入等情況下,並非所有數值都能正確取得,這時可將 NaN 代入取代一般數值,以明確表示「此值無效」。double temperature = isnan(sensor_reading) ? nan("") : sensor_reading;透過這樣的方式活用 NaN,即可不中斷處理,並在後續步驟中偵測與排除異常值。作為計算過程中錯誤偵測用的 NaN 在程式內的數值運算中,即使在途中發生未預期的運算(如除以零或負數的平方根),也不會立即使程式崩潰,而是可以 回傳 NaN 並持續計算。如此可在之後追蹤異常資料產生的來源。#include
double safe_divide(double a, double b) {
if (b == 0.0) {
return nan(""); // 錯誤但仍持續處理
}
return a / b;
}此類程式碼構成了 高可靠性的數值處理程式的基本結構。在資料分析與統計計算中對 NaN 的利用 在統計處理中,常將資料的缺失(missing value)視為 NaN。雖然 C 語言本身未內建統計函式庫,但以 NaN 為基礎的統計處理對於函式庫開發與資料處理的實作相當重要。 以下是一個排除 NaN 後計算平均值的範例:#include
double mean_without_nan(double *arr, int size) {
double sum = 0.0;
int count = 0;
for (int i = 0; i < size; i++) {
if (!isnan(arr[i])) {
sum += arr[i];
count++;
}
}
return (count == 0) ? nan("") : (sum / count);
}透過這樣的方式活用 NaN,即可實現 「不將其納入計算的彈性處理」。在除錯用途中插入 NaN 在程式執行確認時,故意插入 NaN,作為「若存取此值即表示有問題」的標記。此手法屬於測試階段有意加入不正確值以觀察系統回應的「故障注入(Fault Injection)」的一環。 7. NaN相關的注意事項與陷阱比較運算的誤解:NaN與任何值都不相等 NaN最大的特徵,也是最容易引起誤解的,是「NaN與任何事物都不相等」的性質。這意味著,甚至連與自身的比較都不成立,因此即使使用一般的==運算子也無法判斷NaN。double x = nan("");
if (x == x) {
// 若是一般數值則會是 true,
// NaN 時則為 false
}因此,若在檢查數值同一性的程式碼中混入 NaN,意外的分支很容易發生。尤其在複雜的 if 語句或迴圈中使用時,可能成為 bug 的溫床。NaN 參與的運算結果全部為 NaN NaN 具有「傳染性」,無論與任何數值運算,基本上結果都會是 NaN。例如以下的運算都會回傳 NaN。double a = nan("");
double b = 100.0;
double result = a + b; // result 為 NaN若在不了解此特性的情況下繼續運算,可能會出現 程式後半突然所有值皆變成 NaN 的情況。NaN 混入卻不會當機反而成問題 在 C 語言中,NaN 通常不會「觸發例外」。也就是說,即使程式執行異常的運算,也不會被偵測為錯誤,會靜默地繼續執行。 因此,可能在不自覺的情況下持續處理異常值,導致 bug 的發現延遲,形成陷阱。對策包括:在每個步驟加入 isnan() 檢查測試時刻意提供含 NaN 的資料以確認行為 此類對策是有效的。平台與編譯器依賴的行為 NaN 的內部位元表示遵循 IEEE 754,但 不同平台或編譯器可能會產生細微的行為差異。尤其以下幾點需要注意:nan("") 的字串參數是否會被解析(有時會被忽略)printf 的輸出格式(nan、NaN、-nan 等)sNaN 是否可用(多數環境不支援) 因此,在重視可移植性的程式碼中需注意 NaN 的處理,且不要忘記在不同環境進行驗證。 8. 總結正確理解 NaN 的重要性 NaN(Not a Number)是 C 語言中浮點運算時,用來表示不具數值意義的特殊值。作為除以零或無效運算的結果出現的 NaN,並不是以錯誤方式停止處理,而是用數值來表達「發生了無效狀態」的資訊機制。 具備此類特性的 NaN,乍看可能覺得難以處理,但若能正確理解並善加利用,則有可能大幅提升程式的穩健性與彈性。本篇文章學到的要點NaN 是什麼? 是無法作為數值處理的特殊值產生方法:nan("") 函數等可明示產生種類:有 quiet NaN 與 signaling NaN(實作依賴)判定方法:可透過 isnan() 函數安全檢查活用例:涵蓋缺失值、錯誤偵測、除錯用標記等多方面注意點:比較運算始終為 false、傳播性、以及不會導致崩潰的特性與 NaN 相處之道:面向實務的實踐 NaN 含有程式靜靜地進入錯誤狀態的可能性。因此,重要不是「發現就停止」,而是「在可能發生的前提下設計」。 特別是在需要數值處理或高可靠性的系統中,若在初期階段就妥善設計 NaN 的判定與移除處理,則能大幅減少之後的錯誤與故障。 Prev C 語言的歷史與演變:從誕生到現在的系統性解析 Next 新手也能懂!用 C 語言輕鬆判斷奇偶數的方法(附範例程式碼)