36 Stimmen

Ist die Inkonsistenz beim Runden zwischen Java 7 und Java 8 ein Fehler?

Ich sehe Inkonsistenzen beim Runden in der DecimalFormat-Klasse zwischen Java 7 und Java 8. Hier ist mein Testfall:

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));
    }
}

In Java(TM) SE-Laufzeitumgebung (Version 1.7.0_51-b13) ist die Ausgabe:

83.6

In Java(TM) SE-Laufzeitumgebung (Version 1.8.0-b132) ist die Ausgabe:

83.7

Handelt es sich hierbei um einen Regressionsfehler? Oder wurden die Rundungsregeln mit der Veröffentlichung von Java 8 geändert?

23voto

mellamokb Punkte 55046

Es sieht so aus, als ob dies ein langjähriger Fehler in JDK 7 war, der endlich behoben wurde. Siehe zum Beispiel:

Es gibt einen Entwurf, um mit JDK 8 folgende Information bereitzustellen, die das Problem erklärt:

--------------------------------------------------------------------- Bereich: Kernbibliotheken / java.text

Synopse: Ein falsches Rundungsverhalten von JDK 7 wurde behoben. Das Rundungsverhalten der NumberFormat/DecimalFormat format() Methode hat sich geändert, wenn der Wert sehr nahe an einer Tiefe sitzt und genau an der Rundungsposition liegt, die im Formatmuster festgelegt ist.

Natur der Inkompatibilität: verhaltensbedingt

Beschreibung: Bei Verwendung der NumberFormat/DecimalFormat Klassen war das Rundungsverhalten in früheren JDK-Versionen in einigen Randfällen falsch. Dieses falsche Verhalten trat auf, wenn die format() Methode mit einem Wert aufgerufen wurde, der sehr nahe an einer Tiefe liegt, während die Rundungsposition, die durch das Muster der verwendeten NumberFormat/DecimalFormat Instanz festgelegt ist, genau an der Position der Tiefe liegt. In diesem Fall trat eine falsche doppelte Rundung oder fehlerhaftes Nicht-Rundungsverhalten auf.

Als Beispiel, bei Verwendung des empfohlenen Standard-NumberFormat API-Formulars: NumberFormat nf = java.text.NumberFormat.getInstance() gefolgt von nf.format(0.8055d), wird der Wert 0.8055d im Computer als 0.80549999999999999378275106209912337362766265869140625 aufgezeichnet, da dieser Wert nicht genau im binären Format dargestellt werden kann. Hier ist die Standard-Rundungsregel "halb-gleichmäßig", und das Ergebnis des Aufrufs von format() in JDK7 ist eine falsche Ausgabe von "0.806", während das korrekte Ergebnis "0.805" ist, da der vom Computer im Speicher aufgezeichnete Wert "unter" der Tiefe liegt.

Dieses neue Verhalten ist auch für alle Rundungspositionen implementiert, die durch ein beliebiges Muster, das vom Programmierer gewählt wurde (nicht Standardmuster), definiert werden könnten.

RFE 7131459


15voto

William Price Punkte 3841

Wie in anderen Antworten zu dieser Frage erwähnt, hat JDK 8 in der DecimalFormat Rundung absichtliche Änderungen in Ausgabe JDK-7131459: DecimalFormat produces wrong format() results when close to a tie vorgenommen.

Allerdings haben diese Änderungen ein echten Fehler eingeführt, der als JDK-8039915: Wrong NumberFormat.format() HALF_UP rounding when last digit exactly at rounding position greater than 5 gemeldet wurde. Zum Beispiel:

99.9989 -> 100.00
99.9990 ->  99.99

Um es einfach auszudrücken, hier wird ein Fall demonstriert, in dem eine höhere Zahl abgerundet wird und eine niedrigere Zahl aufgerundet: (x <= y) != (round(x) <= round(y)). Dies scheint nur den Rundungsmodus HALF_UP zu betreffen, der die Art der Rundung ist, die in der Grundschularithmetik gelehrt wird: 0,5 wird immer weg von Null gerundet.

Dieses Problem tritt in Oracle- und OpenJDK-Veröffentlichungen von Java 8 und Updates 8u5, 8u11, 8u20, 8u25 und 8u31 auf.

Oracle hat diesen Fehler in Java 8 Update 40 behoben

Ein inoffizieller Laufzeit-Patch ist für frühere Versionen verfügbar

Dank der Recherche von Holger in dieser Antwort zu einer verwandten Frage konnte ich einen Laufzeit-Patch entwickeln und mein Arbeitgeber hat ihn kostenlos unter den Bedingungen der GPLv2-Lizenz mit Classpath-Ausnahme veröffentlicht1 (gleich wie der OpenJDK-Quellcode).

Das Patch-Projekt und der Quellcode sind auf GitHub gehostet mit weiteren Details zu diesem Fehler sowie Links zu herunterladbaren Binärdateien. Der Patch funktioniert zur Laufzeit, sodass keine Änderungen an den Java-Dateien auf der Festplatte vorgenommen werden, und er sollte auf allen Versionen von Oracle Java >= 6 und zumindest bis Version 8 (einschließlich u40 und später) sicher sein.

1 Ich bin kein Anwalt, aber mein Verständnis ist, dass GPLv2 w/ CPE die kommerzielle Nutzung in binärer Form ermöglicht, ohne dass die GPL auf das kombinierte Werk angewendet wird.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X