11.03 Rechnen mit großen Zahlen

Sie wissen aus den vorhergehenden Kapiteln, dass primitive Datentypen in Java nicht unendlich groß werden können, sondern dass jede primitive Zahl einem bestimmten Wertebereich entspricht. Gleichzeitig wissen Sie auch, dass Fließkommazahlen nicht in jedem Fall hundertprozentig genau rechnen. Um mit sehr großen, sehr kleinen oder sehr präzisen Zahlen zu rechnen, verwendet man in Java die Klassen java.math.BigInteger bzw. java.math.BigDecimal.

Einen BigInteger verwendet man immer dann, wenn man nur eine sehr große Ganzzahl benötigt. Sind zusätzlich noch Kommazahlen im Spiel, bietet sich der Einsatz eines BigDecimal an. Beide Klassen haben eine große Menge mathematischer Methoden, die hier nicht im Einzelnen besprochen werden sollen. Vielmehr möchte ich Ihnen in diesem Kapitel die wichtigsten Funktionen und deren Verwendung zeigen. Eine Beschreibung aller Methoden finden Sie in der API-Dokumentation (BigInteger, BigDecimal).

Eine große Zahl erzeugen

Hierzu wird in der Regel im Konstruktor der gewünschte Wert als String angegeben. Alternativ kann beim BigInteger parallel noch ein radix angegeben werden, um Zahlen eines anderen Systems (bspw. Binär oder Hexadezimal) zu verwenden. Der BigDecimal akzeptiert als Basis auch noch ein char-Array, ein double, long oder int. Außerdem kann ein BigDecimal auch Eingaben als 10er Exponent verarbeiten. Ansonsten gibt es auch noch jeweils drei Konstanten für die Werte 1, 10 und 0.

System.out.println(new BigInteger("42")); // 42
System.out.println(new BigInteger("2A", 16)); // 42
System.out.println(new BigDecimal("42")); // 42
System.out.println(new BigDecimal("42".toCharArray())); // 42
System.out.println(new BigDecimal(42)); // 42
System.out.println(new BigDecimal(42L)); // 42
System.out.println(new BigDecimal(42D)); // 42
System.out.println(new BigDecimal("4.2E1")); // 4.2 * (10 ^ 1) => 42
System.out.println(new BigDecimal("420E-1")); // 420 * (10 ^ -1) => 42
System.out.println(BigInteger.ZERO); // 0
System.out.println(BigDecimal.ONE); // 1
System.out.println(BigInteger.TEN); // 10

Basismathematik

Sowohl BigInteger wie auch BigDecimal unterstützen Basisfunktionen der Mathematik. Hierzu zählen Addition, Division, Mulitplikation, Subtraktion und Exponentialfunktionen. Da beide Klassen ebenso wie Strings immutable sind, werden die Werte nie verändert, sonden es wird jedes Mal ein neues Objekt zurückgeliefert.

BigInteger i = BigInteger.ONE; // 1
i = i.multiply(BigInteger.TEN); // 10
i = i.add(new BigInteger("8")); // 18
i = i.divide(BigInteger.TEN); // 1 (Kommazahlen werden abgeschnitten)
i = i.subtract(BigInteger.TEN); // -9
i = i.pow(2); // 81
BigDecimal d = BigDecimal.ONE; // 1
d = d.multiply(BigDecimal.TEN); // 10
d = d.add(new BigDecimal("8")); // 18
d = d.divide(BigDecimal.TEN); // 1.8
d = d.subtract(BigDecimal.TEN); // -8.2
d = d.pow(2); // 67.24

Beim Teilen eines BigDecimals haben Sie zusätzlich noch die Möglichkeit, eine Skalierung und einen Rundungsmodus zu übergeben. Beispielsweise könnte bei einer letzten Ziffer von fünf oder größer aufgerundet, ansonsten abgerundet werden. Ist die Nachkommazahl des Ergebnisses nicht endlich (bspw. 1/3), muss zwingend eine Skalierung (und folglich auch ein Rundungsmodus) angegeben werden, da ansonsten eine ArithmeticException geworfen wird.

try {
  System.out.println(BigDecimal.ONE.divide(new BigDecimal("3"))); // ArithmeticException
}
catch (ArithmeticException ae) {
  System.out.println(BigDecimal.ONE.divide(
      new BigDecimal("3"),  // durch 3
      5, // Auf 5 Nachkommastellen gerundet
      BigDecimal.ROUND_HALF_UP // Bei 0.5 aufrunden
  )); // 0.33333
}

Beim BigInteger steht zusätzlich noch die mod-Funktion zur Verfügung.

System.out.println(BigInteger.ONE.mod(new BigInteger("2"))); // 1

Betrag, Vergleich, Minimum und Maximum

Über max und min wird der größere bzw. kleinere Zahlenwert zurückgeliefert, abs liefert den Absolutwert.

System.out.println(BigInteger.ONE.min(BigInteger.TEN)); // 1
System.out.println(BigInteger.ONE.max(BigInteger.TEN)); // 10
System.out.println(new BigInteger("-2").abs()); // 2
System.out.println(BigDecimal.ONE.min(BigDecimal.TEN)); // 1
System.out.println(BigDecimal.ONE.max(BigDecimal.TEN)); // 10
System.out.println(new BigDecimal("2").abs()); // 2

Möchten Sie zwei BigInteger bzw. BigDecimal miteinander vergleichen, sollten Sie unbedingt die Methode compareTo verwenden, da equals nur die interne Darstellung überprüft.

BigDecimal one1 = new BigDecimal("1");
BigDecimal one2 = new BigDecimal("10E-1");
System.out.println(one1.equals(BigDecimal.ONE)); // true
System.out.println(one1.equals(one2)); // false
System.out.println(one1.compareTo(BigDecimal.ONE) == 0); // true
System.out.println(one1.compareTo(one2) == 0); // true

Bit-Manipulation

Ein BigInteger erlaubt auch die Manipulation der Bits wie bspw. ein int oder long. Bei einem BigDecimal ist das nicht möglich. Anbei eine Auflistung der Bit-Methoden eines BigIntegers.

// and führt einen AND-Vergleich durch
System.out.println(BigInteger.TEN.and(BigInteger.ONE)); // 0
// Gleichbedeutend mit
System.out.println(10 & 1); // 0
// andNot führt einen AND-Vergleich mit dem negierten Übergabewert durch
System.out.println(BigInteger.TEN.andNot(BigInteger.ONE)); // 10
// Gleichbedeutend mit
System.out.println(10 & (-1)); // 10
// or führt einen OR-Vergleich durch
System.out.println(BigInteger.TEN.or(BigInteger.ONE)); // 11
// Gleichbedeutend mit
System.out.println(10 | 1); // 11
// xor führt einen XOR-Vergleich durch
System.out.println(BigInteger.TEN.xor(BigInteger.ONE)); // 11
// Gleichbedeutend mit
System.out.println(10 ^ 1); // 11
// clearBit setzt das Bit an der gegebenen Position auf 0
System.out.println(BigInteger.TEN.clearBit(1)); // 8
// Gleichbedeutend mit
System.out.println(10 & ~(1 << 1)); // 8
// setBit setzt das Bit an der gegebenen Position auf 1
System.out.println(BigInteger.TEN.setBit(5)); // 42
// Gleichbedeutend mit
System.out.println(10 | (1 << 5)); // 42
// flipBit wandelt das Bit an der gegebenen Position in eine 0, falls es
// ursprünglich eine 1 war, und in eine 1, falls es ursprünglich eine 0 war
System.out.println(BigInteger.TEN.flipBit(4)); // 26
// Gleichbedeutend mit:
System.out.println(10 ^ (1 << 4)); // 26
// shiftLeft verschiebt die Bits um eine gegebene Stelle nach Links
System.out.println(BigInteger.TEN.shiftLeft(1)); // 20
// Gleichbedeutend mit
System.out.println(10 << 1);
// shiftRight verschiebt die Bits um eine gegebene Stelle nach Rechts
System.out.println(BigInteger.TEN.shiftRight(1)); // 5
// Gleichbedeutend mit
System.out.println(10 >> 1);
// testBit gibt true zurück, falls das Bit an der gegebenen Stelle gesetzt
// ist
System.out.println(BigInteger.TEN.testBit(1)); // true
// Gleichbedeutend mit
System.out.println((10 & (1 << 1)) != 0); // true&#91;/sourcecode&#93;

<strong>Rundungsmodus und Skalierung</strong>

Sie können den Rundungsmodus und die Skalierung eines <code>BigDecimal</code> - ähnlich wie beim Teilen - individuell festlegen. Mit <code>setScale(int scale)</code> erhalten Sie einen <code>BigDecimal</code> mit den angegebenen Nachkommastellen, <code>setScale(int scale, int roundingMode)</code> setzt zusätzlich noch den Rundungsmodus.

Folgende Modi sind möglich:
<ul>
 	<li><strong>BigDecimal.ROUND_CEILING</strong> - rundet hin zur positiven Unendlichkeit</li>
 	<li><strong>BigDecimal.ROUND_DOWN</strong> - rundet hin zur Null</li>
 	<li><strong>BigDecimal.ROUND_FLOOR</strong> - rundet hin zur negativen Unendlichkeit</li>
 	<li><strong>BigDecimal.ROUND_HALF_DOWN</strong> - rundet kaufmännisch, außer wenn die zu rundene Zahl eine fünf ist. In diesem Fall wird abgerundet</li>
 	<li><strong>BigDecimal.ROUND_HALF_EVEN</strong> - rundet kaufmännisch, außer wenn die zu rundene Zahl eine fünf ist. In diesem Fall wird zur geraden Zahl hin gerundet</li>
 	<li><strong>BigDecimal.ROUND_HALF_UP</strong> - rundet kaufmännisch</li>
 	<li><strong>BigDecimal.ROUND_UNNECESSARY</strong> - gibt an, dass keine Rundung erforderlich ist</li>
 	<li><strong>BigDecimal.ROUND_UP</strong> - rundet von der Null weg</li>
</ul>
Ein paar Beispiele:

System.out.println(new BigDecimal("1.29").setScale(1, BigDecimal.ROUND_CEILING)); // 1.3
System.out.println(new BigDecimal("-1.29").setScale(1, BigDecimal.ROUND_CEILING)); // -1.2
System.out.println(new BigDecimal("1.29").setScale(1, BigDecimal.ROUND_FLOOR)); // 1.2
System.out.println(new BigDecimal("-1.29").setScale(1, BigDecimal.ROUND_FLOOR)); // -1.3
System.out.println(new BigDecimal("1.55").setScale(1, BigDecimal.ROUND_HALF_DOWN)); // 1.5
System.out.println(new BigDecimal("1.35").setScale(1, BigDecimal.ROUND_HALF_EVEN)); // 1.4
System.out.println(new BigDecimal("1.25").setScale(1, BigDecimal.ROUND_HALF_EVEN)); // 1.2
System.out.println(new BigDecimal("-1.25").setScale(1, BigDecimal.ROUND_HALF_UP)); // -1.3
System.out.println(new BigDecimal("0.12345").setScale(5, BigDecimal.ROUND_UNNECESSARY)); // 0.12345
System.out.println(new BigDecimal("0.009").setScale(2, BigDecimal.ROUND_DOWN)); // 0.00
System.out.println(new BigDecimal("-0.001").setScale(2, BigDecimal.ROUND_UP)); // -0.01

Konvertierung in primitive Datentypen

Sie können große Zahlen auch problemlos wieder in primitive Datentypen umwandeln. BigInteger und BigDecimal bieten hierfür die Methoden intValue(), longValue(), floatValue() und doubleValue() an. Beachten Sie jedoch, dass ein BigInteger oder ein BigDecimal nicht immer verlustfrei (da zu groß oder zu klein) in einen primitiven Datentyp konvertiert werden kann.

System.out.println(new BigInteger("2").intValue()); // 2
System.out.println(new BigInteger("100000000000").intValue()); // 1215752192

Möchten Sie überprüfen, ob die Konvertierung verlustfrei abgelaufen ist, können Sie intValueExact, longValueExact, shortValueExact und byteValueExact der Klasse BigDecimal verwenden. Ist die Konvertierung verlustbehaftet, wird eine ArithmeticException geworfen.

BigDecimal bd = new BigDecimal("200");
System.out.println(bd.longValueExact()); // 200
System.out.println(bd.intValueExact()); // 200
System.out.println(bd.shortValueExact()); // 200
System.out.println(bd.byteValueExact()); // ArithmeticException

Selbes funktioniert bei einem BigDecimal übrigens auch mit toBigInteger bzw. toBigIntegerExact für einen BigInteger.

Konvertierung in einen String

Neben der gewöhnlichen toString()-Darstellung unterstützt ein BigInteger noch die Möglichkeit, den Zahlenwert in einem anderen Zahlensystem als dem Dezimalsystem darzustellen. Hierzu wird die Methode toString(int radix) verwendet.

BigInteger bi = new BigInteger("42");
System.out.println(bi.toString()); // 42
System.out.println(bi.toString(16)); // 2a
System.out.println(bi.toString(2)); // 101010

Bei einem BigDecimal gibt es neben toString() (interne Darstellung wird zurückgeliefert) noch die Methoden toPlainString() (Rückgabe ohne Exponent) und toEngineeringString() (Rückgabe mit Exponent – sofern sinnvoll).

BigDecimal bd = new BigDecimal("1.53254E10");
System.out.println(bd.toString()); // 1.53254E+10
System.out.println(bd.toPlainString()); // 15325400000
System.out.println(bd.toEngineeringString()); // 15.3254E+9

4 Replies to “11.03 Rechnen mit großen Zahlen”

  1. Markus

    Hallo,

    schön gemacht. Im Absatz „Basismathematik“ sollte ich der ersten Zeile des Quelltextes die Deklaration der Variablen i stehen, oder? Also ‚BigInteger i = BigInteger.ONE; // 1‘.

    Weiter so mit eurem super Blog!
    Markus

  2. Cyrill Brunner

    Mir ist ein kleiner Fehler aufgefallen: Bei der „Ggleichbedeutung“ des OR-Vergleiches bei BigInteger fehlt das Ergebnis als Kommentar. Ausserdem wird beim XOR-Vergleich wieder die or-Methode aufgerufen ^^

    Grüße
    Cyrill

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.