Eu tenho testado este código em https://dotnetfiddle.net/ :
using System;
public class Program
{
const float scale = 64 * 1024;
public static void Main()
{
Console.WriteLine(unchecked((uint)(ulong)(1.2 * scale * scale + 1.5 * scale)));
Console.WriteLine(unchecked((uint)(ulong)(scale* scale + 7)));
}
}
Se eu compilar com o .NET 4.7.2, recebo
859091763
7
Mas se eu fizer o Roslyn ou o .NET Core, recebo
859091763
0 0
Por que isso acontece?
ulong
está sendo ignorado no último caso, então está acontecendo na conversãofloat
->int
.ulong
afeta o resultado.Respostas:
Minhas conclusões estavam incorretas. Veja a atualização para mais detalhes.
Parece um bug no primeiro compilador que você usou. Zero é o resultado correto neste caso .A ordem das operações ditada pela especificação C # é a seguinte:scale
porscale
, produzindoa
a + 7
, produzindob
b
paraulong
, produzindoc
c
parauint
, produzindod
As duas primeiras operações deixam você com um valor flutuante de
b = 4.2949673E+09f
. Sob a aritmética padrão de ponto flutuante, é isso4294967296
( você pode vê-lo aqui ). Isso se encaixaulong
muito bem, entãoc = 4294967296
, mas é exatamente um a mais do queuint.MaxValue
, portanto, viagens de ida e volta para0
, portantod = 0
. Agora, surpresa surpresa, já que aritmética de ponto flutuante é funky,4.2949673E+09f
e4.2949673E+09f + 7
é exatamente o mesmo número em IEEE 754. Entãoscale * scale
lhe dará o mesmo valor de umfloat
, comoscale * scale + 7
,a = b
, então o segundo operações é basicamente um não-op.O compilador Roslyn executa (algumas) operações const em tempo de compilação e otimiza toda essa expressão para
0
.Novamente, esse é o resultado correto , eo compilador pode executar qualquer otimização que resultará no mesmo comportamento exato do código sem elas.Meu palpite é que o compilador .NET 4.7.2 que você usou também tenta otimizar isso, mas tem um erro que faz com que ele avalie a conversão em um lugar errado. Naturalmente, se você primeiro converterscale
emuint
e depois executar a operação, obterá7
, porquescale * scale
ida e volta0
e depois adiciona7
. Mas isso é inconsistente com o resultado que você obteria ao avaliar as expressões passo a passo no tempo de execução . Novamente, a causa raiz é apenas um palpite ao analisar o comportamento produzido, mas, considerando tudo o que afirmei acima, estou convencido de que essa é uma violação de especificação do lado do primeiro compilador.ATUALIZAR:
Eu fiz uma brincadeira. Existe um pouco da especificação C # que eu não sabia que existia ao escrever a resposta acima:
O C # garante que as operações forneçam um nível de precisão pelo menos no nível da IEEE 754, mas não necessariamente exatamente isso. Não é um bug, é um recurso de especificação. O compilador Roslyn está em seu direito de avaliar a expressão exatamente como IEEE 754 especifica, eo outro compilador está em seu direito de deduzir que
2^32 + 7
é7
quando colocado emuint
.Sinto muito pela minha primeira resposta enganosa, mas pelo menos todos aprendemos algo hoje.
fonte
scale
pelo valor flutuante e avaliasse todo o resto em tempo de execução, o resultado seria o mesmo. Você pode elaborar?O ponto aqui é (como você pode ver nos documentos ) que os valores flutuantes podem ter apenas uma base de até 2 ^ 24 . Portanto, quando você atribui um valor de 2 ^ 32 ( 64 * 2014 * 164 * 1024 = 2 ^ 6 * 2 ^ 10 * 2 ^ 6 * 2 ^ 10 = 2 ^ 32 ), ele se torna, na verdade, 2 ^ 24 * 2 ^ 8 , que é 4294967000 . Adicionar 7 só será adicionado à parte truncada pela conversão para ulong .
Se você mudar para o dobro , que tem uma base de 2 ^ 53 , funcionará para o que você deseja.
Isso pode ser um problema de tempo de execução, mas, nesse caso, é um problema de tempo de compilação, porque todos os valores são constantes e serão avaliados pelo compilador.
fonte
Antes de tudo, você está usando um contexto não verificado, que é uma instrução para o compilador. Você tem certeza, como desenvolvedor, de que o resultado não excederá o tipo e que você não verá nenhum erro de compilação. No seu cenário, você realmente está transbordando de propósito e espera um comportamento consistente em três compiladores diferentes, um dos quais provavelmente é compatível com versões anteriores ao histórico em comparação com o Roslyn e o .NET Core, que são novos.
A segunda coisa é que você está misturando conversões implícitas e explícitas. Não tenho certeza sobre o compilador Roslyn, mas definitivamente os compiladores .NET Framework e .NET Core podem usar otimizações diferentes para essas operações.
O problema aqui é que a primeira linha do seu código usa apenas valores / tipos de ponto flutuante, mas a segunda linha é uma combinação de valores / tipos de ponto flutuante e valor / tipo integral.
Caso você faça um tipo de ponto flutuante inteiro imediatamente (7> 7.0), obterá o mesmo resultado para todas as três fontes compiladas.
Então, eu diria o contrário do que o V0ldek respondeu e que é "O bug (se realmente é um bug) é mais provável nos compiladores Roslyn e .NET Core".
Outro motivo para acreditar que é o resultado dos primeiros resultados de computação não verificados são iguais para todos e é o valor que excede o valor máximo do
UInt32
tipo.Menos um está lá quando começamos do zero, que é um valor difícil de subtrair. Se meu entendimento matemático de estouro estiver correto, começaremos do próximo número após o valor máximo.
ATUALIZAR
De acordo com o comentário jalsh
O comentário dele está correto. No caso de usarmos float, você ainda recebe 0 para Roslyn e .NET Core, mas, por outro lado, usa resultados duplos em 7.
Fiz alguns testes adicionais e as coisas ficaram ainda mais estranhas, mas no final tudo faz sentido (pelo menos um pouco).
O que eu assumo é que o compilador .NET Framework 4.7.2 (lançado em meados de 2018) realmente usa otimizações diferentes dos compiladores .NET Core 3.1 e Roslyn 3.4 (lançado no final de 2019). Essas diferentes otimizações / cálculos são puramente usadas para valores constantes conhecidos em tempo de compilação. É por isso que houve a necessidade de usar a
unchecked
palavra-chave como compilador já sabe que está ocorrendo um estouro, mas computações diferentes foram usadas para otimizar a IL final.O mesmo código fonte e quase o mesmo IL, exceto a instrução IL_000a. Um compilador calcula 7 e outro 0.
Código fonte
IL do .NET Framework (x64)
Filial do compilador Roslyn (set 2019) IL
Começa a seguir o caminho certo quando você adiciona expressões não constantes (por padrão são
unchecked
) como abaixo.O que gera "exatamente" a mesma IL pelos dois compiladores.
IL do .NET Framework (x64)
Filial do compilador Roslyn (set 2019) IL
Portanto, no final, acredito que a razão para um comportamento diferente é apenas uma versão diferente da estrutura e / ou compilador que usa otimizações / computação diferentes para expressões constantes, mas em outros casos o comportamento é o mesmo.
fonte