java - unterschied - programmieren für dummies pdf download




Warum schwimmt C und Java round unterschiedlich? (2)

Betrachten Sie die Gleitkommazahl 0.644696875. Konvertieren wir es mit Java und C in einen String mit acht Dezimalstellen:

Java

import java.lang.Math;
public class RoundExample{
     public static void main(String[] args){
        System.out.println(String.format("%10.8f",0.644696875));
     }
}

Ergebnis: 0.6446968 8

Probieren Sie es aus: http://tpcg.io/oszC0w

C

#include <stdio.h>

int main()
{
    printf("%10.8f", 0.644696875); //double to string
    return 0;
}

Ergebnis: 0.6446968 7

Probieren Sie es selbst aus: http://tpcg.io/fQqSRF

Die Frage

Warum ist die letzte Ziffer anders?

Hintergrund

Die Nummer 0.644696875 kann nicht exakt als Maschinennummer dargestellt werden. Es wird als Bruchteil 2903456606016923/4503599627370496 dargestellt, der einen Wert von 0,6446968749999999 hat

Dies ist zugegebenermaßen ein Randfall. Aber ich bin wirklich neugierig auf die Ursache des Unterschieds.

Verwandte: https://mathematica.stackexchange.com/questions/204359/is-numberform-double-rounding-numbers


Fazit

Die Java-Spezifikation erfordert in dieser Situation eine mühsame Doppelrundung. Die Nummer 0.6446968749999999470645661858725361526012420654296875 wird zuerst in 0.644696875 konvertiert und dann auf 0.64469688 gerundet.

Im Gegensatz dazu rundet die C-Implementierung einfach 0,64469687499999470645661858725361526012420654296875 direkt auf acht Stellen und ergibt 0,64469687.

Vorbereitungen

Für Double verwendet Java IEEE-754 (Basic 64-Bit Binary Floating Point). In diesem Format ist der Wert, der der Zahl im Quelltext am nächsten kommt, 0.644696875, 0.64469687499999470645661858725361526012420654296875, und ich glaube, dies ist der tatsächliche Wert, der mit String.format("%10.8f",0.644696875) formatiert werden String.format("%10.8f",0.644696875) . 1

Was die Java-Spezifikation sagt

In der Dokumentation zur Formatierung mit dem Double Typ und dem f Format heißt es:

… Wenn die Genauigkeit geringer ist als die Anzahl der Stellen, die nach dem Dezimalpunkt in der von Float.toString(float) bzw. Double.toString(double) Zeichenfolge erscheinen würden, wird der Wert mit dem Double.toString(double) gerundet . Andernfalls können Nullen angehängt werden, um die Genauigkeit zu erreichen.

Betrachten wir den String, der von Double.toString(double) . Für die Nummer 0.6446968749999999470645661858725361526012420654296875 lautet diese Zeichenfolge „0.644696875“. Dies liegt daran, dass die Java-Spezifikation besagt, dass toString gerade genug Dezimalstellen erzeugt, um die Zahl innerhalb des Double Wertesatzes eindeutig zu unterscheiden. In diesem Fall hat „0.644696875“ gerade genug Stellen. 2

Diese Zahl hat neun Stellen nach dem Komma, und "%10.8f" fordert acht an, sodass in der oben angegebenen Passage "der Wert" gerundet ist. Welchen Wert meint es - den tatsächlichen Operanden des format , der 0,64469687499999470645661858725361526012420654296875 ist, oder die Zeichenfolge, von der es spricht, „0,644696875“? Da letzteres kein numerischer Wert ist, hätte ich erwartet, dass „der Wert“ ersteres bedeutet. Im zweiten Satz heißt es jedoch: "Andernfalls (dh wenn mehr Ziffern angefordert werden) können Nullen angehängt werden ...". Wenn wir den tatsächlichen Operanden des format , würden wir dessen Ziffern anzeigen und keine Nullen verwenden. Wenn wir die Zeichenfolge jedoch als numerischen Wert annehmen, wird ihre Dezimalrepräsentation nach den darin angezeigten Ziffern nur Nullen enthalten. Es scheint also, dass dies die beabsichtigte Interpretation ist, und Java-Implementierungen scheinen dem zu entsprechen.

"%10.8f" diese Zahl mit "%10.8f" , konvertieren wir sie zuerst in 0,644696875 und runden sie dann mit der Rundungsregel, die 0,64469688 ergibt.

Dies ist eine schlechte Spezifikation, weil:

  • Es sind zwei Rundungen erforderlich, wodurch sich der Fehler erhöhen kann.
  • Die Rundungen treten an schwer vorhersehbaren und schwer kontrollierbaren Stellen auf. Einige Werte werden nach zwei Dezimalstellen gerundet. Einige werden nach 13 gerundet. Ein Programm kann dies nicht einfach vorhersagen oder anpassen.

(Es ist auch eine Schande, dass sie Nullen geschrieben haben, die „angehängt werden können“. Warum nicht „Ansonsten werden Nullen angehängt, um die Genauigkeit zu erreichen“? Mit „kann“ scheint es, als würden sie der Implementierung eine Wahl geben, obwohl ich vermute, dass sie bedeutet, dass das "Mai" davon abhängt, ob Nullen erforderlich sind, um die Genauigkeit zu erreichen, und nicht davon, ob der Implementierer sie anfügt.)

Fußnote

1 Wenn 0.644696875 im Quelltext in Double konvertiert wird, sollte das Ergebnis meines Erachtens der nächste Wert sein, der im Double Format darstellbar ist. (Ich habe dies nicht in der Java-Dokumentation gefunden, aber es entspricht der Java-Philosophie, dass Implementierungen sich identisch verhalten müssen, und ich vermute, dass die Konvertierung in Übereinstimmung mit Double.valueOf(String s) , was dies erfordert .) Double to 0.644696875 ist 0.6446968749999999470645661858725361526012420654296875.

2 Mit weniger Stellen reicht die siebenstellige Zahl von 0,64469687 nicht aus, da der nächstliegende Double Wert 0,6446968 699999999774519210404832847416400909423828125 beträgt . Es werden also acht Stellen benötigt, um 0,6446968 749999999470645661858725361526012420654296875 eindeutig zu unterscheiden.


Wahrscheinlich werden hier etwas andere Methoden zum Konvertieren der Zahl in eine Zeichenfolge verwendet, wodurch ein Rundungsfehler auftritt. Es ist auch möglich, dass die Methode, mit der der String während des Kompilierens in einen Float konvertiert wird, unterschiedlich ist. Auch hier kann es aufgrund der Rundung zu geringfügig unterschiedlichen Werten kommen.

Denken Sie jedoch daran, dass float für seinen Bruch eine Genauigkeit von 24 Bit hat, was ~ 7,22 Dezimalstellen ergibt [log10 (2) * 24], und die ersten 7 Ziffern stimmen überein, sodass es sich nur um die letzten niederwertigen Bits handelt anders.

Willkommen in der unterhaltsamen Welt der Gleitkomma-Mathematik, in der 2 + 2 nicht immer 4 entspricht.





printf