[英]How to safely compare std::complex<double> with Zero with some precision Epsilon?
[英]Compare double to zero using epsilon
今天,我瀏覽了一些 C++ 代碼(由其他人編寫)並找到了這個部分:
double someValue = ...
if (someValue < std::numeric_limits<double>::epsilon() &&
someValue > -std::numeric_limits<double>::epsilon()) {
someValue = 0.0;
}
我試圖弄清楚這是否有意義。
epsilon()
的文檔說:
該函數返回 1 和大於 1 的最小值之間的差值,該值可表示 [用雙精度數]。
這是否也適用於 0,即epsilon()
是大於 0 的最小值? 或者是否有0
到0 + epsilon
之間的數字可以用double
表示?
如果不是,那么比較不等於someValue == 0.0
嗎?
假設 64 位 IEEE double,則有 52 位尾數和 11 位指數。 讓我們將其分解為:
1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1
大於 1 的最小可表示數:
1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52
所以:
epsilon = (1 + 2^-52) - 1 = 2^-52
0和epsilon之間有數字嗎? 很多......例如,最小的正可表示(正常)數是:
1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022
實際上,在 0 和 epsilon 之間有(1022 - 52 + 1)×2^52 = 4372995238176751616
數字,占所有可表示的正數的 47%...
該測試當然與someValue == 0
不同。 浮點數的整個想法是它們存儲一個指數和一個有效數。 因此,它們表示具有一定數量的二進制有效數字精度的值(在 IEEE 雙精度的情況下為 53)。 可表示的值在 0 附近比在 1 附近更密集。
要使用更熟悉的十進制系統,假設您將十進制值存儲為“4 位有效數字”和指數。 那么下一個大於1
的可表示值是1.001 * 10^0
,而epsilon
是1.000 * 10^-3
。 但是1.000 * 10^-4
也是可以表示的,假設指數可以存儲 -4。 您可以相信我的話,IEEE double可以存儲的指數小於epsilon
的指數。
您無法僅從這段代碼中判斷將epsilon
專門用作界限是否有意義,您需要查看上下文。 epsilon
可能是對產生someValue
的計算中的錯誤的合理估計,也可能不是。
有些數字存在於 0 和 epsilon 之間,因為 epsilon 是 1 和可以在 1 以上表示的下一個最高數字之間的差,而不是 0 和可以在 0 以上表示的下一個最高數字之間的差(如果是,那代碼做的很少):-
#include <limits>
int main ()
{
struct Doubles
{
double one;
double epsilon;
double half_epsilon;
} values;
values.one = 1.0;
values.epsilon = std::numeric_limits<double>::epsilon();
values.half_epsilon = values.epsilon / 2.0;
}
使用調試器,在 main 結束時停止程序並查看結果,您會發現 epsilon / 2 與 epsilon、0 和 1 不同。
因此,此函數采用 +/- epsilon 之間的值並將它們設為零。
可以使用以下程序打印數字 (1.0, 0.0, ...) 周圍的 epsilon 近似值(可能的最小差異)。 它打印以下輸出:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
稍微思考一下就清楚了,我們用於查看其 epsilon 值的數字越小,epsilon 就越小,因為指數可以調整到該數字的大小。
#include <stdio.h>
#include <assert.h>
double getEps (double m) {
double approx=1.0;
double lastApprox=0.0;
while (m+approx!=m) {
lastApprox=approx;
approx/=2.0;
}
assert (lastApprox!=0);
return lastApprox;
}
int main () {
printf ("epsilon for 0.0 is %e\n", getEps (0.0));
printf ("epsilon for 1.0 is %e\n", getEps (1.0));
return 0;
}
X
和X
的下一個值之間的差值根據X
變化。
epsilon()
只是1
和1
的下一個值之間的差異。
0
和下一個0
值之間的差異不是epsilon()
。
相反,您可以使用std::nextafter
將雙精度值與0
進行比較,如下所示:
bool same(double a, double b)
{
return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
&& std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}
double someValue = ...
if (same (someValue, 0.0)) {
someValue = 0.0;
}
假設我們正在使用適合 16 位寄存器的玩具浮點數。 有一個符號位、一個 5 位指數和一個 10 位尾數。
這個浮點數的值是尾數,解釋為二進制十進制值,乘以 2 的指數次方。
在 1 附近,指數等於 0。 所以尾數的最小位數是 1024 的一部分。
接近 1/2 的指數是負一,所以尾數的最小部分是原來的一半。 使用 5 位指數可以達到負 16,此時尾數的最小部分值 32m 中的一部分。 在負 16 指數處,該值大約是 32k 的一部分,比我們上面計算的大約 1 的 epsilon 更接近於零!
現在這是一個玩具浮點模型,它不能反映真實浮點系統的所有怪癖,但反映小於 epsilon 的值的能力與真實浮點值相當相似。
由於尾數和指數部分,您不能將此應用於 0。 由於指數,您可以存儲比 epsilon 小的數字,但是當您嘗試執行類似 (1.0 - "very small number") 之類的操作時,您將獲得 1.0。 Epsilon 不是值的指標,而是值精度的指標,以尾數表示。 它顯示了我們可以存儲多少個正確的后繼十進制數字。
因此,假設系統無法區分 1.000000000000000000000 和 1.000000000000000000001。 即 1.0 和 1.0 + 1e-20。 你認為在 -1e-20 和 +1e-20 之間還有一些值可以表示嗎?
對於 IEEE 浮點,在最小的非零正值和最小的非零負值之間,存在兩個值:正零和負零。 測試一個值是否在最小的非零值之間,相當於測試是否與零相等; 然而,賦值可能會產生影響,因為它會將負零變為正零。
可以想象,浮點格式可能具有介於最小有限正負值之間的三個值:正無窮小、無符號零和負無窮小。 我不熟悉實際上以這種方式工作的任何浮點格式,但這樣的行為將是完全合理的,並且可以說比 IEEE 的更好(也許不夠好到值得添加額外的硬件來支持它,但在數學上 1 /(1/INF)、1/(-1/INF) 和 1/(1-1) 應該代表三種不同的情況,說明三個不同的零)。 我不知道是否有任何 C 標准會要求有符號的無窮小,如果它們存在,則必須比較等於零。 如果他們不這樣做,像上面這樣的代碼可以有效地確保例如將一個數字重復除以 2 最終會產生零,而不是停留在“無窮小”上。
“1 和大於 1 的最小值之間的差”表示 1 +“機器零”,大約為 10^-8 或 10^-16,具體取決於您是否分別使用雙變量的浮點數。 要查看機器零,您可以將 1 除以 2,直到計算機看到 1 = 1+1/2^p,如下所示:
#include <iostream>
#include "math.h"
using namespace std;
int main() {
float a = 1;
int n = 0;
while(1+a != 1){
a = a/2;
n +=1;
}
cout << n-1 << endl << pow(2,-n);
return 0;
}
此外,擁有這樣一個函數的一個很好的理由是刪除“非規范化”(那些不能再使用隱含的前導“1”並具有特殊 FP 表示的非常小的數字)。 你為什么想做這個? 因為有些機器(特別是一些較舊的 Pentium 4s)在處理非規范化時會變得非常非常慢。 其他人只是變得有點慢。 如果您的應用程序並不真正需要這些非常小的數字,那么將它們清零是一個很好的解決方案。 考慮這一點的好地方是任何 IIR 濾波器或衰減函數的最后一步。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.