Autorenarchiv

(Ver)rechnen mit Java – Double vs. BigDecimal

Freitag, 17. Juli 2009

Ein einfaches Beispiel:

System.out.println(3 * 0.7);

Die Ausgabe sollte, rein rechnerisch, 2.1 lauten. Probieren Sie es aus. Auf den meisten Rechnern wird das Ergebnis 2.0999999999999996 lauten. Java rechnet hier intern mit Näherungswerten. Das macht die Berechnung zwar performanter aber eben nicht exakt und zudem kann das Ergebnis auf verschiedenen Plattformen unterschiedlich ausfallen (siehe strictfp).

Rechnen mit Strings: BigDecimal

Abhilfe soll hier die Verwendung von java.math.BigDecimal schaffen. Doch auch hier sei Vorsicht geboten:

System.out.println(new BigDecimal(2.1));

Liefert die Ausgabe:
2.100000000000000088817841970012523233890533447265625

Das Beispiel zeigt deutlich mit welchen internen Werten gerechnet wird. Verwenden wir jedoch den Konstruktor mit String erhalten wir den genauen Wert und rechnen auch mit diesem:

System.out.println(new BigDecimal("2.1"));

Die Multiplikation

System.out.println(
   new BigDecimal("3").multiply(new BigDecimal("0.7")));

ergibt dann auch endlich wie erwartet 2.1 und das auf allen Plattformen.

Zum Vergleich zweier Werte sollte die Methode compareTo der equals Methode vorgezogen werden weil sonst scheinbar gleiche Werte mit unterschiedlicher Anzahl an Nachkommastellen (scale) nicht gleich sind.
Ein Beispiel:

new BigDecimal("2.0").equals(new BigDecimal("2.00"))
// false
new BigDecimal("2.0").compareTo(new BigDecimal("2.00")) == 0
// true

Hierzu noch eine kleine Besonderheit. Java unterscheidet 0.0 und -0.0

new Double(0.0).equals(new Double(-0.0))
// false

Bei Verwendung von BigDecimal und der compareTo Methode gibt es hier allerdings keine Unterscheidung.

Was aber tun, wenn die Werte nur als double vorliegen?
Dann kann man sich so behelfen:

BigDecimal value = new BigDecimal(Double.toString(doubleValue));

sollte jedoch die JavaDocs zur toString Methode der Klasse java.lang.Double berücksichtigen.

strictfp

Noch ein kurzer Hinweis zum Schlüsselwort strictfp (das wohl nur wenigen bekannt sein dürfte, da es selten genutzt wird). Mit strictfp können sowohl Klassen als auch Methoden gekennzeichnet werden und versprechen dadurch Rechengenauigkeit nach IEEE-Norm. D.h. zumindest schon mal, dass die Ergebnisse auf verschiedenen Plattform identisch sein werden. Das oben besprochenen Dilemma mit double und float besteht aber weiterhin, auf allen Plattformen wird somit gleich ungenau gerechnet.

Fazit

Zur genauen Berechnung sollte immer BigDecimal(String val) verwendet werden, auch wenn dies zu Lasten der Performance geht. Zudem bietet die Klasse BigDecimal eine Vielzahl praktischer Methoden.
Um sich eine grobe Vorstellung bezüglicher der Performance zu machen, habe ich die Rechnung 3.0 * 0.7 jeweils 100000000 mal mit BigDecimal und double durchgeführt. Dauer:
BigDecimal: 160016 ms
double: 109 ms
Also ein gravierender Unterschied.

Weiterführenden Themen wäre hier u.a. die Klasse java.math.MathContext wenn es um Rundung und die Genauigkeit der Nachkommastellen geht.

Viel Erfolg beim Rechnen.


Christof Aenderl


Hibernate 3.3 Update und c3p0 connection pool

Donnerstag, 15. Januar 2009

Die Umstellung einer Anwendung von Hibernate Version 3.2 auf Version 3.3 ist weniger trivial als zu vermuten wäre. Ein einfaches Austauschen des hibernate3.jar führt hier nicht zum Erfolg sondern endet in einer Reihe von Fehlermeldungen.
Ab Version 3.3 benötigt Hibernate zusätzliche Bibliotheken, da z.B. das Logging auf SLF4J umgestellt wurde. Hier eine kurze Übersicht der zusätzlich nötigen JAR Dateien und welche konkret auf unserem System zum Einsatz kommen:

  • commons-collection ab Version 3.1 : commons-collection-3.2.jar
  • slf4j-api : slf4j-api-1.5.5.jar
  • slf4j Adapter für log4j: slf4j-log4j12-1.5.5.jar

Nachdem die Anwendung nun fehlerfrei startete, dachte man. alles sei in Ordnung. Doch nach einigen Stunden meldete die Überwachung Fehler beim Zugriff auf die Datenbank:

Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was100413 seconds ago.The last packet sent successfully to the server was 100413 seconds ago, which is longer than the server configured value of ‘wait_timeout’. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property ‘autoReconnect=true’ to avoid this problem.

