Pregunta ¿Es un error la incoherencia en el redondeo entre Java 7 y Java 8?


Veo inconsistencias en el redondeo en el DecimalFormat clase entre java 7 y java 8. Aquí está mi caso de prueba:

DecimalFormatTest.java

import java.text.DecimalFormat;

public class DecimalFormatTest {
    public static void main(String[] args) {
        DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance();
        format.setDecimalSeparatorAlwaysShown(true);
        format.setMinimumFractionDigits(1);
        format.setMaximumFractionDigits(1);
        System.out.println(format.format(83.65));
    }
}

En Java (TM) SE Runtime Environment (compilación 1.7.0_51-b13), el resultado es:

83.6

En Java (TM) SE Runtime Environment (compilación 1.8.0-b132) el resultado es:

83.7

¿Es esto un error de regresión? ¿O las reglas de redondeo cambiaron con el lanzamiento de Java 8?


32
2018-04-01 22:14


origen


Respuestas:


Parece que se trataba de un error de larga data en JDK 7 que finalmente se solucionó. Ver por ejemplo:

Existe un borrador del plan para proporcionar el siguiente aviso con JDK 8 que explica el problema:

-------------------------------------------------- ------------------- Área: Core Libraries / java.text

Sinopsis: Se ha corregido un comportamiento de redondeo incorrecto de JDK7. los   el comportamiento de redondeo del método NumberFormat / DecimalFormat format () tiene   cambiado cuando el valor está muy cerca de un empate sentado exactamente en el   posición de redondeo especificada en el patrón de formateo.

Naturaleza de la incompatibilidad: comportamiento

Descripción: Cuando se usan las clases NumberFormat / DecimalFormat,   el comportamiento de redondeo de las versiones anteriores de JDK era incorrecto en algún rincón   casos. Este comportamiento incorrecto ocurrió al llamar al método format () con   un valor que está muy cerca de un empate, mientras se especifica la posición de redondeo   por el patrón de la instancia NumberFormat / DecimalFormat utilizada es   exactamente sentado en la posición de la corbata. En ese caso, el doble equivocado   el redondeo o el comportamiento erróneo de no redondeo sucedió.

Como ejemplo, al usar el valor predeterminado recomendado NumberFormatFormat API   formar: NumberFormat nf = java.text.NumberFormat.getInstance() seguido   por nf.format(0.8055d), valor 0.8055d se registra en la computadora como    0.80549999999999999378275106209912337362766265869140625 ya que este valor no se puede representar exactamente en formato binario. Aquí por defecto   la regla de redondeo es "mitad-par", y el resultado de llamar al formato () en   JDK7 es una salida incorrecta de "0,806", mientras que el resultado correcto es "0,805"   ya que el valor registrado en la memoria por la computadora está "debajo" del empate.

Este nuevo comportamiento también se implementa para todas las posiciones de redondeo   eso podría definirse por cualquier patrón elegido por el programador (no   los predeterminados).

RFE          7131459



20
2018-04-01 22:22



Como se menciona en otras respuestas a esta pregunta, JDK 8 hizo intencional cambios a DecimalFormat redondeo en cuestión JDK-7131459: DecimalFormat produce resultados de formato incorrecto () cuando está cerca de un empate.

Sin embargo, esos cambios introdujo un error real archivado como JDK-8039915: Wrong NumberFormat.format () HALF_UP redondeo cuando el último dígito está exactamente en la posición de redondeo mayor que 5. Por ejemplo:

99.9989 -> 100.00
99.9990 ->  99.99

Para decirlo simplemente, esto demuestra un caso donde un mayor número de rondas abajoy un número menor de rondas arriba: (x <= y) != (round(x) <= round(y)). Parece que solo afecta el HALF_UP modo de redondeo, que es el tipo de redondeo que se enseña en las clases de aritmética de la escuela primaria: 0,5 rondas de cero, siempre.

Este problema existe en las versiones de Oracle 8 y OpenJDK de 8 y las actualizaciones 8u5, 8u11, 8u20, 8u25 y 8u31.

Oracle corrigió este error en la actualización de Java 8 40

Un parche de tiempo de ejecución no oficial está disponible para versiones anteriores

Gracias a la investigación de Holger en esta respuesta a una pregunta relacionada, pude desarrollar un parche de tiempo de ejecución y mi empleador lo ha liberado de forma gratuita según los términos de la licencia GPLv2. con la excepción Classpath1 (lo mismo que el código fuente de OpenJDK).

El proyecto de parche y el código fuente es alojado en GitHub con más detalles sobre este error, así como enlaces a binarios descargables. El parche funciona en tiempo de ejecución por lo que no se realizan modificaciones en los archivos Java en el disco, y debe ser seguro para usar en todas las versiones de Oracle Java> = 6 y al menos hasta la versión 8 (incluido u40 y posterior).

1 No soy abogado, pero tengo entendido que GPLv2 con CPE permite el uso comercial en forma binaria sin GPL aplicando al trabajo combinado.


14
2018-05-09 20:16