IT story

엡실론을 사용하여 double을 0으로 비교

hot-time 2020. 5. 5. 19:38
반응형

엡실론을 사용하여 double을 0으로 비교


오늘 저는 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에서 엡실론 사이의 숫자가 있습니까? Plenty ... 예를 들어 최소 양수 (정상) 숫자는 다음과 같습니다.

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

실제로 (1022 - 52 + 1)×2^52 = 43729952381767516160과 엡실론 사이의 숫자 가 있으며 이는 모든 양의 표현 가능한 숫자의 47 %입니다.


테스트는 확실히 동일하지 않습니다 someValue == 0. 부동 소수점 숫자의 전체 아이디어는 지수와 의미를 저장한다는 것입니다. 따라서 특정 수의 이진 유효 정밀도 수치 (IEEE double의 경우 53)를 가진 값을 나타냅니다. 표현 가능한 값은 1에 비해 0에 훨씬 더 밀집되어 있습니다.

보다 친숙한 10 진수 시스템을 사용하려면 지수와 함께 "4 자리 유효 숫자까지"10 진수 값을 저장한다고 가정하십시오. 그런 다음 표현할 수있는 값보다 크 1입니다 1.001 * 10^0, 그리고 epsilon이다 1.000 * 10^-3. 그러나 1.000 * 10^-4지수가 -4를 저장할 수 있다고 가정하면 표현할 수 있습니다. IEEE double 은의 지수보다 적은 지수 저장할 수 있다는 내 말을들 있습니다 epsilon.

이 코드만으로는 epsilon구체적으로 바운드 로 사용하는 것이 타당한 지 여부를 알 수 없으므로 컨텍스트를 살펴 봐야합니다. 그것은 그 수있다 epsilon생성 된 계산의 오차의 적절한 추정치 someValue, 그리고 그렇지 않은 것일 수있다.


epsilon은 1과 1보다 위에 표시 될 수있는 다음 가장 높은 숫자의 차이이며 0과 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 사이의 값을 취하여 0으로 만듭니다.


An aproximation of epsilon (smallest possible difference) around a number (1.0, 0.0, ...) can be printed with the following program. It prints the following output:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
A little thinking makes it clear, that the epsilon gets smaller the more smaller the number is we use for looking at its epsilon-value, because the exponent can adjust to the size of that number.

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

Suppose we are working with toy floating point numbers that fit in a 16 bit register. There is a sign bit, a 5 bit exponent, and a 10 bit mantissa.

The value of this floating point number is the mantissa, interpreted as a binary decimal value, times two to the power of the exponent.

Around 1 the exponent equals zero. So the smallest digit of the mantissa is one part in 1024.

Near 1/2 the exponent is minus one, so the smallest part of the mantissa is half as large. With a five bit exponent it can reach negative 16, at which point the smallest part of the mantissa is worth one part in 32m. And at negative 16 exponent, the value is around one part in 32k, much closer to zero than the epsilon around one we calculated above!

Now this is a toy floating point model that does not reflect all the quirks of a real floating point system , but the ability to reflect values smaller than epsilon is reasonably similar with real floating point values.


The difference between X and the next value of X varies according to X.
epsilon() is only the difference between 1 and the next value of 1.
The difference between 0 and the next value of 0 is not epsilon().

Instead you can use std::nextafter to compare a double value with 0 as the following:

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

I think that depend on the precision of your computer. Take a look on this table: you can see that if your epsilon is represented by double, but your precision is higher, the comparison is not equivalent to

someValue == 0.0

Good question anyway!


You can't apply this to 0, because of mantissa and exponent parts. Due to exponent you can store very little numbers, which are smaller than epsilon, but when you try to do something like (1.0 - "very small number") you'll get 1.0. Epsilon is an indicator not of value, but of value precision, which is in mantissa. It shows how many correct consequent decimal digits of number we can store.


So let's say system cannot distinguish 1.000000000000000000000 and 1.000000000000000000001. that is 1.0 and 1.0 + 1e-20. Do you think there still are some values that can be represented between -1e-20 and +1e-20?


With IEEE floating-point, between the smallest non-zero positive value and the smallest non-zero negative value, there exist two values: positive zero and negative zero. Testing whether a value is between the smallest non-zero values is equivalent to testing for equality with zero; the assignment, however, may have an effect, since it would change a negative zero to a positive zero.

It would be conceivable that a floating-point format might have three values between the smallest finite positive and negative values: positive infinitesimal, unsigned zero, and negative infinitesimal. I am not familiar with any floating-point formats that in fact work that way, but such a behavior would be perfectly reasonable and arguably better than that of IEEE (perhaps not enough better to be worth adding extra hardware to support it, but mathematically 1/(1/INF), 1/(-1/INF), and 1/(1-1) should represent three distinct cases illustrating three different zeroes). I don't know whether any C standard would mandate that signed infinitesimals, if they exist, would have to compare equal to zero. If they do not, code like the above could usefully ensure that e.g. dividing a number repeatedly by two would eventually yield zero rather than being stuck on "infinitesimal".


Also, a good reason for having such a function is to remove "denormals" (those very small numbers that can no longer use the implied leading "1" and have a special FP representation). Why would you want to do this? Because some machines (in particular, some older Pentium 4s) get really, really slow when processing denormals. Others just get somewhat slower. If your application doesn't really need these very small numbers, flushing them to zero is a good solution. Good places to consider this are the last steps of any IIR filters or decay functions.

See also: Why does changing 0.1f to 0 slow down performance by 10x?

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

참고URL : https://stackoverflow.com/questions/13698927/compare-double-to-zero-using-epsilon

반응형