No programa a seguir, você pode ver que cada valor um pouco menor que o .5
arredondado, exceto por 0.5
.
for (int i = 10; i >= 0; i--) {
long l = Double.doubleToLongBits(i + 0.5);
double x;
do {
x = Double.longBitsToDouble(l);
System.out.println(x + " rounded is " + Math.round(x));
l--;
} while (Math.round(x) > i);
}
impressões
10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 1
0.4999999999999999 rounded is 0
Estou usando o Java 6, atualização 31.
java
floating-point
double
rounding
Peter Lawrey
fonte
fonte
0.5
ao número e depois usandofloor
; O Java 7 não o documenta mais dessa maneira (provavelmente / provavelmente porque eles o corrigiram).Respostas:
Sumário
No Java 6 (e presumivelmente antes),
round(x)
é implementado comofloor(x+0.5)
. 1 Este é um bug de especificação, exatamente para este caso patológico. 2 O Java 7 não exige mais essa implementação interrompida. 3O problema
0,5 + 0,49999999999999994 é exatamente 1 em dupla precisão:
Isso ocorre porque 0.49999999999999994 tem um expoente menor que 0,5; portanto, quando são adicionados, a mantissa é alterada e o ULP fica maior.
A solução
Desde o Java 7, o OpenJDK (por exemplo) o implementa da seguinte maneira: 4
1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29
2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (créditos para @SimonNickerson por encontrar isso)
3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29
4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29
fonte
round
no Javadoc paraMath.round
ou na visão geral daMath
classe.Parece ser um bug conhecido (bug do Java 6430675: Math.round tem um comportamento surpreendente para 0x1.fffffffffffffp-2 ) que foi corrigido no Java 7.
fonte
Código fonte no JDK 6:
Código fonte no JDK 7:
Quando o valor é 0,49999999999999994d, no JDK 6, ele chamará floor e, portanto, retornará 1, mas no JDK 7, a
if
condição está verificando se o número é o maior valor duplo menor que 0,5 ou não. Como neste caso o número não é o maior valor duplo menor que 0,5, oelse
bloco retorna 0.Você pode tentar 0.49999999999999999d, que retornará 1, mas não 0, porque esse é o maior valor duplo menor que 0,5.
fonte
floor
método o arredonda corretamente.Eu tenho o mesmo no JDK 1.6 de 32 bits, mas no Java 7 de 64 bits, tenho 0 para 0,49999999999999994, que arredondado é 0 e a última linha não é impressa. Parece ser um problema de VM, no entanto, usando pontos flutuantes, você deve esperar que os resultados diferam um pouco em vários ambientes (CPU, modo de 32 ou 64 bits).
E, ao usar
round
ou inverter matrizes, etc., esses bits podem fazer uma enorme diferença.saída x64:
fonte
A resposta a seguir é um trecho de um relatório de bug do Oracle 6430675 em. Visite o relatório para obter a explicação completa.
Os métodos {Math, StrictMath.round são operacionalmente definidos como
para argumentos duplos. Embora essa definição geralmente funcione conforme o esperado, ela fornece o resultado surpreendente de 1, em vez de 0, para 0x1.fffffffffffffp-2 (0.49999999999999994).
O valor 0,49999999999999994 é o maior valor de ponto flutuante menor que 0,5. Como um literal de ponto flutuante hexadecimal, seu valor é 0x1.fffffffffffffp-2, que é igual a (2 - 2 ^ 52) * 2 ^ -2. == (0,5 - 2 ^ 54). Portanto, o valor exato da soma
é 1 - 2 ^ 54. Isso está na metade do caminho entre os dois números adjacentes de ponto flutuante (1 - 2 ^ 53) e 1. No modo aritmético IEEE 754 de arredondamento para o mais próximo, usado pelo Java, quando um resultado de ponto flutuante é inexato, o mais próximo dos dois valores de ponto flutuante representáveis que suportam o resultado exato devem ser retornados; se ambos os valores estiverem igualmente próximos, aquele cujo último bit zero será retornado. Nesse caso, o valor de retorno correto da adição é 1, e não o maior valor menor que 1.
Enquanto o método está operando conforme definido, o comportamento nessa entrada é muito surpreendente; a especificação pode ser alterada para algo mais como "Arredondar para o comprimento mais próximo, arredondar as amarras", o que permitiria mudar o comportamento dessa entrada.
fonte