Nach einiger Suche stellte sich heraus, dass Hibernate gar nicht, wie gewünscht, für das Connection Pooling C3P0 verwendetet sondern seinen eigenen, rudimentären Connection Pool. Die hibernate.cfg.xml enthielt aber alle nötigen hibernate.c3p0 Properties weshalb die Ursache nicht offensichtlich war. Des Rätsels Lösung liegt in der ab Version 3.3 nötigen expliziten Angabe der ConnectionProviderClass. Mit Hilfe der Property hibernate.connection.provider_class lässt sich diese angeben. Folgender Eintrag muss also in der hibernate.cfg.xml hinzugefügt werden um C3P0 als Connection Pool zu verwenden:

<property name="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider
</property>

Nun verwendet Hibernate auch wieder C3P0 und die Anwendung läuft seitdem ohne Probleme.

Viel Spaß und Erfolg beim Ausprobieren und Anwenden.


Christof Aenderl


Apache Commons-IO im praktischen Einsatz

Montag, 08. September 2008

Die Projekte aus der Apache Commons Reihe erleichtern dem Programmierer oft den Alltag, da sie für sehr viele Situationen Standardroutinen anbieten.

Hier wollen wir uns einige Anwendungsbeispiele der Commons IO Util-Klassen anschauen. Die gängigsten sind hier FileUtils, IOUtils und FilenameUtils.

Beim Umgang mit Textdateien z.B. XML ist das Encoding beim einlesen und schreiben von großer Bedeutung. Ansonsten werden Umlaute und Sonderzeichen nicht richtig dargestellt. Commons IO bietet hierfür viele read/write Methoden mit optionaler Angabe des Encoding an.
Zum Thema Encoding wird sicherlich noch ein extra Blog folgen: ein Gebiet, dass einen bei sämtlichen Datei-, Datenbank- usw. Zugriffen verfolgt.

Textdatei in einen String einlesen

Die Methode FileUtils.readFileToString liefert den Textinhalt einer Datei als String oder einen leeren String (nicht null). Im Fehlerfall wird eine java.io.IOException geworfen.

// use system default encoding
String text = FileUtils.readFileToString(
    new File("text.txt"));
// set encoding to UTF-8
String textUTF8 = FileUtils.readFileToString(
    new File("text-utf8.txt"),
    org.apache.commons.lang.CharEncoding.UTF_8);

Binärdatei einlesen

Ebenso einfach lässt sich eine Binärdatei einlesen.

byte[] bytes = FileUtils.readFileToByteArray(
    new File("file.bin"));

String als Textdatei speichern

Das Speichern einer Textdatei wird zum Einzeiler:

String content = "Das ist ein Text";
FileUtils.writeStringToFile(new File("text.txt"), content);

Mit Angabe des Encoding (UTF-8):

String content = "Das ist ein Text mit Umlauten: äöü ÄÖÜ";
FileUtils.writeStringToFile(new File("text.txt"), content,
    org.apache.commons.lang.CharEncoding.UTF_8);

Als Variation kann noch eine Collection in eine Textdatei geschrieben werden:

List lines = new ArrayList();
// befüllen der List ...
FileUtils.writeLines(new File("lines.txt"), lines);

Analog funktioniert das Ganze genau so einfach mit Binärdateien.

Dateiliste anhand der Endung erstellen

Anwendungsfall: In einem Verzeichnis liegen viele XML und PDF Dateien. Nun sollen alle XML Dateien nacheinander geparsed werden. Die Methode FileUtils.listFiles erwartet als Parameter das Verzeichnis, in dem sich die Dateien befinden. Ein Array der Dateiendungen, die eingelesen werden sollen. Ein Flag ob das Verzeichnis rekursiv durchsucht werden soll.

Collection files = FileUtils.listFiles(
    new File("/dir"),
    new String [] {"xml"},
    false);
for (File file : files) {
    // parse file
}

Textdatei Zeilenweise auslesen

Anwendungsfall: Eine csv Datei wird Zeilenweise eingelesen und verarbeitet.

List lines = FileUtils.readLines(
    new File("data.csv"),
    org.apache.commons.lang.CharEncoding.UTF_8);
for (String string : lines) {
    // process single line
}

Streams/Reader/Writer schliessen

Auch ein sehr häufiger Anwendungsfall ist das Schließen von Streams usw.
wobei vorher auf Null geprüft wird und eine Exception gefangen und in der
Regel ignoriert wird.

Vorher:

Writer writer = null;
try {
    writer = new FileWriter("filename.txt");
    // ...
} catch (Exception e) {
    // handle exception
} finally {
    if (writer != null) {
        try {
            writer.close();
        } catch (IOException e) {
            // ignore
        }
    }
}

Nachher:

Writer writer = null;
try {
    writer = new FileWriter("filename.txt");
    // ...
} catch (Exception e) {
    // handle exception
} finally {
    IOUtils.closeQuietly(writer);
}

