Apfloat tutorial

Contents:

Classpath setting
First example
Constructing Apfloats
Double and float constructor caveats
Apfloats are immutable
Precision
Output
Advanced mathematical functions
Integers
Complex numbers
Rational numbers
Using some other radix than 10
Equality and comparison
Formatting

Classpath setting

To use the apfloat library, you need to add the apfloat.jar file to the classpath. Depending on the development environment this is done in different ways. In a command prompt or shell the CLASSPATH environment variable simply needs to be set to include the path to the apfloat.jar file.

To run the apfloat samples, you can additionally append the samples.jar to the classpath. Similarly the calculator application can be run by appending calc.jar to the classpath.

First example

A simple example program for printing the square root of two to one thousand digits of precision is as follows:

import org.apfloat.Apfloat;
import org.apfloat.ApfloatMath;

public class Test
{
    public static void main(String[] args)
    {
        Apfloat x = new Apfloat(2, 1000);
        System.out.println(ApfloatMath.sqrt(x));
    }
}

Constructing Apfloats

There are multiple ways to construct an Apfloat object. The following are some examples:

Apfloat x = new Apfloat(2)Constructs an Apfloat with value 2 and infinite precision. This happens because an integer is used for the value and integers are exact values i.e. have infinite precision.
Apfloat x = new Apfloat("2.00")Constructs an Apfloat with value 2 and a precision of 3 digits. This happens because a string is used to specify the value: by default the number of significant digits in the string specifies the precision of the number.
Apfloat x = new Apfloat(2, 100)Constructs an Apfloat with value 2 and precision 100 digits. The second argument can be used to specify the precision.
Apfloat x = new Apfloat("2.00", 10)Constructs an Apfloat with value 2 and precision 10 digits. The second argument can be used to specify the precision.
Apfloat x = new Apfloat("0.5", Apfloat.INFINITE)Constructs an Apfloat with value 0.5 and infinite precision. The symbolic constant Apfloat.INFINITE can be used to specify infinite precision, which is useful especially for the string constructor.
Apfloat x = new Apfloat(2.0)Constructs an Apfloat with value 2 and a precision of 16 digits. This happens because the default precision for doubles is 16 decimal digits.
Apfloat x = new Apfloat(2.0f)Constructs an Apfloat with value 2 and a precision of 8 digits. This happens because the default precision for single-precision floats is 8 decimal digits.

Double and float constructor caveats

In general, it's not recommended to use any of the Apfloat constructors taking a double or float to specify the value of the number. This is because the conversion from a float or double to the apfloat internal presentation does not happen exactly. Therefore, even if you specify the number to have more precision than the default, an Apfloat constructed from a double will never be accurate to more than 16 digits and from a float more than 8 digits. So, just don't use them.

Many floating-point numbers can't even be presented exactly as such in a float or double. Internally, floats and doubles are specified as an integer multiplied by a power of two (the exponent of two can be negative), as per the IEEE 754 standard for binary floating-point numbers. So, numbers that aren't powers of two multiplied by some integer can't be presented exactly as a float or double. Try the following:

System.out.println((double) 0.1f);

Instead of printing 0.1 as one might expect, the printout is something like 0.10000000149011612. So, a float with value 0.1 isn't really 0.1 at all. Therefore it would not make sense to try to construct an Apfloat with such a value and say it should have e.g. 50 digits of precision.

Furthermore, this can and does happen also with numbers that can be presented exactly as floats or doubles. For example:

Apfloat x = new Apfloat(0.5, 50);
System.out.println(x);

The above might print, not 5e-1 but something like 5.00000000000000059604644775390625e-1

Apfloats are immutable

Apfloats are immutable. A common mistake is to attempt something like the following:

Apfloat a = new Apfloat(2);
Apfloat b = new Apfloat(3);
a.add(b);
System.out.println(a);

If the above is supposed to add b to a and then print the result, the code does not work. Instead of printing 5 it prints 2.

This happens because a.add(b) does not modify a by adding b to it, but instead performs the addition without modifying a and then returns the result. The correct code (to print 5) is then:

Apfloat a = new Apfloat(2);
Apfloat b = new Apfloat(3);
a = a.add(b);
System.out.println(a);

Precision

The concept of precision was mentioned already earlier. Every Apfloat object has both a value and a precision. The precision affects most mathematical operations performed with the number.

The above example used the square root function, which is calculated with the method ApfloatMath.sqrt(). The square root of two, for example, can't be presented exactly in a decimal printout, since it's an irrational number (it has an infinite, non-repeating decimal expansion). How does the square root function then work? It calculates the value of the square root to the same precision that the number given as the parameter has. For example (see the section on constructing for an explanation how the precision of the parameter is determined):

