ACM中关于计算几何(浮点数)的精度问题
計算幾何的精度問題說到底其實是浮點數的精度問題,但我覺得“計算幾何”比“浮點數”更能吸引眼球,所以選了這個標題。
1.浮點數為啥會有精度問題:
浮點數(以C/C++為準),一般用的較多的是float, double。
| ? | 占字節數 | 數值范圍 | 十進制精度位數 |
| float | 4 | -3.4e-38~3.4e38 | 6~7 |
| double | 8 | -1.7e-308~1.7e308 | 14~15 |
如果內存不是很緊張或者精度要求不是很低,一般選用double。14位的精度(是有效數字位,不是小數點后的位數)通常夠用了。注意,問題來了,數據精度位數達到了14位,但有些浮點運算的結果精度并達不到這么高,可能準確的結果只有10~12位左右。那低幾位呢?自然就是不可預料的數字了。這給我們帶來這樣的問題:即使是理論上相同的值,由于是經過不同的運算過程得到的,他們在低幾位有可能(一般來說都是)是不同的。這種現象看似沒太大的影響,卻會一種運算產生致命的影響: ==。恩,就是判斷相等。注意,C/C++中浮點數的==需要完全一樣才能返回true。來看下面這個例子:
#include<stdio.h>
#include<math.h>
int main()
{
???double a = asin(sqrt(2.0) / 2) * 4.0;
???double b = acos(-1.0);
???printf("??????a = %.20lf\n", a);
???printf("??????b = %.20lf\n", b);
???printf(" a - b = %.20lf\n", a - b);
???printf("a == b = %d\n", a == b);
???return 0;
}
輸出:
??????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){return a < -eps ? -1 : a < eps ? 0 : 1;}
則各種判斷大小的運算都應做如下修正:
| 傳統意義 | 修正寫法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<T>
有時候我們可能會有這種需求,對浮點數進行?插入、查詢是否插入過?的操作。手寫hash表是一個方法(hash函數一樣要小心設計),但set不是更方便嗎。但set好像是按==來判重的呀?貌似行不通呢。經觀察,set不是通過==來判斷相等的,是通過<來進行的,具體說來,只要a<b?和?b<a?都不成立,就認為a和b相等,可以發現,
如果將小于定義成:??????bool operator < (const Dat dat)const{return val < dat.val - eps;}就可以解決問題了。?(基本類型不能重載運算符,所以封裝了下)
8.?輸入值波動過大
這種情況不常見,不過可以幫助你更熟悉eps。假如一道題輸入說,給一個浮點數a, 1e-20 < a < 1e20。那你還敢用1e-8做eps么?合理的做法是把eps按照輸入規模縮放到合適大小。
典型案例: HUSTOJ 1361
9.?一些建議
容易產生較大浮點誤差的函數有asin、?acos。歡迎盡量使用atan2。
另外,如果數據明確說明是整數,而且范圍不大的話,使用int或者long long代替double都是極佳選擇,因為就不存在浮點誤差了(盡管我幾乎從來都只用double --!)
轉自https://blog.csdn.net/entalent/article/details/47620341
總結
以上是生活随笔為你收集整理的ACM中关于计算几何(浮点数)的精度问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Steam Deck付运量翻番
- 下一篇: 日本樱岛火山喷发 烟柱高达1500米:正