Aqui está o que está acontecendo em binário. Como sabemos, alguns valores de ponto flutuante não podem ser representados exatamente em binário, mesmo que possam ser representados exatamente em decimal. Esses três números são apenas exemplos desse fato.
Com este programa, produzo as representações hexadecimais de cada número e os resultados de cada adição.
public class Main{
public static void main(String args[]) {
double x = 23.53; // Inexact representation
double y = 5.88; // Inexact representation
double z = 17.64; // Inexact representation
double s = 47.05; // What math tells us the sum should be; still inexact
printValueAndInHex(x);
printValueAndInHex(y);
printValueAndInHex(z);
printValueAndInHex(s);
System.out.println("--------");
double t1 = x + y;
printValueAndInHex(t1);
t1 = t1 + z;
printValueAndInHex(t1);
System.out.println("--------");
double t2 = x + z;
printValueAndInHex(t2);
t2 = t2 + y;
printValueAndInHex(t2);
}
private static void printValueAndInHex(double d)
{
System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
}
}
O printValueAndInHex
método é apenas um auxiliar de impressora hexadecimal.
A saída é a seguinte:
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
Os primeiros 4 números são x
, y
, z
, e s
's representações hexadecimais. Na representação de ponto flutuante IEEE, os bits 2 a 12 representam o expoente binário , ou seja, a escala do número. (O primeiro bit é o bit de sinal e os bits restantes da mantissa .) O expoente representado é realmente o número binário menos 1023.
Os expoentes para os 4 primeiros números são extraídos:
sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Primeiro conjunto de adições
O segundo número ( y
) é de menor magnitude. Ao adicionar esses dois números para obter x + y
, os últimos 2 bits do segundo número ( 01
) são deslocados para fora do intervalo e não aparecem no cálculo.
A segunda adição adiciona x + y
e z
e acrescenta dois números da mesma escala.
Segundo conjunto de adições
Aqui, x + z
ocorre primeiro. Eles têm a mesma escala, mas produzem um número mais alto:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
A segunda adição adiciona x + z
e y
, e agora são eliminados 3 bits y
para adicionar os números ( 101
). Aqui, deve haver um arredondamento para cima, porque o resultado é o próximo número de ponto flutuante acima: 4047866666666666
para o primeiro conjunto de adições vs. 4047866666666667
para o segundo conjunto de adições. Esse erro é significativo o suficiente para aparecer na impressão do total.
Em conclusão, tenha cuidado ao executar operações matemáticas nos números IEEE. Algumas representações são inexatas e se tornam ainda mais inexatas quando as escalas são diferentes. Adicione e subtraia números de escala semelhante, se puder.
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
). Portanto, sim: tenha cuidado ao escolher a ordem das somas e outras operações. Algumas linguagens fornecem um built-in para executar somas de "alta precisão" (por exemplo, python'smath.fsum
), portanto, você pode considerar o uso dessas funções em vez do ingênuo algoritmo de soma.