ApfloatMath.sqrt(new Apfloat(2, 100))Calculates the square root of two to 100 digits of precision. This happens because the number given as the parameter has a precision of 100 digits.
ApfloatMath.sqrt(new Apfloat("2.00"))Calculates the square root of two to 3 digits of precision. This happens because the number given as the parameter has a precision of 3 digits.
ApfloatMath.sqrt(new Apfloat(2))The input number has infinite precision. But the square root of 2 can't be calculated to infinite precision. Therefore, this code throws an org.apfloat.InfiniteExpansionException.

Elementary operators

The behavior of precision can sometimes be unexpected even with the basic mathematical operators. The following explains the handling of precision.

Multiplication and division

The precision of the result of a multiplication or division is the minimum of the precision of the two operands.

Such a precision for multiplication makes most sense if you think about the precision for some physical measure. For example, if you have a wooden stick that is 100cm long, to an accuracy of 1cm, then the precision of the length of the stick is 3 digits. The stick could be 99cm long or 101cm long. In other words, the length of the stick is 100±1cm.

Now, let's assume you have exactly 100 of such sticks (you counted every one of them). If you put all of the sticks together to make one long stick, then how long is that stick, and how accurately you know its length? To calculate the length, you must perform a multiplication of two numbers: the length of one stick (which you know to a precision of 3 digits) and the number of sticks (which you know to infinite precision, since it's an integer). The total length is then of course 10000±100cm, i.e. a number with 3 digits of precision (the minimum of 3 and ∞).

On the other hand, if you just have a large bunch of these sticks, but you don't know exactly how many sticks there are, then the result has different precision. For example if you have a pile of 100 sticks, give or take 10, then you know there are 100±10 sticks, i.e. a number with only two digits of precision. If you put all of them in a row to make one long stick, then you can calculate the total length of the sticks to only two digits of precision. It's at least 100cm*90 and at most 100cm*110 i.e. 10000±1000cm, a number with two digits of precision (again, the minimum of 3 and 2).

Addition and subtraction

To determine the precision of the result, the numbers are lined up just like with the elementary school addition and subtraction algorithm. Whichever number has less precision, considering the scales of the numbers (the location of the decimal point), determines the precision of the result. For example:

  111.111111    // Precision of 9 digits
+  22.222       // Precision of 5 digits
------------
  133.333       // The extra 3 digits of precision that the first operand has does not help; the result has only 6 digits of precision here

Subtraction works in a similar fashion.

With addition, if there is a carry, it can increase the precision by one digit. For example:

  987.654       // Precision of 6 digits
+  51.234       // Precision of 5 digits
---------
 1038.888       // Because of the carry, there is one more digit of precision than what might be expected; precision is 7 digits

With subtraction, if there is a loss of digits, precision can decrease significantly. For example:

  111.222333    // Precision of 9 digits
- 111.222222    // Precision of 9 digits
------------
    0.000111    // Most of the digits cancel out each other; the result has only 3 digits of precision left

The precision of zero

Note that a number with a value of zero always has infinite precision, no matter if you try to specify otherwise. This can cause unexpected behavior in rare cases. For example:

ApfloatMath.acos(new Apfloat(0, 100))

The above would not return π/2 to 100 digits of precision. Instead it would throw an exception, because the parameter with value zero has an infinite precision (and not 100 digits of precision as specified). The library can't calculate π/2 to infinite precision, obviously; an exception would be thrown.

If you think the above is a problem, then there are two helper classes to get around this problem: FixedPrecisionApfloatHelper and FixedPrecisionApcomplexHelper. They always round the results to a specified precision and avoid this problem.

Fixed-point and floating-point output

By default, when you convert an Apfloat to a string (e.g. for printing it), it uses a floating-point format. For example:

Apfloat x = new Apfloat("0.5");
System.out.println(x);

The above does not print 0.5 but instead 5e-1. It's the same value but in a floating-point format that specifies the mantissa and exponent, meaning 5*10-1.

To get the output of 0.5 the Apfloat can be converted to a string with the method toString(true), for example:

Apfloat x = new Apfloat("0.5");
System.out.println(x.toString(true));

This is often very helpful, however there are some caveats. If the exponent of the number is very large compared to the number of significant digits, the string can become very long. For example:

Apfloat x = new Apfloat("1.2345e1000000");
System.out.println(x);
System.out.println(x.toString(true));

The first line outputs compactly 1.2345e1000000, however the second line of output is approximately one million characters long.

Advanced mathematical functions