IOUtils Kurzübersicht

Weitere Methoden der IOUtlis dienen u.A. zum vergleichen und kopieren von Dateien,
erzeugen von Streams und schreiben in Streams.

FilenameUtils Kurzübersicht

Wie der Name erahnen lässt dienen die Methoden dieser Klasse zur Auswertung und Manipulation
von Dateiname. Häufig verwendet:

FilenameUtils.getBaseName(filename);
FilenameUtils.getExtension(filename);
FilenameUtils.removeExtension(filename);

Ein Blick in die Javadocs lohnt sich hier auf alle Fälle.

Viel Spaß und Erfolg beim Ausprobieren und Anwenden.


Christof Aenderl


Maven2 in Eclipse nutzen

Dienstag, 29. April 2008

Viele Projekte verwenden mittlerweile Maven2 für Build, Deployment, usw. Um Maven aus Eclipse heraus relativ komfortabel zu nutzen, sind nicht unbedingt Plugins nötig. Mit wenigen Handgriffen ist das lokale Repository konfiguriert und häufig genutzte Goals eingerichtet.
Plugins bieten zwar Vorteile, z.B. bei der Verwaltung der Dependencies, sind aber leider (noch) an vielen Stellen unflexibel, nicht stabil und umständlich zu benutzen.

Voraussetzung ist eine lauffähige Maven2 Version (Kommandozeile) und im Betriebssystem die Path-Variable MAVEN_HOME, die auf das Maven Installationsverzeichnis verweist (dort wo man Maven entpackt hat).
Die folgenden Schritte wurden mit Eclipse 3.3 durchgeführt.

Lokales Repository zum Buildpath hinzufügen

Damit in Eclipse das Maven Repository gefunden wird, muss die Classpath-Variable M2_REPO angelegt und auf das lokale Repository verlinkt werden.
Das geschieht über das Menü:
Window – Preferences – Java – Build Path – Classpath Variables
Hier mit “New” die Variable M2_REPO anlegen und den Path auf das lokale Maven Repository setzen.
Unter Windows liegt das lokale Repository in der Regel unter:
C:\Dokumente und Einstellungen\<Benutzername>\.m2\repository
Bei Linux unter:
/home/<Benutzername>/.m2/repository

Maven Goals konfigurieren

Hierfür bietet es sich an die “External Tools Launch Configuration” von Eclipse zu nutzen mit der, wie der Name vermuten lässt, externe Programme aufgerufen werden können.

1. Über die Schaltfläche External Tools den Open External Tools Dialog … öffnen.

2. Unter Program eine New launch configuration anlegen.

3. Location: ${env_var:MAVEN_HOME}\bin\mvn.bat
Hiermit wird über die Systemvariable MAVEN_HOME die aktuelle Maven Version aufgerufen.
Z.B. liegt die Maven Installation im Verzeichnis: c:/dev/maven-2.0.7 auf welches die Variable MAVEN_HOME verweist. Eine neuere Maven Version packt man im Verzeichnis c:/dev/maven-2.0.8 aus und stellt nur die MAVEN_HOME Variable entspr. um.

4. Working Directory: ${project_loc}
Nun wollen wir nicht für jedes Projekt eine eigene Launch Configuration anlegen. Das nervt und wird schnell sehr unübersichtlich. Auch hierfür biete Eclipse eine eigene Variable an: ${project_loc} verweist auf das aktuell ausgewählte Projekt.

5. Arguments: install -DcreateChecksum=true
Hier werden Maven die Kommandozeilen-Argumente übergeben. In diesem Fall also ein einfaches install mit Erzeugen der Checksum. Ein weiteres gutes Beispiel ist das eclipse:eclipse Goal. Mit diesen beiden Goals sind schon mal die Grundbedürfnisse erfüllt.

Ausführen eines Maven Goal

Im z.B. Package Explorer ein Maven Projekt auswählen (anklicken), hierdurch wird die Eclipse Variable ${project_loc} gesetzt. Anschließend aus den External Tools das gewünschte Goal auswählen und Maven startet in der Console.
Fertig :-)

Ein typischer Arbeitsschritt wäre z.B.:

  • Anpassungen am pom.xml machen
  • Goal eclipse:eclipse ausführen
  • Refresh des Projekts (F5) -> kann man auch durch die Launch Configuration auto. durchführen lassen
  • Goal install ausführen

BTW: Zusätzlich fügt man diese Launch Configurations noch zu den Favoriten hinzu, dann geht es noch schneller.

Beispiele:
Folgende Goals verwende ich wie oben beschrieben.

  • install -DcreateChecksum=true
  • eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true
  • Da ab maven 2.0.10 beim eclipse Goals auch Projekte referenziert werden, dies aber nicht immer gewünscht ist, kann man dieses Feature abschalten

  • eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true -Declipse.useProjectReferences=false

Viel Spaß und Erfolg beim Ausprobieren und Anwenden


Christof Aenderl