Estranho problema ao comparar os carros alegóricos no objective-C

Em algum momento de um algoritmo, preciso comparar o valor flutuante de uma propriedade de uma class com uma flutuação. Então eu faço isso:

if (self.scroller.currentValue <= 0.1) { } 

em que currentValue é uma propriedade flutuante.

No entanto, quando tenho igualdade e self.scroller.currentValue = 0.1 a instrução if não é atendida e o código não é executado! Eu descobri que eu posso consertar isso colocando 0.1 em float. Como isso:

 if (self.scroller.currentValue <= (float)0.1) { } 

Isso funciona bem.

Alguém pode explicar o meu porque isso está acontecendo? O 0.1 é definido como um duplo por padrão ou algo assim?

Obrigado.

Acredito, não tendo encontrado o padrão que diz isso, que ao comparar um float a um double o float é convertido em um double antes de comparar. Os números de ponto flutuante sem um modificador são considerados double em C.

No entanto, em C não há representação exata de 0,1 em carros alegóricos e duplos. Agora, usando um float, você recebe um pequeno erro. Usando um duplo, você recebe um erro ainda menor. O problema agora é que, ao lançar o float para um double você carrega o maior erro do float . É claro que eles não se foram, compare-se agora.

Em vez de usar (float)0.1 você poderia usar 0.1f que é um pouco melhor de ler.

O problema é, como você sugeriu em sua pergunta, que você está comparando um float com um double.

Existe um problema mais geral na comparação de floats, isso acontece porque quando você faz um cálculo em um número de ponto flutuante, o resultado do cálculo pode não ser exatamente o que você espera. É bastante comum que o último bit do float resultante esteja errado (embora a imprecisão possa ser maior do que apenas o último bit). Se você usar == para comparar dois carros alegóricos, então todos os bits têm que ser iguais para que os carros alegóricos sejam iguais. Se o seu cálculo der um resultado ligeiramente impreciso, eles não serão comparados quando você espera. Em vez de comparar os valores como esse, você pode compará-los para ver se eles são quase iguais. Para fazer isso, você pode pegar a diferença positiva entre os floats e ver se ele é menor que um determinado valor (chamado epsilon).

Para escolher um bom epsilon, você precisa entender um pouco sobre números de ponto flutuante. Os números de ponto flutuante funcionam de forma semelhante a representar um número para um determinado número de algarismos significativos. Se trabalharmos com 5 algarismos significativos e os resultados do seu cálculo no último dígito do resultado estiverem errados, então, 1.2345 terá um erro de + -0.0001, enquanto 1234500 terá um erro de + -100. Se você sempre basear sua margem de erro no valor 1.2345, então sua rotina de comparação será idêntica a == para todos os valores maiores que 10 (quando usar decimal). Isso é pior em binário, são todos valores maiores que 2. Isso significa que o épsilon que escolhemos tem que ser relativo ao tamanho dos floats que estamos comparando.

FLT_EPSILON é a lacuna entre 1 e a próxima flutuação mais próxima. Isso significa que pode ser um bom epsilon para escolher se seu número está entre 1 e 2, mas se seu valor for maior que 2 usando este épsilon é inútil porque o intervalo entre 2 e o próximo float mais próximo é maior que epsilon. Portanto, temos que escolher um épsilon em relação ao tamanho de nossos carros alegóricos (como o erro no cálculo é relativo ao tamanho de nossos carros alegóricos).

Uma boa rotina de comparação de ponto flutuante (ish) se parece com algo assim:

 bool compareNearlyEqual (float a, float b, unsigned epsilonMultiplier) { float epsilon; /* May as well do the easy check first. */ if (a == b) return true; if (a > b) { epsilon = scalbnf(1.0f, ilogb(a)) * FLT_EPSILON * epsilonMultiplier; } else { epsilon = scalbnf(1.0, ilogb(b)) * FLT_EPSILON * epsilonMultiplier; } return fabs (a - b) <= epsilon; } 

Essa rotina de comparação compara floats em relação ao tamanho da maior flutuação passada. scalbnf(1.0f, ilogb(a)) * FLT_EPSILON localiza a lacuna entre a e a próxima flutuação mais próxima. Isso é então multiplicado pelo epsilonMultiplier , de modo que o tamanho da diferença pode ser ajustado, dependendo de quão impreciso será o resultado do cálculo.

Você pode fazer uma simples rotina compareLessThan assim:

 bool compareLessThan (float a, float b, unsigned epsilonMultiplier) { if (compareNearlyEqual (a, b, epsilonMultiplier) return false; return a < b; } 

Você também pode escrever uma function compareGreaterThan muito semelhante.

Vale a pena notar que comparar carros alegóricos como esse nem sempre é o que você quer. Por exemplo, isso nunca vai achar que um float está próximo de 0, a menos que seja 0. Para corrigir isso, você precisaria decidir qual valor você achava que era próximo de zero e escrever um teste adicional para isso.

Às vezes, as imprecisões que você recebe não dependem do tamanho do resultado de um cálculo, mas dependem dos valores que você coloca em um cálculo. Por exemplo sin(1.0f + (float)(200 * M_PI)) dará um resultado muito menos preciso que sin(1.0f) (os resultados devem ser idênticos). Nesse caso, sua rotina de comparação teria que examinar o número que você colocou no cálculo para saber a margem de erro da resposta.

Duplas e floats têm valores diferentes para o mantissa store em binário (float é 23 bits, double 54). Estes quase nunca serão iguais.

O artigo do IEEE Float Point na wikipedia pode ajudar você a entender essa distinção.

Em C, um literal de ponto flutuante como 0.1 é um duplo, não um float. Como os tipos de itens de dados comparados são diferentes, a comparação é feita no tipo mais preciso (double). Em todas as implementações que eu conheço, float tem uma representação mais curta que double (geralmente expressa como algo como 6 x 14 casas decimais). Além disso, a aritmética está em binário e 1/10 não possui uma representação exata em binário.

Portanto, você está tomando um float de 0.1, que perde a precisão, estendendo-o para o dobro e esperando que ele seja igual a um 0.1, o que perde menos precisão.

Suponha que estivéssemos fazendo isso em decimal, com float sendo três dígitos e double sendo seis, e estávamos comparando com 1/3.

Nós temos o valor float armazenado sendo 0.333. Estamos comparando com um duplo com o valor 0.333333. Nós convertemos o float 0.333 para double 0.333000, e o achamos diferente.

0.1 é realmente um valor muito difícil de armazenar binário. Na base 2, 1/10 é a fração de repetição infinita

 0.0001100110011001100110011001100110011001100110011... 

Como vários apontaram, a comparação deve ser feita com uma constante exatamente igual à precisão.

Geralmente, em qualquer idioma, você não pode realmente contar com a igualdade de tipos flutuantes. No seu caso, já que parece que você tem mais controle, parece que 0.1 não é flutuante por padrão. Você provavelmente poderia descobrir isso com sizeof (0.1) (vs. sizeof (self.scroller.currentValue).

Converta-o em uma string e compare:

 NSString* numberA = [NSString stringWithFormat:@"%.6f", a]; NSString* numberB = [NSString stringWithFormat:@"%.6f", b]; return [numberA isEqualToString: numberB];