CodeGym /課程 /C# SELF /精確度問題與特殊值

精確度問題與特殊值

C# SELF
等級 6 , 課堂 3
開放

1. 前言

你可能覺得,電腦這麼「聰明」,那 0.1 + 0.2 應該就等於 0.3 啊。但事實不是這樣。來,直接看個簡單例子。

double x = 0.1;
double y = 0.2;
double sum = x + y;
Console.WriteLine(sum); // 程式會印出什麼?
浮點數相加:我們以為會是 0.3,實際上呢?

現在你試試跟 0.3 比較一下:

Console.WriteLine(sum == 0.3); // 這裡會是 true 還是 false?

如果你看到 False,別太驚訝啦!

原因藏在數字在記憶體裡的表示方式

電腦是用二進位來處理數字的。但不是每個十進位小數都能用有限的二進位小數來表示,就像 1/3 沒辦法精確寫成十進位小數(0.333...)。舉例來說,0.1 在二進位裡是無窮小數,所以只能「四捨五入」存起來。

如果用白話來說,double 有時候「假裝」能精確存你的數字,其實只是存了個很接近的近似值而已。

2. 用 double 算數會遇到哪些「怪現象」?

來看幾個實際例子。

例子 1. 經典「魔法」0.1 + 0.2

double a = 0.1;
double b = 0.2;
double sum = a + b;
Console.WriteLine(sum);            // 0.30000000000000004
Console.WriteLine(sum == 0.3);     // False

電腦印出來不是 0.3,而是 0.30000000000000004。差一點點,但如果你在做金融相關的東西,這就很嚴重了。

例子 2. 疊加小數

double result = 0;
for (int i = 0; i < 10; i++)
{
    result += 0.1;
}
Console.WriteLine(result); // 0.9999999999999999

你想要 1.0,結果卻少了一點點。又是 double 內部四捨五入的問題。

為什麼這在實務上很重要

很多人會想:「差一點點有差嗎?沒什麼大不了吧!」來看個支付世界的例子。

假設你的網銀要把 100 筆每筆 0.1 歐元的交易加總。如果你的程式每次都「掉」個十萬分之一歐元,銀行規模一大就真的會「不見」錢。這時會計馬上來問你:「我們的錢去哪了?!」

3. 怎麼正確比較浮點數

因為 double 很常沒辦法存你想要的精確值,直接用 == 比較很容易出包。正確做法是比「兩個數的差的絕對值」有沒有小於某個「超小的數」(epsilon)。

用誤差容忍值比較的例子

double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 0.000001;

if (Math.Abs(a - b) < epsilon)
{
    Console.WriteLine("幾乎一樣啦!"); // 這樣比比較安全
}

這裡我們的意思是:「如果兩個數的差小於一百萬分之一,就當作一樣。」

4. double 的特殊值:Infinity, NaN, -Infinity

double 不只可以存數字,還有一些很特別的值。這些值會在數學老師平常叫你千萬別做的情況下出現。

無限大(Infinity

如果你拿 1 去除以 0 會怎樣?

double result = 1.0 / 0.0;
Console.WriteLine(result); // Infinity

在 C#(還有很多語言)裡,double 除以 0 不會丟出例外!而是直接給你一個特殊值「正無限大」。

負無限大(-Infinity

如果你拿負數除以 0,就會得到負無限大:

double result = -1.0 / 0.0;
Console.WriteLine(result); // -Infinity

「不是數字」(NaN — Not a Number)

如果你做了很奇怪的事,比如要算負數的平方根:

double result = Math.Sqrt(-1);
Console.WriteLine(result); // NaN

或者 0.0 / 0.0 的結果:

double result = 0.0 / 0.0;
Console.WriteLine(result); // NaN

NaN 就是「在現實生活裡根本不是數字的東西」。

檢查特殊值的方法

C# 有內建函數可以檢查這些特殊值:

Console.WriteLine(double.IsInfinity(result));    // true 代表是無限大
Console.WriteLine(double.IsNaN(result));         // true 代表是 NaN

表格:double 遇到奇怪運算時的反應

運算 結果 double 裡存什麼
1.0 / 0.0
Infinity +∞
-1.0 / 0.0
-Infinity -∞
0.0 / 0.0
NaN 不是數字
Math.Sqrt(-1)
NaN 不是數字
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION