Compare o dobro a zero usando o epsilon


214

Hoje, eu estava procurando por um código C ++ (escrito por outra pessoa) e encontrei esta seção:

double someValue = ...
if (someValue <  std::numeric_limits<double>::epsilon() && 
    someValue > -std::numeric_limits<double>::epsilon()) {
  someValue = 0.0;
}

Estou tentando descobrir se isso faz sentido.

A documentação para epsilon()diz:

A função retorna a diferença entre 1 e o menor valor maior que 1 que é representável [por um duplo].

Isso também se aplica a 0, ou epsilon()seja, o menor valor é maior que 0? Ou existem números entre 0e 0 + epsilonque podem ser representados por um double?

Caso contrário, a comparação não é equivalente a someValue == 0.0?


3
O epsilon em torno de 1 provavelmente será muito maior do que em torno de 0, portanto, provavelmente haverá valores entre 0 e 0 + epsilon_at_1. Eu acho que o autor desta seção queria usar algo pequeno, mas ele não queria usar uma constante mágica, então ele apenas usou esse valor essencialmente arbitrário.
Enobayram 04/12/12

2
A comparação de números de ponto flutuante é difícil, e o uso de epsilon ou valor limite é até incentivado. Por favor, consulte: cs.princeton.edu/introcs/91float e cygnus-software.com/papers/comparingfloats/comparingfloats.htm
Aditya Kumar Pandey

40
O primeiro link é 403.99999999
graham.reeds 4/12/12

6
OMI, neste caso, o uso de numeric_limits<>::epsiloné enganoso e irrelevante. O que queremos é assumir 0 se o valor real diferir não mais do que alguns ε de 0. E ε deve ser escolhido com base na especificação do problema, não em um valor dependente da máquina. Eu suspeitaria que o epsilon atual é inútil, já que apenas algumas operações de FP podem acumular um erro maior que isso.
Andrey Vihrov 04/12/12

1
+1. O epsilon não é o menor possível, mas pode servir a um determinado objetivo na maioria das tarefas práticas de engenharia, se você souber de que precisão precisa e o que está fazendo.
precisa

Respostas:


192

Supondo que o IEEE de 64 bits seja duplo, há uma mantissa de 52 bits e um expoente de 11 bits. Vamos dividir em pedaços:

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

O menor número representável maior que 1:

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

Portanto:

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

Existem números entre 0 e epsilon? Muito ... Por exemplo, o número mínimo representável positivo (normal) é:

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

De fato, existem (1022 - 52 + 1)×2^52 = 4372995238176751616números entre 0 e epsilon, que representa 47% de todos os números representáveis ​​positivos ...


27
Tão estranho que você pode dizer "47% dos números positivos" :)
configurador

13
@ configurador: Nah, você não pode dizer isso (não existe nenhuma medida finita 'natural'). Mas você pode dizer "47% dos números representáveis positivos ".
Yakov Galka

1
@ybungalobill Não consigo descobrir. O expoente possui 11 bits: 1 bit de sinal e 10 bits de valor. Por que 2 ^ -1022 e não 2 ^ -1024 é o menor número positivo?
Pavlo Dyban

3
@ PavloDyban: simplesmente porque os expoentes não têm um bit de sinal. Eles são codificados como deslocamentos: se o expoente codificado for 0 <= e < 2048, a mantissa será multiplicada por 2 à potência de e - 1023. Por exemplo, expoente de 2^0é codificado como e=1023, 2^1como e=1024e 2^-1022como e=1. O valor de e=0é reservado para subnormais e zero real.
Yakov Galka

2
@ PavloDyban: também 2^-1022é o menor número normal . O menor número é realmente 0.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^-1022 = 2^-1074. Isso é subnormal, o que significa que a parte da mantissa é menor que 1 e, portanto, é codificada com o expoente e=0.
Yakov Galka

17

