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 產生 NaN 的方式3.2 tagp 參數的意義3.3 除函式呼叫外產生 NaN 的方法3.4 編譯器依賴的注意事項4 4. NaN的種類與特徵4.1 quiet NaN(qNaN)與 signaling NaN(sNaN)的差異4.1.1 quiet NaN(qNaN)4.1.2 signaling NaN(sNaN)4.2 NaN 的位元模式與辨識4.3 NaN 的行為:不可預測的比較與運算5 5. NaN的判定方法5.1 NaN在一般比較中無法判定5.2 isnan() 函式的使用5.3 各資料型別的對應5.4 判定時的注意事項5.5 NaN 檢測的最佳實踐6 6. NaN的應用範例6.1 作為異常值與缺失值的表示的 NaN6.2 作為計算過程中錯誤偵測用的 NaN6.3 在資料分析與統計計算中對 NaN 的利用6.4 在除錯用途中插入 NaN7 7. NaN相關的注意事項與陷阱7.1 比較運算的誤解:NaN與任何值都不相等7.2 NaN 參與的運算結果全部為 NaN7.3 NaN 混入卻不會當機反而成問題7.4 平台與編譯器依賴的行為8 8. 總結8.1 正確理解 NaN 的重要性8.2 本篇文章學到的要點8.3 與 NaN 相處之道:面向實務的實踐1. 前言C語言中 NaN 的意義與重要性 C語言中,處理浮點數時不可避免的就是「NaN(Not a Number)」的存在。這是一種特殊的數值,在日文中也被譯為「非數」,用來表示無法作為數值處理的結果。例如,除以零或計算負數的平方根等,數學上無法定義的運算結果會返回 NaN。為什麼需要了解 NaN? 在程式設計中,如果未正確理解 NaN 的存在,可能會導致意想不到的錯誤。特別是在 C 語言中,與含 NaN 的變數進行比較或運算時,可能會出現預期之外的行為,因此必須深入了解其特性並妥善理。 此外,在進行資料處理或數值分析的程式中,將異常值或缺失值以 NaN 表示,能在保持資料正確性的同時持續處理。因此,學習 NaN 的意義與使用方式不僅僅是錯誤處理,更是與打造穩健軟體開發直接相關的重要知識。本文的目的 本文將全面說明 C 語言中 NaN 的定義、產生方式、判定方法、實際應用案例,以及與 NaN 相關的注意事項。這些在處理浮點數時必備的知識,將以易於理解的方式編排,讓初學者到中級者皆能輕鬆學習。 2. NaN是什麼?NaN(Not a Number)的基本定義 NaN是「Not a Number(非數值)」的縮寫,屬於數值型資料中包含的特殊值之一。顧名思義,它表示沒有數值意義的資料。這不是錯誤,而是一種明確表示「無法以數值處理的運算結果」的機制。 例如,以下的計算結果在數學上無法定義:0.0 / 0.0(零除)sqrt(-1.0)(負值的平方根)log(-5.0)(負值的對數) 在此情況下,使用 C 語言的標準函式庫時,會以 NaN 作為返回值,表示「此計算結果不具數值意義」。IEEE 754 中 NaN 的定位 C 語言的浮點數(float和double)是依據 IEEE 754 這一國際標準設計的。此標準將 NaN 定義為一種特殊的位元模式,用以表示超出數值範圍的結果或無效的運算結果。 在 IEEE 754 中,NaN 大致分為以下兩種類型:quiet NaN(qNaN):在大多數環境中常用的 NaN。運算時不會產生錯誤,會作為 NaN 處理。signaling NaN(sNaN):本來用於觸發例外處理,但在 C 語言中多數實作並未支援。 在 C 語言中使用的 NaN 大多屬於 quiet NaN。NaN 的傳播特性 NaN 若與其他值進行運算,結果基本上也會是 NaN。這基於「包含 NaN 的結果不可可信」的概念。#include

#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 的方法 產生 NaN 的方式 在 C 語言中,為了產生浮點數的特殊值 NaN,通常使用標準函式庫 中定義的函式。代表性的函式如下:nan(const char *tagp)nanf(const char *tagp)nanl(const char *tagp) 這些函式分別回傳 double、float、long double 型別的 NaN 值。#include

#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,使用在標準函式庫 中定義的 isnan() 函式。#include

#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 語言輕鬆判斷奇偶數的方法(附範例程式碼)