Zehnerrechnen in Java
Im Alltag und in der Finanzmathematik wird meistens das Zehnersystem verwendet.
Der endliche Zehnerbruch 0,1 hat die Zweierdarstellung 0,0(0011). Die Periode wurde hier eingeklammert. Die Zweierdarstellung des endlichen Zehnerbruchs ist also nicht mehr endlich und kann deswegen nicht genau gespeichert werden. Statt dessen wird eine Näherung, wie die Zweierdarstellung 0,0001100110011 gespeichert, die nach wenigen Stellen abbricht. Der gespeicherte Wert ist dann also nicht mehr genau der Zehnerbruch 0,1. Daraus ergeben sich dann Fehler in Berechnungen, die besonders auffallen, wenn endliche Zehnerbrüche für die Darstellung der Eingaben und Ausgabe verwendet werden.
Das Rechnen mit Zweierdarstellungen ist nicht mehr oder weniger genau als das Rechnen mit Zehnerdarstellungen. Es sind nur andere Werte, die dabei mit wenigen Stellen genau dargestellt werden können. Wenn allerdings im Anwendungsgebiet gerade solche Werte verwendet werden, die im Zehnersystem genau dargestellt werden können, dann führt die Zweierdarstellung öfter zu Fehlern. In physikalischen oder mathematischen Anwendungen ist die Zweierdarstellung nicht störend, da diese Anwendungen kein Zahlensystem besonders bevorzugen. Preis und Wertangaben in Währungen verwenden jedoch bevorzugt genaue Angaben mit Zehnerbrüchen, so daß hier schon kleine Abweichungen besonders stören.
Die Standardklasse "java.math.BigDecimal" stellt Zahlen intern im Zehnersystem dar, so daß die Ungenauigkeiten entfallen, die sich aus der Wandlung in eine Zweierdarstellung ergeben.
Das Programm "Operator.java" berechnet das Quadrat der Zahl 1.1. Bei einer Zweierdarstellung ergäbe sich hier ein Fehler, das Quadrat wäre nicht genau 1.21. Die Zehnerdarstellung kann das korrekte Ergebnis berechnen, wie der Vergleich mit der Operation "equals" ergibt.
Operator.java
public final class Operator { public static void main( final java.lang.String[] args )
{ final java.math.BigDecimal b11 = new java.math.BigDecimal( "1.1" );
final java.math.BigDecimal b121 = new java.math.BigDecimal( "1.21" );
final java.math.BigDecimal product = b11.multiply ( b11 );
final boolean result = product.equals( b121 );
java.lang.System.out.println( result ); }}System.out
true
Die Übersetzungseinheit "Operator1.java" verzichtet auf Referenznamen und verwendet eine Importdeklaration. Dadurch ist sie kürzer als die Übersetzungseinheit "Operator.java". Noch kürzer ließe sie sich in einer Programmiersprache mit der Möglichkeit der Überladung von Operatoren schreiben, wo die Operatoren der Sprache auch für den Versand von Nachrichten an Objekte verwendet werden können.
Operator1.java
import java.math.*;
public final class Operator1 { public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( ( new BigDecimal( "1.1" ).multiply( new BigDecimal( "1.1" )))
.equals
( new BigDecimal( "1.21" ))); }}System.out
true
Textliterale bevorzugen!
Eine Zehnerzahl sollte bevorzugt aus einem Textliteral und nicht aus einem Gleitkommaliteral erzeugt werden, denn der Wert eines Gleitkomma-Zahlenliterals wird ja schon im Zweiersystem dargestellt. Wenn der Wert mit der verwendeten Zehnerschreibweise im Zweiersystem mit wenigen Stellen nicht genau dargestellt werden kann, dann weicht dieser Wert schon vom gewünschten Wert ab und eine anschließenden Konstruktion eines BigDecimal -Objekts auf der Grundlage dieses Wertes könnte dann ebenfalls von dem gewünschten Wert abweichen. Die direkte Erzeugung einer Dezimalzahl aus einer Zeichenkette ist daher der sicherste Weg um genau eine bestimmte Zehnerdarstellung zu erhalten.
Die Nachkommastellen (NKS)
Ein BigDecimal -Objekt kann recht viele (mehrere Milliarden) Nachkommastellen haben. Die Anzahl (scale ) dieser Nachkommastellen ist ebenfalls eine Eigenschaft solch eines Objekts und kann durch die Meßoperation "int scale()" ermittelt werden.
Scale.java
public final class Scale { public static void main( final java.lang.String[] args )
{ final java.math.BigDecimal b = new java.math.BigDecimal( "1.000" );
java.lang.System.out.println( b1.scale() ); }}System.out
3
Zwei BigDecimal -Objekte mit unterschiedlicher Anzahl von Nachkommastellen gelten bereits als ungleich, obwohl sie die gleiche Größe haben.
Equals.java
public final class Equals { public static void main( final java.lang.String[] args )
{ final java.math.BigDecimal b11 = new java.math.BigDecimal( "1.0" );
final java.math.BigDecimal b11a = new java.math.BigDecimal( "1.0" );
final java.math.BigDecimal b13 = new java.math.BigDecimal( "1.000" );
java.lang.System.out.println( b11.equals( b11a ) );
java.lang.System.out.println( b11.equals( b13 ) );
java.lang.System.out.println( b11.compareTo( b11a )== 0 );
java.lang.System.out.println( b11.compareTo( b13 )== 0 ); }}System.out
true
false
true
true
Beim Runden eines BigDecimal -Objektes kann ein bestimmtes Rundungsverfahren angegeben werden. Dazu wird eine Konstante verwendet, deren Wert ein bestimmtes Verfahren bedeutet.
Für die folgenden Erklärungen wird zunächst angenommen, eine Zahl mit Nachkommastellen soll zu einer ganzen Zahl gerundet werden. Die Zahl besteht aus einem Vorkommaanteil und einem Nachkommaanteil, so hat die Zahl 51,311 beispielsweise den Vorkommaanteil 51 und den Nachkommaanteil 0,311.
Die Konstante "BigDecimal.ROUND_HALF_UP" kennzeichnet das Verfahren, welches in diesem Fall aufrundet, wenn der Nachkommaanteil größer oder gleich 0,5 ist und sonst abrundet (kaufmännisches Runden). Die Konstante "BigDecimal.ROUND_HALF_EVEN" kennzeichnet das Verfahren, das aufrundet, wenn der Nachkommaanteil größer als 0,5 ist und abrundet, wenn er kleiner als 0,5 ist; falls der Nachkommaanteil genau gleich 0,5 ist, wird in diesem Fall zur nächsten ganzen geraden Zahl gerundet (mathematisches Runden).
Round.java
public final class Round { public static void main( final java.lang.String[] args )
{ final java.math.BigDecimal b187 =
new java.math.BigDecimal( "1.87" );
java.lang.System.out.println
( b187.setScale( 2, java.math.BigDecimal.ROUND_HALF_UP )); }}System.out
1.87
Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ final java.math.BigDecimal bigDecimal
= new java.math.BigDecimal( "0.001234" );
for( int i = 9; i >= 0; -- i )try
{ final java.lang.String string
= java.lang.String.format( "%%." + i + "f: %." + i + "f", bigDecimal );
java.lang.System.out.println( string ); }
catch( final java.lang.IllegalArgumentException illegalArgumentException )
{ java.lang.System.out.println( illegalArgumentException ); }}}transcript
%.9f: 0.001234000
%.8f: 0.00123400
%.7f: 0.0012340
%.6f: 0.001234
%.5f: 0.00123
%.4f: 0.0012
%.3f: 0.001
%.2f: 0.00
%.1f: 0.0
%.0f: 0- Ergebnis ausgeben
- Ändern Sie das Programm "Operator.java" so ab, daß das berechnete Produkt ausgegeben wird.
- Runden
- Finden Sie eine Zahl, für die sich das Ergebnis einer kaufmännischen Rundung vom Ergebnis einer mathematischen Rundung unterscheidet. Schreiben Sie ein Programm, das die beiden Ergebnisse dieser beiden Rundungen dieser Zahl ausgibt.
- Dokumentation lesen
- Entnehmen Sie der Spezifikation der Klasse "BigDecimal", ob Objekte dieser Klasse veränderlich sind und wie zwei mit Objekten dieser Klasse dargestellte Zahlen durcheinander dividiert werden.
- Währungsumrechnung
- Ändern Sie das Programm "Umwandlung.java" so ab, daß es mit der Klasse "BigDecimal" rechnet.
Umwandlung.java
public final class Umwandlung
{ public static void main( String s[])
{ final double dm = 12.32;
final double euro = 12.32 / 1.95583;
java.lang.System.out.print( euro ); }}System.out
6.299115976337412
Notiz *
A counterexample is the following calculation I once programmed,
where a large precession is needed for some intermediate values:
A walker is walking on a treadmill. The walker walks with
1 m/s relative to the treadmill. The treadmill itself has
a speed of 1/s relative to the ground. What is the speed
of the walker relative to the ground?
approximately 1,999999999999999977746998878927631604116230150263 m/s
299.792.458 segments are moving, each one is moving relative
to the other with 1 m/s. What is the speed of the last segment
relative to the first?
approximately 228.320.184 m/s
// v = ((k²-1)/(k²+1))c
// k = sqrt((1+v/c)/(1-v/c))
public final class Main
{
static final java.math.BigDecimal l = new java.math.BigDecimal( 1 );
static final java.math.BigDecimal Z = new java.math.BigDecimal( 2 );
static final java.math.BigDecimal c = new java.math.BigDecimal( 299_792_458 );
static final int s = 99;
private static java.math.BigDecimal sqrt( final java.math.BigDecimal x, final int scale )
{ java.math.BigDecimal a = new java.math.BigDecimal( 0 );
java.math.BigDecimal b = new java.math.BigDecimal( java.lang.Math.sqrt( x.doubleValue() ));
while( !a.equals( b ))
{ a = b;
b = x.divide( a, scale, java.math.BigDecimal.ROUND_HALF_UP );
b = b.add( a );
b = b.divide( Z, scale, java.math.BigDecimal.ROUND_HALF_UP ); }
return a; }
private static java.math.BigDecimal v( final java.math.BigDecimal k )
{ final java.math.BigDecimal k2 = k.multiply( k );
final java.math.BigDecimal k2p = k2.add( l );
final java.math.BigDecimal k2m = k2.subtract( l );
final java.math.BigDecimal k2mk2p = k2m.divide( k2p, s, java.math.BigDecimal.ROUND_HALF_UP );
final java.math.BigDecimal k2mk2pc = k2mk2p.multiply( c );
return k2mk2pc; }
private static java.math.BigDecimal k( final java.math.BigDecimal v )
{ final java.math.BigDecimal vc = v.divide( c, s, java.math.BigDecimal.ROUND_HALF_UP );
final java.math.BigDecimal vcp = l.add( vc );
final java.math.BigDecimal vcm = l.subtract( vc );
final java.math.BigDecimal vcpvcm = vcp.divide( vcm, s, java.math.BigDecimal.ROUND_HALF_UP );
final java.math.BigDecimal vcpvcmq = sqrt( vcpvcm, s );
return vcpvcmq; }
private static java.math.BigDecimal x( final int x )
{ return new java.math.BigDecimal( x ); }
private static java.math.BigDecimal k( final int v )
{ return k( x( v )); }
private static java.math.BigDecimal pow( final java.math.BigDecimal a, final int n )
{ java.math.BigDecimal r = new java.math.BigDecimal( 1 );
for( int i = 0; i < n; ++i )
{ r = r.multiply( a );
r = r.setScale( s, java.math.BigDecimal.ROUND_HALF_UP ); }
return r; }
public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( v( k( 1 ).multiply( k( 1 ))));
java.lang.System.out.println( v( pow( k( 1 ), 299792458 ))); }}
1.999999999999999977746998878927631604116230150263845237978085586939779752047404383998830005681059958
228320184.012414095546388600393623787197402155676918681940985313453221688645601326141408124419632293624912144
Notiz *
Main.java
public final class Main
{public static void main( final java.lang.String[] args )
{ final var difference =
new java.math.BigDecimal( "0.3000000000000000166533453693773481063544750213623046875" ).subtract
( new java.math.BigDecimal( "0.299999999999999988897769753748434595763683319091796875" ));
java.lang.System.out.printf( "%.99f\n", difference ); }}- transcript
0.000000000000000027755575615628913510590791702270507812500000000000000000000000000000000000000000000
Notiz *
Main.java
public final class Main
{public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( 0xf.9ffffffffff7p+59 );
java.lang.System.out.println( 0xf.9fffffffffffp+59 );
java.lang.System.out.println( 0xf.9ffffffffff9p+59 );
java.lang.System.out.println( new java.math.BigDecimal( 0xf.9ffffffffff7p+59 ));
java.lang.System.out.println( new java.math.BigDecimal( 0xf.9ffffffffff8p+59 ));
java.lang.System.out.println( new java.math.BigDecimal( 0xf.9ffffffffff9p+59 )); }}- transcript
9.0071992547409736E18
9.0071992547409756E18
9.0071992547409777E18
9007199254740973568
9007199254740975616
9007199254740977664