O teste certamente não é o mesmo que someValue == 0. A idéia dos números de ponto flutuante é que eles armazenam um expoente e um significando. Eles, portanto, representam um valor com um certo número de números binários significativos de precisão (53 no caso de um duplo IEEE). Os valores representáveis ​​são muito mais densamente compactados perto de 0 do que perto de 1.

Para usar um sistema decimal mais familiar, suponha que você armazene um valor decimal "até 4 algarismos significativos" com expoente. Em seguida, o próximo valor representável maior que 1é 1.001 * 10^0e epsiloné 1.000 * 10^-3. Mas 1.000 * 10^-4também é representável, assumindo que o expoente possa armazenar -4. Você pode acreditar que um duplo IEEE pode armazenar expoentes menos que o expoente deepsilon .

Você não pode dizer apenas a partir deste código se faz sentido ou não usar epsilonespecificamente como limite, é necessário examinar o contexto. Pode ser que epsilonseja uma estimativa razoável do erro no cálculo produzido someValue, e pode ser que não seja.


2
Bom ponto, mas mesmo se for esse o caso, uma prática melhor seria manter o erro vinculado em uma variável com nome razoável e usá-lo na comparação. Tal como está, não é diferente de uma constante mágica.
Enobayram 04/12/12

Talvez eu devesse ter sido mais claro na minha pergunta: não questionei se o epsilon era um "limiar" suficientemente grande para cobrir o erro computacional, mas se essa comparação é igual someValue == 0.0ou não.
Sebastian Krysmanski

13

Existem números que existem entre 0 e epsilon porque epsilon é a diferença entre 1 e o próximo número mais alto que pode ser representado acima de 1 e não a diferença entre 0 e o próximo número mais alto que pode ser representado acima de 0 (se fosse, esse código faria muito pouco): -

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

Usando um depurador, pare o programa no final do main e observe os resultados e você verá que o epsilon / 2 é distinto do epsilon, zero e um.

Portanto, essa função pega valores entre +/- epsilon e os torna zero.


5

Uma aproximação de epsilon (a menor diferença possível) em torno de um número (1,0, 0,0, ...) pode ser impressa com o seguinte programa. Ele imprime a seguinte saída:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
Um pouco de reflexão deixa claro que o epsilon fica menor, quanto menor o número que usamos para analisar seu valor epsilon, porque o expoente pode se ajustar ao tamanho desse número.

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

2
Quais implementações você verificou? Definitivamente, este não é o caso do GCC 4.7.
Anton Golov 04/12/12

3

Suponha que estamos trabalhando com números de ponto flutuante de brinquedo que se encaixam em um registro de 16 bits. Há um bit de sinal, um expoente de 5 bits e uma mantissa de 10 bits.

O valor desse número de ponto flutuante é a mantissa, interpretada como um valor decimal binário, multiplicado por dois à potência do expoente.

Em torno de 1, o expoente é igual a zero. Portanto, o menor dígito da mantissa é uma parte em 1024.

Quase 1/2 do expoente é menos um, então a menor parte da mantissa é metade do tamanho. Com um expoente de cinco bits, pode chegar a 16 negativos, quando a menor parte da mantissa vale uma parte em 32m. E no expoente 16 negativo, o valor está em torno de uma parte em 32k, muito mais próximo de zero do que o epsilon em torno de um calculado acima!

Agora, este é um modelo de ponto flutuante de brinquedo que não reflete todas as peculiaridades de um sistema real de ponto flutuante, mas a capacidade de refletir valores menores que epsilon é razoavelmente semelhante aos valores reais de ponto flutuante.


3

A diferença entre Xe o próximo valor de Xvaria de acordo com X.
epsilon()é apenas a diferença entre 1e o próximo valor de 1.
A diferença entre 0e o próximo valor de 0não é epsilon().

Em vez disso, você pode usar std::nextafterpara comparar um valor duplo com 0o seguinte:

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