In addition to the square root, the ApfloatMath class contains many other mathematical functions: the exponential function and logarithm, trigonometric functions, hyperbolic functions, and the inverse trigonomeric and hyperbolic functions. And many more. The details are described in the Apfloat JavaDocs.

Integers

There is a subclass of Apfloat for arbitrary-precision integers, the Apint class. Apint numbers work in much the same way as the floating-point Apfloat numbers, except the precision of an Apint is always infinite. The ApintMath class contains some integer-specific mathematical functions.

Complex numbers

The superclass of Apfloat is the Apcomplex class, for arbitrary-precision complex numbers. An Apcomplex can be constructed from two Apfloats, to define the real and imaginary parts. There is also a string constructor. Apcomplex has mostly the same methods that Apfloat has, and the ApcomplexMath class has correspondingly complex mathematical functions. For example:

Apfloat x = new Apfloat("3.00");
Apfloat y = new Apfloat("4.00");
Apcomplex w = new Apcomplex(x, y);
Apcomplex q = new Apcomplex("(5.00,6.00)");
System.out.println(w.multiply(q).toString(true));   // Prints (-9, 38)

Rational numbers

There is also a class for arbitrary-precision rational numbers, the Aprational class. Aprationals work in much the same way as the floating-point Apfloat numbers and Apint integers. The AprationalMath class contains some rational number specific mathematical functions.

Using some other radix than 10

Every number has a value, precision and radix. By default radix 10 i.e. the decimal radix is used. To specify a different radix to be used, the radix can be specified as the third argument in the Apfloat constructor (or second argument in the Apint constructor). Only numbers with the same radix can be used in mathematical operations. To convert a number to a different radix, use the toRadix(int) method. For example:

Apfloat x = new Apfloat(1);
Apfloat y = new Apfloat("10", Apfloat.DEFAULT, 16);
System.out.println(x.add(y.toRadix(10)).toString(true));

The above should print 17. Note that the constant Apfloat.DEFAULT can be used for the precision of the number, in case the radix is specified in the constructor but default precision is desired.

Equality and comparison

All of the arbitrary precision number classes have equals() and hashCode() methods. The Apfloat class also implements Comparable. These methods usually behave well, e.g. compareTo() returns zero when equals() returns true. However, the handling of comparison and equality is done differently than all of the matematical operations (such as addition and subtraction). Apfloat numbers may have "residual" digits after the significant digits specified by the number's precision. The equals() and compareTo() methods correctly ignore these, however for performance reasons, all other operations do not. Therefore, it could be e.g. that a.equals(b) and c.equals(d) are true but a.add(c).equals(b.add(d)) is false.

As usual with floating-point numbers, it's better to compare that two numbers are equal to each other within some margin of error. Apfloats have a method, equalDigits() for comparing to how many digits the numbers are equal. The following pathological example illustrates this problem:

Apfloat a = new Apfloat(1000000, 6);
Apfloat b = new Apfloat(1000005, 6);
Apfloat c = new Apfloat(2000000, 6);
Apfloat d = new Apfloat(2000005, 6);
System.out.println(a.equals(b));                        // true, equal within 6 digits of precision
System.out.println(c.equals(d));                        // true, equal within 6 digits of precision
System.out.println(a.add(c).equals(b.add(d)));          // false, not equal within 6 digits of precision
System.out.println(a.equalDigits(b));                   // 6
System.out.println(c.equalDigits(d));                   // 6
System.out.println(a.add(c).equalDigits(b.add(d)));     // 5 due to round-off error in b.add(d)

Formatting

Apfloat implements the Formattable interface, so they can be used in e.g. System.out.printf(). The following formatting flags can be used with Apfloats:

#Equivalent to toString(true) i.e. prints the number using the fixed-point notation (not floating-point), e.g. 123.45 instead of 1.2345e2.
12345Minimum width of the output (padding with spaces).
.12345Maximum precision of the number to output.
-Use left justification for the number.

Only the %s conversion (string) works with Apfloats, like with any Formattable objects. For example:

Apfloat x = ApfloatMath.pi(50).multiply(new Apfloat(100));
String.format("%s", x)                  // "3.1415926535897932384626433832795028841971693993751e2"
String.format("%100s", x)               // "                                               3.1415926535897932384626433832795028841971693993751e2"
String.format("%-100s", x)              // "3.1415926535897932384626433832795028841971693993751e2                                               "
String.format("%.10s", x)               // "3.141592653e2"
String.format("%#s", x)                 // "314.15926535897932384626433832795028841971693993751"
String.format(Locale.GERMAN, "%s", x)   // "3,1415926535897932384626433832795028841971693993751e2"