Pergunta

Eu tenho uma função simples C #:

public static double Floor(double value, double step)
{
    return Math.Floor(value / step) * step;
}

que calcula o número maior, menor ou igual ao "valor", isto é múltiplo de "passo". Mas carece de precisão, como visto nos seguintes testes:

[TestMethod()]
public void FloorTest()
{
    int decimals = 6;
    double value = 5F;
    double step = 2F;
    double expected = 4F;
    double actual = Class.Floor(value, step);
    Assert.AreEqual(expected, actual);
    value = -11.5F;
    step = 1.1F;
    expected = -12.1F;
    actual = Class.Floor(value, step);
    Assert.AreEqual(Math.Round(expected, decimals),Math.Round(actual, decimals));
    Assert.AreEqual(expected, actual);
}

O primeiro e segundo afirma são ok, mas o terceiro falhar, porque o resultado é apenas iguais, até ao 6º lugar decimal. Por que é que? Existe alguma maneira de corrigir isso?

Atualizar Se eu depurar o teste que eu ver que os valores são iguais até a casa decimal 8ª vez da 6ª, talvez porque Math.Round introduz alguma imprecisão.

Nota No meu código de teste que eu escrevi o sufixo "F" (explícita constante float), onde eu quis dizer "D" (duplo), então se eu mudança que eu posso ter mais precisão.

Foi útil?

Solução

Se você omitir todos os sufixos de F (ie -12.1 vez de -12.1F) você terá a igualdade para alguns dígitos mais. Suas constantes (e especialmente os valores esperados) são agora flutua por causa da F. Se você está fazendo isso de propósito, por favor, explicar.

Mas para o resto concordo com as outras respostas na comparação de valores duplos ou flutuar pela igualdade, não é apenas confiável.

Outras dicas

Na verdade, eu tipo de desejo não tinham implementado o operador == para carros alegóricos e duplos. É quase sempre a coisa errada a fazer para sempre perguntar se um casal ou uma bóia é igual a qualquer outro valor.

aritmética de ponto flutuante em computadores não são exatas Ciência :).

Se você quiser precisão exata para um número pré-definido de decimais usar Decimal em vez de casal ou aceitar um intervalo menor.

http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Por exemplo, o não-representatividade de 0,1 e 0,01 (em binário) significa que o resultado da tentativa de quadrado 0,1 é nem 0,01 nem o número representável mais próximo a ele.

Use apenas de ponto flutuante se você quiser interpretação de uma máquina (binário) de sistemas de número. Você não pode representar 10 cêntimos.

Se você quiser precisão, use System.Decimal. Se você quiser velocidade, use System.Double (ou System.Float). Números de ponto flutuante não são números "infinita precisão", e, portanto, afirmar a igualdade deve incluir uma tolerância. Enquanto seus números têm um número razoável de dígitos significativos, isso é ok.

  • Se você estiver olhando para fazer matemática em números muito grandes ou muito pequenos, não use float ou double.
  • Se você precisar de precisão infinita, não use float ou double.
  • Se você está agregando um número muito grande de valores, não use float ou double (os erros serão compostos em si).
  • Se você precisa de velocidade e tamanho, use float ou double.

Consulte esta resposta (também por mim) para uma análise detalhada de como precisão afeta o resultado de suas operações matemáticas.

Verifique as respostas a esta pergunta: é seguro para verificar valores de ponto para a igualdade flutuante para 0?

Realmente, basta verificar para "dentro da tolerância de ..."

flutua e duplas não pode armazenar com precisão todos os números. Esta é uma limitação com o sistema IEEE de ponto flutuante. Para se ter precisão fiel você precisa usar uma biblioteca de matemática mais avançada.

Se você não precisa de precisão passado um certo ponto, então talvez decimal irá funcionar melhor para você. Ele tem uma precisão maior do que o dobro.

Para o problema semelhante, eu acabo usando a seguinte implementação que parece sucesso maior parte do meu caso de teste (até 5 precisão dígito):

public static double roundValue(double rawValue, double valueTick)
{
    if (valueTick <= 0.0) return 0.0;

    Decimal val = new Decimal(rawValue);
    Decimal step = new Decimal(valueTick);
    Decimal modulo = Decimal.Round(Decimal.Divide(val,step));

    return Decimal.ToDouble(Decimal.Multiply(modulo, step));
}

Às vezes, o resultado é mais preciso do que você esperaria de estrita: FP IEEE 754. Isso porque HW usa mais bits para o cálculo. Consulte C # especificação e este artigo

Java tem palavra-chave strictfp e C ++ têm opções de compilador. Eu perca essa opção em .NET

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top