Eu acho que isso depende da precisão do seu computador. Dê uma olhada nesta tabela : você pode ver que, se o seu epsilon for representado pelo dobro, mas sua precisão for maior, a comparação não será equivalente a

someValue == 0.0

Boa pergunta de qualquer maneira!


2

Você não pode aplicar isso a 0, devido a partes de mantissa e expoente. Devido ao expoente, você pode armazenar números muito pequenos, menores que o epsilon, mas quando você tenta fazer algo como (1.0 - "número muito pequeno"), você obtém 1.0. Epsilon é um indicador não de valor, mas de precisão de valor, que está em mantissa. Ele mostra quantos dígitos decimais consequentes corretos podemos armazenar.


2

Com o ponto flutuante IEEE, entre o menor valor positivo diferente de zero e o menor valor negativo diferente de zero, existem dois valores: zero positivo e zero negativo. Testar se um valor está entre os menores valores diferentes de zero é equivalente a testar a igualdade com zero; a atribuição, no entanto, pode ter um efeito, pois alteraria um zero negativo para um positivo.

Seria concebível que um formato de ponto flutuante pudesse ter três valores entre os menores valores positivos e negativos finitos: infinitesimal positivo, zero não assinado e infinitesimal negativo. Não conheço nenhum formato de ponto flutuante que funcione dessa maneira, mas esse comportamento seria perfeitamente razoável e sem dúvida melhor do que o do IEEE (talvez não seja o suficiente para valer a pena adicionar hardware extra para suportá-lo, mas matematicamente 1 / (1 / INF), 1 / (- 1 / INF) e 1 / (1-1) devem representar três casos distintos que ilustram três zeros diferentes). Não sei se algum padrão C determinaria que infinitesimais assinados, se existirem, precisariam comparar igual a zero. Se não o fizerem, código como o acima poderia garantir que, por exemplo,


Não é "1 / (1-1)" (do seu exemplo) infinito em vez de zero?
Sebastian Krysmanski

As quantidades (1-1), (1 / INF) e (-1 / INF) representam zero, mas a divisão de um número positivo por cada uma delas deveria, em teoria, produzir três resultados diferentes (a matemática do IEEE considera as duas primeiras idênticas )
Super12

1

Então, digamos que o sistema não possa distinguir 1.000000000000000000000 e 1.0000000000000000000000001. isso é 1.0 e 1.0 + 1e-20. Você acha que ainda existem alguns valores que podem ser representados entre -1e-20 e + 1e-20?


Exceto pelo zero, não acho que haja valores entre -1e-20 e + 1e-20. Mas só porque acho que isso não faz com que seja verdade.
Sebastian Krysmanski

@SebastianKrysmanski: não é verdade, existem muitos valores de ponto flutuante entre 0 e epsilon. Porque é ponto flutuante , não ponto fixo.
precisa

O menor valor representável que é distinto de zero é limitado pelo número de bits alocados para representar o expoente. Portanto, se o dobro tiver um expoente de 11 bits, o menor número será 1e-1023.
Cababunga

0

Além disso, uma boa razão para ter essa função é remover "denormals" (números muito pequenos que não podem mais usar o "1" inicial implícito e têm uma representação FP especial). Por que você quer fazer isso? Porque algumas máquinas (em particular, algumas Pentium 4s mais antigas) ficam muito, muito lentas ao processar denormals. Outros ficam um pouco mais lentos. Se seu aplicativo realmente não precisar desses números muito pequenos, liberá-los para zero é uma boa solução. Bons locais para considerar isso são os últimos passos de quaisquer filtros IIR ou funções de decaimento.

Veja também: Por que alterar 0,1f para 0 diminui o desempenho em 10x?

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


1
Isso remove muito mais números do que apenas números desnormalizados. Altera a constante de Planck ou a massa de um elétron para zero, o que fornecerá resultados muito, muito errados, se você usar esses números.
gnasher729
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.