Transmitindo um resultado para flutuar no método retornando o resultado das mudanças de flutuação

Por que esse código imprime False no .NET 4? Parece que algum comportamento inesperado está sendo causado pelo casting explícito.

Eu gostaria de uma resposta além de “ponto flutuante é impreciso” ou “não faça isso”.

 float a(float x, float y) { return ( x * y ); } float b(float x, float y) { return (float)( x * y ); } void Main() { Console.WriteLine( a( 10f, 1f/10f ) == b( 10f, 1f/10f ) ); } 

PS: Esse código veio de um teste de unidade, não de código de liberação. O código foi escrito dessa maneira deliberadamente. Eu suspeitava que iria falhar eventualmente, mas eu queria saber exatamente quando e exatamente por quê. A resposta comprova a validade desta técnica porque fornece uma compreensão que vai além da compreensão usual do determinismo de ponto flutuante. E esse foi o ponto de escrever esse código dessa maneira; exploração deliberada.

PPS: O teste de unidade estava passando no .NET 3.5, mas agora falha após a atualização para o .NET 4.

O comentário de David está correto, mas insuficientemente forte. Não há garantia de que fazer esse cálculo duas vezes no mesmo programa produzirá os mesmos resultados.

A especificação C # é extremamente clara neste ponto:


Operações de ponto flutuante podem ser executadas com maior precisão do que o tipo de resultado da operação. Por exemplo, algumas arquiteturas de hardware suportam um tipo de ponto flutuante “estendido” ou “longo duplo” com maior alcance e precisão do que o tipo duplo, e implicitamente executam todas as operações de ponto flutuante usando esse tipo de precisão mais alta. Somente com custo excessivo no desempenho essas arquiteturas de hardware podem executar operações de ponto flutuante com menos precisão e, em vez de exigir uma implementação para perder desempenho e precisão, o C # permite que um tipo de precisão mais alta seja usado para todas as operações de ponto flutuante . Além de fornecer resultados mais precisos, isso raramente tem efeitos mensuráveis. No entanto, em expressões da forma x * y / z , onde a multiplicação produz um resultado que está fora do intervalo duplo, mas a divisão subsequente traz o resultado temporário de volta para o intervalo duplo, o fato de que a expressão é avaliada em um maior O formato de intervalo pode causar um resultado finito a ser produzido em vez de um infinito.


O compilador C #, o jitter e o tempo de execução têm ampla latitude para fornecer resultados mais precisos do que o requerido pela especificação, a qualquer momento, por um capricho – eles não precisam escolher fazê-lo consistentemente e, de fato, eles não.

Se você não gosta disso, então não use números binários de ponto flutuante; use decimais ou racionais de precisão arbitrária.

Eu não entendo porque lançar para flutuar em um método que retorna float faz a diferença que faz

Ponto excelente.

Seu programa de amostra demonstra como pequenas alterações podem causar grandes efeitos. Você observa que em algumas versões do tempo de execução, a conversão para float explicitamente fornece um resultado diferente do que não fazer isso. Quando você explicitamente converter para float, o compilador C # dá uma dica para o tempo de execução para dizer “tirar essa coisa do modo de alta precisão extra se você estiver usando essa otimização”. Como a especificação observa, isso tem um custo potencial de desempenho.

Que isso aconteça para arredondar para a “resposta certa” é apenas um acidente feliz; a resposta certa é obtida porque neste caso perdendo precisão aconteceu perdê-lo na direção correta .

Como o .net 4 é diferente?

Você pergunta qual é a diferença entre tempos de execução de 3,5 e 4,0; A diferença é que, na versão 4.0, o jitter decide ir para maior precisão em seu caso particular, e o jitter 3.5 não escolhe. Isso não significa que essa situação fosse impossível em 3,5; isso foi possível em todas as versões do tempo de execução e em todas as versões do compilador C #. Você acabou de passar por um caso em que, na sua máquina, eles diferem em seus detalhes. Mas o jitter sempre teve permissão para fazer essa otimização, e sempre fez isso a seu bel-prazer.

O compilador C # também está totalmente dentro de seus direitos de optar por fazer otimizações semelhantes ao calcular flutuações constantes em tempo de compilation. Dois cálculos aparentemente idênticos em constantes podem ter resultados diferentes dependendo dos detalhes do estado de tempo de execução do compilador.

Mais geralmente, sua expectativa de que os números de ponto flutuante devam ter as propriedades algébricas de números reais está completamente fora de linha com a realidade; eles não têm essas propriedades algébricas. As operações de ponto flutuante não são nem associativas ; eles certamente não obedecem às leis dos inversos multiplicativos como você parece esperar que aconteçam. Os números de ponto flutuante são apenas uma aproximação da aritmética real; uma aproximação que é próxima o suficiente para, digamos, simular um sistema físico, ou computar statistics resumidas, ou algo parecido.

Eu não tenho compilador da Microsoft agora e o Mono não tem esse efeito. Até onde sei, o GCC 4.3+ usa gmp e mpfr para calcular algumas coisas em tempo de compilation . O compilador C # pode fazer o mesmo para methods não virtuais, estáticos ou privados no mesmo assembly. O casting explícito pode interferir com essa otimização (mas não vejo razão para não ter o mesmo comportamento). Ou seja, ele pode inline com o cálculo da expressão constante para algum nível (para b() pode ser, por exemplo, até o casting).

O GCC também tem a otimização que promove a operação para maior precisão, se isso fizer sentido.

Então, eu consideraria a otimização como razão potencial. Mas, para ambos, não vejo razão para que a definição explícita do resultado possa ter algum significado adicional, como “estar mais próximo do padrão”.