Porównaj podwójnie do zera za pomocą epsilon

Dzisiaj przeglądałem jakiś kod C++ (napisany przez kogoś innego) i znalazłem ten dział:

double someValue = ...
if (someValue <  std::numeric_limits<double>::epsilon() && 
    someValue > -std::numeric_limits<double>::epsilon()) {
  someValue = 0.0;
}
Próbuję się dowiedzieć, czy to ma sens.

Dokumentacja dla epsilon() mówi:

Funkcja zwraca różnicę między 1 a najmniejszą wartością większą niż 1, która jest reprezentowalna [przez podwojenie].

Czy dotyczy to również 0, tzn. {[1] } Czy najmniejsza wartość jest większa od 0? Czy są liczby pomiędzy 0 a 0 + epsilon które mogą być reprezentowane przez double?

Jeśli nie, to czy porównanie nie jest równoważne someValue == 0.0?

 197
Author: Sebastian Krysmanski, 2012-12-04

11 answers

Zakładając 64-bitowe podwójne IEEE, istnieje 52-bitowa mantysa i 11-bitowy wykładnik. Spójrz na następujące liczby:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1

Najmniejsza reprezentowalna liczba większa od 1:

1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52

Dlatego:

epsilon = (1 + 2^-52) - 1 = 2^-52

Czy są jakieś liczby od 0 do epsilon? Mnóstwo... Np. minimalna liczba dodatnia reprezentowalna (normalna) wynosi:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022

W rzeczywistości istnieje około (1022 - 52 + 1)×2^52 = 4372995238176751616 liczb od 0 do epsilona, co stanowi około 47% wszystkich liczb dodatnich reprezentowalnych...

 182
Author: ybungalobill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 09:25:13

Test z pewnością nie jest taki sam jak someValue == 0. Cała idea liczb zmiennoprzecinkowych polega na tym, że przechowują wykładnik i significand. Reprezentują więc wartość z pewną liczbą binarnych cyfr znaczących o precyzji (53 W przypadku IEEE double). Wartości reprezentowalne są znacznie bardziej gęsto upakowane w pobliżu 0 niż w pobliżu 1.

Aby użyć bardziej znanego systemu dziesiętnego, Załóżmy, że zapisujesz wartość dziesiętną "do 4 cyfr znaczących" z wykładnikiem. Następnie Następna reprezentowalna wartość większa niż 1 to 1.001 * 10^0, a epsilon to 1.000 * 10^-3. Ale 1.000 * 10^-4 jest również reprezentowalne, zakładając, że wykładnik może przechowywać -4. Możesz mi wierzyć na słowo, że podwójne IEEE Może przechowywać wykładniki mniejsze niż wykładnik epsilon.

Na podstawie samego kodu nie można stwierdzić, czy użycie epsilon jest sensowne, czy też nie, należy przyjrzeć się kontekstowi. Może być tak, że epsilon jest rozsądnym oszacowaniem błędu w obliczeniach, że nie jest to jednak możliwe.]}

 17
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 09:26:57

Istnieją liczby, które istnieją między 0 a epsilon, ponieważ epsilon jest różnicą między 1 a następną najwyższą liczbą, która może być reprezentowana powyżej 1, a nie różnicą między 0 a następną najwyższą liczbą, która może być reprezentowana powyżej 0 (gdyby tak było, kod zrobiłby bardzo mało):-

#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;
}

Używając debugera, zatrzymaj program na końcu main i spójrz na wyniki, a zobaczysz, że epsilon / 2 różni się od epsilon, zero i one.

Więc ta funkcja pobiera wartości pomiędzy + / - epsilon i czyni je zerem.

 12
Author: Skizz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 09:11:52

Aproksymacja epsilonu (najmniejsza możliwa różnica) wokół liczby (1.0, 0.0, ...) można wydrukować za pomocą następującego programu. Wyświetla następujące wyjście:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
Trochę namysłu wyjaśnia, że epsilon staje się mniejszy im mniejsza jest liczba, której używamy do patrzenia na jego wartość epsilon, ponieważ wykładnik może dostosować się do wielkości tej liczby.

#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;
}
 5
Author: pbhd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 13:41:41

Załóżmy, że pracujemy z liczbami zmiennoprzecinkowymi, które mieszczą się w rejestrze 16-bitowym. Jest bit znakowy, 5-bitowy wykładnik i 10-bitowa mantissa.

Wartością tej liczby zmiennoprzecinkowej jest mantysa, interpretowana jako binarna wartość dziesiętna, razy dwa do potęgi wykładnika.

Około 1 wykładnik równa się zero. Najmniejszą cyfrą mantysy jest jedna część w 1024.

