簡體   English   中英

使用 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 的最小值? 或者是否有00 + 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 ,而epsilon1.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;
}

XX的下一個值之間的差值根據X變化。
epsilon()只是11的下一個值之間的差異。
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 不是值的指標,而是值精度的指標,以尾數表示。 它顯示了我們可以存儲多少個正確的后繼十進制數字。

我認為這取決於您的計算機的 精度 看一下這張:你可以看到如果你的epsilon用double表示,但是你的精度更高,比較不等於

someValue == 0.0

總之是個好問題!

因此,假設系統無法區分 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 濾波器或衰減函數的最后一步。

另請參閱: 為什么將 0.1f 更改為 0 會使性能降低 10 倍?

http://en.wikipedia.org/wiki/Denormal_number

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM