Pergunta

Considere o seguinte código C #:

double result1 = 1.0 + 1.1 + 1.2;
double result2 = 1.2 + 1.0 + 1.1;

if (result1 == result2)
{
    ...
}

result1 deveria direito result2 sempre igual? A coisa é, isso não acontece. result1 é 3,3 e result2 é 3,3000000000000003. A única diferença é a ordem das constantes.

Eu sei que duplos são implementados de tal maneira a que as questões de arredondamento podem ocorrer. Estou ciente de que posso usar casas decimais em vez se eu precisar de uma precisão absoluta. Ou que eu possa usar Math.Round () na minha declaração se. Eu sou apenas um nerd que quer entender o que o compilador C # está fazendo. Alguém pode me dizer?

Editar:

Obrigado a todos que está até agora sugeriu lendo sobre aritmética de ponto flutuante e / ou falou sobre a imprecisão inerente de como as duplas alças da CPU. Mas eu sinto o principal impulso da minha pergunta ainda está sem resposta. Que é minha culpa por não fraseado-lo corretamente. Deixe-me colocar desta forma:

quebrar o código acima, eu esperaria as seguintes operações para estar acontecendo:

double r1 = 1.1 + 1.2;
double r2 = 1.0 + r1
double r3 = 1.0 + 1.1
double r4 = 1.2 + r3

Vamos supor que cada uma das adições acima tinha um erro de arredondamento (e1..e4 numerada). Então R1 contém arredondamento e1 erro, r2 inclui erros de arredondamento e1 + e2, R3 contém e3 e R4 contém e3 + e4.

Agora, eu não sei como exatamente como os erros de arredondamento acontecer, mas eu teria esperado e1 + e2 para igualar e3 + e4. Claramente isso não acontecer, mas que parece de alguma forma errado para mim. Outra coisa é que quando eu executar o código acima, eu não receber quaisquer erros de arredondamento. Isso é o que me faz pensar que é o compilador C # que está fazendo algo estranho em vez do CPU.

Eu sei que estou pedindo muito e talvez a melhor resposta que alguém pode dar é ir e fazer um doutoramento em design de CPU, mas eu apenas pensei que eu ia perguntar.

Editar 2

Olhando para o IL do meu exemplo de código original, é claro que é o compilador não a CPU que está fazendo isso:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
    .maxstack 1
    .locals init (
        [0] float64 result1,
        [1] float64 result2)
    L_0000: nop 
    L_0001: ldc.r8 3.3
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003
    L_0014: stloc.1 
    L_0015: ret 
}

O compilador está somando os números para mim!

Foi útil?

Solução

Eu teria esperado e1 + e2 para igualar e3 + e4.

Isso não é inteiramente diferente de espera

 floor( 5/3 ) + floor( 2/3 + 1 )

para igualar

 floor( 5/3 + 2/3 ) + floor( 1 )

, exceto que você está multiplicando por 2 ^ 53 antes de tomar a palavra.

Usando ponto flutuante de 12 bits precisão e truncagem com seus valores:

1.0            =  1.00000000000
1.1            =  1.00011001100
1.2            =  1.00110011001

1.0 + 1.1      = 10.00011001100 // extended during sum
r1 = 1.0 + 1.1 = 10.0001100110  // truncated to 12 bit
r1  + 1.2      = 11.01001100101 // extended during sum
r2 = r1  + 1.2 = 11.0100110010  // truncated to 12 bit

1.1 + 1.2      = 10.01001100110 // extended during sum
r3 = 1.1 + 1.2 = 10.0100110011  // truncated to 12 bit
r3 + 1.0       = 11.01001100110 // extended during sum
r4 = r3  + 1.0 = 11.0100110011  // truncated to 12 bit

Então, mudando a ordem das operações / truncamentos faz com que o erro para a mudança, e R4! = R2. Se você adicionar 1.1 e 1.2 neste sistema, o último bit carrega, por isso, não perdeu a truncagem. Se você adicionar 1.0 para 1.1, o último bit de 1,1 está perdido e por isso o resultado não é o mesmo.

Em uma encomenda, o arredondamento (por truncagem) remove um 1 final.

Na outra encomenda, o arredondamento remove uma 0 à direita duas vezes.

Um não é igual zero; de modo que os erros não são os mesmos.

Duplas têm muitos mais bits de precisão, e C # provavelmente usa o arredondamento em vez de truncagem, mas espero que este simples mostra modelo que diferentes erros pode acontecer com diferentes ordenamentos dos mesmos valores.

A diferença entre fp e matemática é que + é uma abreviação para 'adicionar então round' em vez de apenas adicionar.

Outras dicas

O c # compilador não está fazendo nada. A CPU é.

Se você tem um num registo CPU, e você, em seguida, adicionar B, o resultado armazenado no referido registo é A + B, aproximado ao flutuante de precisão usado

Se você seguida, adicione C, o erro acrescenta-se. Esta adição de erro não é uma operação transitiva, portanto, a diferença final.

o artigo clássico (O que cada cientista da computação deve saber sobre ponto flutuante aritmética) sobre o assunto. Este tipo de coisa é o que acontece com aritmética de ponto flutuante. É preciso um cientista da computação para lhe dizer que 1/3 + 1/3 + 1/3 is'nt igual a 1 ...

Ordem de operações de ponto flutuante é importante. não responder diretamente sua pergunta, mas você deve sempre ser cuidadosos números de ponto comparando flutuantes. É usual incluir uma tolerância:

double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
    ...
}

Isto pode ser de interesse: O que cada cientista computador deve saber sobre Floating ponto Aritmética

result1 deve sempre igual result2 certo?

errado . Isso é verdade em matemática, mas não em aritmética de ponto flutuante .

Você precisa ler alguns Análise Numérica cartilha .

Por que os erros não são os mesmos conforme a encomenda pode ser explicado com um exemplo diferente.

Vamos dizer que para os números abaixo de 10, ele pode armazenar todos os números, para que ele possa armazenar 1, 2, 3, e assim por diante até e incluindo 10, mas depois de 10, só pode armazenar cada segundo número, devido à perda interna de precisão, em outras palavras, só pode armazenar 10, 12, 14, etc.

Agora, com esse exemplo, você verá porque o seguinte produz resultados diferentes:

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10

O problema com números de ponto flutuante é que eles não podem ser representados com precisão, eo erro nem sempre seguir o mesmo caminho, assim que a ordem importa.

Por exemplo, 3,00000000003 + 3,00000000003 pode acabar sendo 6,00000000005 (aviso não 6 no final), mas 3,00000000003 + 2,99999999997 pode acabar sendo 6,00000000001, e com isso:

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002

mas, alterar a ordem:

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004

Por isso importa.

Agora, é claro, você pode ter sorte em que os exemplos acima equilibrar um ao outro, em que a primeira vai balançar por .xxx1 e o outro para baixo por .xxx1, dando-lhe .xxx3 em ambos, mas não há é garantia.

Você está realmente não usando os mesmos valores, porque os resultados intermediários são diferentes:

double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;

Porque duplas não pode representar valores decimais exatamente você obter resultados diferentes.

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