在acm中,精度問題非常常見。其中計算幾何頭疼的地方一般在於**量大和精度問題,**量問題只要平時注意積累模板一般就不成問題了。精度問題則不好說,有時候乙個精度問題就可能成為一道題的瓶頸,讓你debug半天都找不到錯誤出在哪。
1.浮點數為啥會有精度問題:
浮點數(以c/c++為準),一般用的較多的是float, double。
佔位元組數
數值範圍
十進位制精度位數
float
-3.4e-38~3.4e38
6~7double
-1.7e-308~1.7e308
14~15
如果記憶體不是很緊張或者精度要求不是很低,一般選用double。14位的精度(是有效數字位,不是小數點後的位數)通常夠用了。注意,問題來了,資料精度位數達到了14位,但有些浮點運算的結果精度並達不到這麼高,可能準確的結果只有10~12位左右。那低幾位呢?自然就是不可預料的數字了。這給我們帶來這樣的問題:即使是理論上相同的值,由於是經過不同的運算過程得到的,他們在低幾位有可能(一般來說都是)是不同的。這種現象看似沒太大的影響,卻會一種運算產生致命的影響: ==。恩,就是判斷相等。注意,c/c++中浮點數的==需要完全一樣才能返回true。來看下面這個例子:
#include
#include
intmain()
輸出: a =
3.14159265358979360000
b =3.14159265358979310000
a - b =
0.00000000000000044409
a == b =
0
我們解決的辦法是引進eps,來輔助判斷浮點數的相等。
2. eps
eps縮寫自epsilon,表示乙個小量,但這個小量又要確保遠大於浮點運算結果的不確定量。eps最常見的取值是1e-8左右。引入eps後,我們判斷兩浮點數a、b相等的方式如下:
定義三出口函式如下: int sgn(double a)
則各種判斷大小的運算都應做如下修正:
傳統意義
修正寫法1
修正寫法2
a == b
sgn(a - b) == 0
fabs(a – b) < eps
a != b
sgn(a - b) != 0
fabs(a – b) > eps
a < b
sgn(a - b) < 0
a – b < -eps
a <= b
sgn(a - b) <= 0
a – b < eps
a > b
sgn(a - b) > 0
a – b > eps
a >= b
sgn(a - b) >= 0
a – b > -eps
這樣,我們才能把相差非常近的浮點數判為相等;同時把確實相差較大(差值大於eps)的數判為不相等。
ps: 養成好習慣,盡量不要再對浮點數做==判斷。例如,我的修正寫法2裡就沒有出現==。
3. eps帶來的函式越界
如果sqrt(a), asin(a), acos(a) 中的a是你自己算出來並傳進來的,那就得小心了。
如果a本來應該是0的,由於浮點誤差,可能實際是乙個絕對值很小的負數(比如1e-12),這樣sqrt(a)應得0的,直接因a不在定義域而出錯。
類似地,如果a本來應該是±1,則asin(a)、acos(a)也有可能出錯。
因此,對於此種函式,必需事先對a進行校正。
4. 輸出陷阱i
這一節和下一節一樣,都是因為題目要求輸出浮點數,導致的問題。而且都和四捨五入有關。
1. printf(「%.3lf」, a); //保留a的三位小數,按照第四位四捨五入
2. (int)a; //將a靠進0取整
3. ceil(a); floor(a); //顧名思義,向上取證、向下取整。需要注意的是,這兩個函式都返回double,而非int
其中第一種很常見於輸出(nonsense…)。
現在考慮一種情況,題目要求輸出保留兩位小數。有個case的正確答案的精確值是0.005,按理應該輸出0.01,但你的結果可能是0.005000000001(恭喜),也有可能是0.004999999999(悲劇),如果按照printf(「%.2lf」, a)輸出,那你的遭遇將和括號裡的字相同。
解決辦法是,如果a為正,則輸出a+eps, 否則輸出a-eps
典型案例: poj2826
5. 輸出陷阱ii
icpc題目輸出有個不成文的規定(有時也成文),不要輸出: -0.000
那我們首先要弄清,什麼時候按printf(「%.3lf\n」, a)輸出會出現這個結果。
直接給出結果好了:a∈(-0.000499999……, -0.000……1)
所以,如果你發現a落在這個範圍內,請直接輸出0.000。更保險的做法是用sprintf直接判斷輸出結果是不是-0.000再予處理。
典型案例:uva746
6. 範圍越界
這個嚴格來說不屬於精度範疇了,不過湊數還是可以的。請注意,雖然double可以表示的數的範圍很大,卻不是不窮大,上面說過最大是1e308。所以有些時候你得小心了,比如做連乘的時候,必要的時候要換成對數的和。
典型案例:hdu3558
7. 關於set
有時候我們可能會有這種需求,對浮點數進行 插入、查詢是否插入過 的操作。手寫hash表是乙個方法(hash函式一樣要小心設計),但set不是更方便嗎。但set好像是按==來判重的呀?貌似行不通呢。經觀察,set不是通過==來判斷相等的,是通過《來進行的,具體說來,只要a如果將小於定義成: bool operator < (const dat dat)const就可以解決問題了。 (基本型別不能過載運算子,所以封裝了下)
8. 輸入值波動過大
這種情況不常見,不過可以幫助你更熟悉eps。假如一道題輸入說,給乙個浮點數a, 1e-20 < a < 1e20。那你還敢用1e-8做eps麼?合理的做法是把eps按照輸入規模縮放到合適大小。
典型案例: hustoj 1361
9. 一些建議
容易產生較大浮點誤差的函式有asin、 acos。歡迎盡量使用atan2。
另外,如果資料明確說明是整數,而且範圍不大的話,使用int或者long long代替double都是極佳選擇,這樣就不存在浮點誤差了
參考:
浮點數比較
在數 算當中經常會涉及到判斷兩個數是否相等的情況 對於整數很好處理 a b這樣的乙個語句就可以解決全部的問題 但是對於浮點數是不同的 首先,浮點數在計算機當中的二進位制表達方式就決定了大多數浮點數都是無法精確的表達的 現在的計算機大部分都是數字計算機,不是模擬機,數字機的離散化的資料表示方法自然無法...
浮點數比較
部分 思路來自網路。fxxki整理發布。double變數以帶符號的 ieee 64 位 8 個位元組 雙精度浮點數形式儲存 它可以表示十進位制的15或16位有效數字.負值取值範圍為 1.79769313486231570e 308 到 4.94065645841246544e 324,正值取值範圍為...
浮點數比較
0 我們來看乙個程式 include int main else 1 執行結果 可以看出,我們輸入的2.3和計算出來的 4.6 2 相等,這個沒有問題。但是如果遇到下面這個問題 3 我們再來看一段程式 include include define eqs 1e 8 define equal a,b ...