Blisko 1/2 wykładnika jest minus jeden, więc najmniejsza część mantysy jest połowa tak duża. Przy pięciobitowym wykładniku może osiągnąć wartość ujemną 16, w którym to momencie najmniejsza część mantysy jest warta jedną część w 32m. a przy ujemnym wykładniku 16 wartość jest około jednej części w 32k, znacznie bliżej zera niż epsilon wokół jednej, którą obliczyliśmy powyżej!

Teraz jest to zabawkowy model zmiennoprzecinkowy, który nie odzwierciedla wszystkich dziwactw rzeczywistego systemu zmiennoprzecinkowego , ale zdolność do odbijania wartości mniejszych niż epsilon jest dość podobna do rzeczywistego zmiennoprzecinkowego wartości punktowe.

 3
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-28 22:51:10

To zależy od precyzji twojego komputera. Spójrz na tabelę : możesz zobaczyć, że jeśli twój epsilon jest reprezentowany przez podwójne, ale twoja precyzja jest wyższa, porównanie nie jest równoważne

someValue == 0.0
Dobre pytanie!
 2
Author: Luca Davanzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 08:57:04

Nie możesz zastosować tego do 0, z powodu mantysy i części wykładniczych. Ze względu na wykładnik można przechowywać bardzo małe liczby, które są mniejsze niż epsilon, ale kiedy spróbujesz zrobić coś takiego (1.0 - "bardzo mała liczba") dostaniesz 1.0. Epsilon jest wskaźnikiem nie wartości, ale precyzji wartości, która jest w mantysie. Pokazuje ile poprawnych cyfr dziesiętnych możemy zapisać.

 2
Author: Arsenii Fomin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 08:57:11

Różnica pomiędzy X a następną wartością X różni się w zależności od X.
epsilon() jest tylko różnicą pomiędzy 1 a następną wartością 1.
Różnica pomiędzy 0 a następną wartością 0 nie jest epsilon().

Zamiast tego możesz użyć std::nextafter, aby porównać podwójną wartość z 0 w następujący sposób:

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;
}
 2
Author: Daniel Laügt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-06 18:29:34

Powiedzmy więc, że system nie rozróżnia 1.00000000000000000000000000000000000000001. to jest 1.0 i 1.0 + 1e-20. Czy są jeszcze jakieś wartości, które można przedstawić między -1e-20 a +1e-20?

 1
Author: cababunga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 08:50:36

W IEEE zmiennoprzecinkowym, pomiędzy najmniejszą niezerową wartością dodatnią a najmniejszą niezerową wartością ujemną, istnieją dwie wartości: zero dodatnie i zero ujemne. Testowanie, czy wartość znajduje się pomiędzy najmniejszymi niezerowymi wartościami, jest równoważne testowaniu równości z zerem; przypisanie może jednak mieć skutek, ponieważ zmieniłoby ujemne zero na dodatnie zero.

Można sobie wyobrazić, że format zmiennoprzecinkowy może mieć trzy wartości między najmniejsze skończone wartości dodatnie i ujemne: dodatnie infinitezymalne, niepodpisane zero i ujemne infinitezymalne. Nie znam żadnych formatów zmiennoprzecinkowych, które w rzeczywistości działają w ten sposób, ale takie zachowanie byłoby całkowicie rozsądne i prawdopodobnie lepsze niż IEEE(być może nie na tyle lepsze, aby warto było dodać dodatkowy sprzęt do jego obsługi, ale matematycznie 1/(1/INF), 1/(-1/INF) i 1/(1-1) powinny reprezentować trzy różne przypadki ilustrujące trzy różne zera). Nie wiem. wiedzieć, czy jakakolwiek norma C nakazuje, że podpisane infinitezymale, jeśli istnieją, musiałyby być równe zeru. Jeśli tego nie zrobią, kod taki jak powyższy mógłby zapewnić, że np. wielokrotne dzielenie liczby przez dwa w końcu przyniosłoby zero, a nie utknięcie na "nieskończoności".

 1
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-04 16:05:49

Również, dobrym powodem dla posiadania takiej funkcji jest usunięcie " denormaliów "(tych bardzo małych liczb, które nie mogą już używać implikowanej wiodącej" 1 " i mają specjalną reprezentację FP). Dlaczego chcesz to zrobić? Ponieważ niektóre maszyny (W szczególności niektóre starsze Pentium 4s) stają się bardzo, bardzo wolne podczas przetwarzania denormaliów. Inni po prostu stają się nieco wolniejsi. Jeśli Twoja aplikacja tak naprawdę nie potrzebuje tych bardzo małych liczb, płukanie ich do zera jest dobrym rozwiązaniem. Dobre miejsca rozważając to, są to ostatnie kroki wszelkich filtrów IIR lub funkcji rozpadu.

Zobacz także: Dlaczego zmiana 0.1 f na 0 spowalnia wydajność o 10x?

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

 0
Author: Dithermaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:09