Multiple precision library AMP

# Introduction

The AMP library (ALGLIB Multiple Precision) is an object oriented interface for the MPFR library. This interface was created as part of the ALGLIB project (see www.alglib.net and alglib.sources.ru). In order to illustrate the reason why it was necessary to create yet another interface for MPFR, we should first give a brief introduction to the ALGLIB project.

The goal of the project is to create a numerical analysis library which provides programs in different programming languages for each algorithm. This goal is attained by using automatic translation. Algorithms are stored as a pseudocode which can then be translated into the required programming language. Pseudocode syntax allows automatic translation into the most popular programming languages (e.g. C++, C#, Pascal etc.). It should be noted that, in a number of cases, programs in pseudocode can be modified to use multiple precision arithmetic. For this purpose, the AMP library is used as an interface for MPFR substituting standard double precision data type.

Programs using AMP were originally implemented to work with a 64-bit accuracy. This fact has an impact on a library design. Because of this, the following features had appeared: compile time precision definition (in AMP accuracy is given by using the template class parameterization) and prohibition to operate with numbers of different precisions (e.g. addition of 256 bit and 512 bit numbers). These restrictions are necessary in order to use the library within the ALGLIB project.

It should be noted that although AMP was designed especially for the ALGLIB project, it could be useful as just an MPFR interface. Particularly, automatic garbage collection could be useful for a number of applications. It is this part of functionality that we are going to develop, because the ALGLIB part has already been developed accurately enough and doesn't need any extension.

# License

The AMP library is available under triple license conditions MPL 1.1 / GPL 2.0 / LGPL 2.1

Explanation. Triple license conditions mean that the user can choose a license to use a program with. When distributing and/or modifying the AMP, the user has two options. First, he can distribute it using the same triple license conditions. In this case, the library recipient will have the same right to choose a license. Second, the user can distribute the AMP library subject to conditions of a GPL 2.0 license (or, depending on his choice, LGPL 2.1) and prohibit the use of other licenses.

These distribution conditions allow the use of the AMP library in both proprietary/non-free programs (this is allowed by the MPL license) and GPL/LGPL licensed software. It is the author's opinion that such conditions will cause wide distribution and extensive use of a library.

# How to Start

To use the AMP library you need to:

- add the GMP library to the project
- add the MPFR library to the project
- add the AP library to the project (see www.alglib.net or alglib.sources.ru)
- add the main library module (amp.cpp) to the project
- include the library header file (amp.h). It contains all the necessary declarations

# AMP Basics

Real numbers. MPFR (and, therefore, AMP) is almost compliant with IEE-754 which describes real number arithmetic implementation requirements. The following features are available: signed zeros, finite numbers, positive and negative infinites, NANs. Denormalized numbers are not supported.

Operations and rounding. MPFR supports four types of rounding: round towards plus infinity, round towards minus infinity, round to zero and round to the nearest representable value. AMP always uses the last one. Thus, all the real number operations are performed by using exact rounding. Special values (infinites or NaNs) appearing during the calculation don't cause exception generation and the calculation is continued under normal conditions. Operations with infinites and signed zeros are performed according to the rules of limits.

Precision. Real number precision is a number of bits in a floating-point mantissa, including the high-order one (non-zero bit). For example, if the precision of a given number is equal to 53, this number is of a standard `double` type. The AMP library allows to set any precision no less than 32 (which represents the `signed long` type) including numbers which are not powers of two. The restriction was added due to compatibility reasons: a number of mathematical programs suppose that we can set any integer value to the real variable without any loss of accuracy.

Compile time precision definition. The main feature of the AMP library is that class performing multiple precision real numbers operations is a template class with one parameter specifying precision of a stored value. Hence, the precision to perform the calculation with is known at the compile time (on template specialization) and could not be changed in run-time. This restriction was introduced knowingly. The reason is that the AMP library was designed to be used mainly within the ALGLIB project. In that case, the AMP library is used by the automatically translated programs. Automatically generated codes cannot work with dynamic precision real numbers due to the number of problems. For example, we can't say what precision ought to be used when some elements of the matrix have 128 bit precision and some have 256 bit. That's the reason why this restriction was introduced.

Mixed precision operands. There are several floating point types in C++: `float`, `double`, `long double`. At that, if operands have a different precision, it is usually performed by automatic casting from less to finer precision (e.g. sum of `float` and `double` is `double`). The AMP library does not allow automatic casting of operands with different precision. For example, the sum of `amp::ampf<128>` and `amp::ampf<144>` will cause compilation errors because it is required to cast operand types. It is partly defined by C++ restrictions (which could be overcome), and partly by the developer's conviction that in numerical analysis such functionality is very seldom required.

Garbage collection. Since in MPFR the memory for real numbers is allocated dynamically, real number initialization takes a great deal of time comparatively. In order to increase performance, the AMP library doesn't free used memory, but puts needless variables into the list. When it is necessary to create a new real number, it is taken from the list.

No multithreading. The AMP library doesn't support multithreading. The library can work in a multithreaded environment, but it can't be used by two or more threads simultaneously.

# Exception Classes

The AMP library includes the following exception classes:

1. `amp::incorrectPrecision` - generated when the real number of an invalid precision is initialized (less than 32 or more than MPFR library limitations)
2. `amp::invalidConversion` - generated when the real number to be rounded using `trunc`/`round`/`ceil`/`floor` doesn't fit in the `signed long` type
3. `amp::internalError` - generated when an internal AMP library error occurs (it should not happen under normal conditions)

# amp::mpfr_record and amp::mpfr_storage

These classes are service classes. Although these classes are available to the programmer, he shouldn't use them, because they aren't intended to be used outside the AMP library.

# amp::ampf

## Key Concepts

`amp::ampf class` is a template class incapsulating real numbers in MPFR library format. Class implements memory allocation, memory release and garbage collection. Arithmetic operations are overloaded. In most cases, code using this class is almost the same as code working with real numbers.

## amp::ampf members

        template<unsigned int Precision>
        class ampf
        {
        public:
            ~ampf();

            ampf ();
            ampf (long double v);
            ampf (double v);
            ampf (float v);
            ampf (signed long v);
            ampf (unsigned long v);
            ampf (signed int v);
            ampf (unsigned int v);
            ampf (signed short v);
            ampf (unsigned short v);
            ampf (signed char v);
            ampf (unsigned char v);
            ampf (const std::string &s);
            ampf (const char *s);

            ampf(const ampf& r);
            template<unsigned int Precision2>
            ampf(const ampf<Precision2>& r);

            ampf& operator= (long double v);
            ampf& operator= (double v);
            ampf& operator= (float v);
            ampf& operator= (signed long v);
            ampf& operator= (unsigned long v);
            ampf& operator= (signed int v);
            ampf& operator= (unsigned int v);
            ampf& operator= (signed short v);
            ampf& operator= (unsigned short v);
            ampf& operator= (signed char v);
            ampf& operator= (unsigned char v);
            ampf& operator= (const char *s);
            ampf& operator= (const std::string &s);
            ampf& operator= (const ampf& r);
            template<unsigned int Precision2>
            ampf& operator= (const ampf<Precision2>& r);

            template<class T>
            ampf& operator+=(const T& v);
            template<class T>
            ampf& operator-=(const T& v);
            template<class T>
            ampf& operator*=(const T& v);
            template<class T>
            ampf& operator/=(const T& v);

            mpfr_srcptr getReadPtr() const;
            mpfr_ptr getWritePtr();

            bool isFiniteNumber() const;
            bool isPositiveNumber() const;
            bool isZero() const;
            bool isNegativeNumber() const;
            const ampf getUlpOf();

            double toDouble() const;
            std::string toHex() const;
            std::string toDec() const;

            static const ampf getUlpOf(const ampf &x);
            static const ampf getUlp();
            static const ampf getUlp256();
            static const ampf getUlp512();
            static const ampf getMaxNumber();
            static const ampf getMinNumber();
            static const ampf getAlgoPascalEpsilon();
            static const ampf getAlgoPascalMaxNumber();
            static const ampf getAlgoPascalMinNumber();
            static const ampf getRandom();
        };

# Variable Declaration and Initialization

`amp::ampf` is a template class having one parameter which specifies the precision of a real number. Thus, it is necessary to determine what degree of precision is required before using this class. A number of constructors are defined for class initialization. They can take instance of the same class, integer numbers, standard real numbers (float, double, long double) and strings as the input. Strings can have fractional ("12.34") or exponential form, at that the mantissa could be decimal or hexadecimal (see MPFR library description).

Example:

        int i = 3;
        double d = 2.5;
        amp::ampf<128> a(i), b(d), c("2.33");
        a = "1.56E-3";
        b = a;

## Text Conversions

To convert a number into a text form, you can use member functions `toDec()` and `toHex()`. They return decimal and hexadecimal exponential representation of a number (at that, the hexadecimal conversion is performed faster than the decimal one, therefore if performance is critical it is better to use the hexadecimal conversion). You can't set the format of the result: all significant digits of the number are output.

Example:

        amp::ampf<128> a;
        a = "11";
        printf("%s\n", amp::sqrt(a).toDec().c_str());

## Properties Checking

In order to check whether a real number is finite (as opposed to one of the special values), positive, zero or negative, use member function `isFiniteNumber()`, `isPositiveNumber()`, `isZero()`, `isNegativeNumber()`.

## Information Functions

A number of functions were designed to get information about data type properties:

- member function `getUlpOf()` returns a value of the ULP, Unit-in-Last-Place, of the given `x`. This value is similar to machine precision. Static function `getUlpOf()` has the same functionality.
- static function `getUlp()` is equivalent to `amp::ampf<Precision>::getUlpOf(1)`
- static function `getMaxNumber()` returns the biggest representable (using this data type) positive number
- static function `getMinNumber()` returns the smallest representable (using this data type) positive number
- functions `getAlgoPascalEpsilon()`, `getAlgoPascalMaxNumber()`, `getAlgoPascalMinNumber()` are similar to the above-mentioned ones, but they return values with safety margin. These functions are used by the ALGLIB project programs.

## Access to the `mpfr_t` Pointer

The `getReadPtr()` and `getWritePtr()` functions provide access to mpfr_t pointer. The `getReadPtr()` function allows to get a read-only pointer, which can be passed into an MPFR library function as an argument. The `getWritePtr()` function allows to get a writable pointer, which can be passed into an MPFR library function as a place to store the result in.

It should be noted that the AMP library uses copy-on-write technique, i.e. pointer which has been received by `getReadPtr()` could be common to several objects. Thus, such a pointer must not be used in operations which can change its contents.

A pointer which has been received by `getWritePtr()` can be used in operations which can change its contents, but these operations should not change the precision of a pointer contents. You should take into account that due to its using copy-on-write technique, the pointer which has been received by `getWritePtr()` could differ from the pointer which had previously been received by `getReadPtr()`.

## Random Numbers Generation

In order to generate random numbers from [0, 1), use the static function `getRandom()`. At first, the call function initializes a random number generator using system time.

## Arithmetic Operations

`amp::ampf` class overloads comparison, addition, subtraction, multiplication and division operations. The result is rounded by using exact rounding. Two objects of `amp::ampf` class with the same precision could be the operands. It is acceptable to use objects of `amp::ampf` class and variables of standard data types. You can also use two objects of `amp::ampf` class with different precision as operands, but one of them should be casted to precision of another one.

Example:

        amp::ampf<128> a(3), b(4), c(99);
        amp::ampf<256> d(1);

        a = b+c;          //
        b = c+2.3;        // acceptable  expressions
        c = (2*a-5)/b;    //

        a = b+d;          // compile time error -
                          // arguments of a different type

        a = b + amp::ampf<128>(d); // this is the right way

        a = d;            // this is also acceptable

## Mathematical functions

All functions described here are members of the `amp` namespace. Thus, when you call, for example, the `sqr` function, you should write `amp::sqr`. Unless otherwise specified, the result is rounded up by using exact rounding.

template<unsigned int Precision>
const ampf<Precision> sqr(const ampf<Precision> &x)
Returns the square of `x`.

template<unsigned int Precision>
const int sign(const ampf<Precision> &x)
Returns:
+1, if X>0
-1, if X<0
0, if X=0

template<unsigned int Precision>
const ampf<Precision> abs(const ampf<Precision> &x)
Returns the absolute value of `x`.

template<unsigned int Precision>
const ampf<Precision> maximum(const ampf<Precision> &x, const ampf<Precision> &y)
Returns the maximum of two integers.

template<unsigned int Precision>
const ampf<Precision> minimum(const ampf<Precision> &x, const ampf<Precision> &y)
Returns the minimum of two real numbers.

template<unsigned int Precision>
const ampf<Precision> sqrt(const ampf<Precision> &x)
Returns the square root of `x`.

template<unsigned int Precision>
const signed long trunc(const ampf<Precision> &x)
Rounds towards zero. Throws `amp::invalidConversion` if result doesn't fit in the in the `signed long` type.

template<unsigned int Precision>
const ampf<Precision> frac(const ampf<Precision> &x)
Returns `x-trunc(x)`.

template<unsigned int Precision>
const signed long floor(const ampf<Precision> &x)
Rounds towards minus infinity. Throws `amp::invalidConversion` if result doesn't fit in the in the `signed long` type.

template<unsigned int Precision>
const signed long ceil(const ampf<Precision> &x)
Rounds towards plus infinity. Throws `amp::invalidConversion` if result doesn't fit in the in the `signed long` type.

template<unsigned int Precision>
const signed long round(const ampf<Precision> &x)
Rounds towards nearest integer, rounding halfway cases away from zero. Throws `amp::invalidConversion` if result doesn't fit in the in the `signed long` type.

template<unsigned int Precision>
const ampf<Precision> frexp2(const ampf<Precision> &x, mp_exp_t *exponent)
Splits a number into mantissa and exponent.

template<unsigned int Precision>
const ampf<Precision> ldexp2(const ampf<Precision> &x, mp_exp_t exponent)
Calculates x*2exponent.

template<unsigned int Precision>
const ampf<Precision> pi()
Returns `Pi`.

template<unsigned int Precision>
const ampf<Precision> halfpi()
Returns `0.5*Pi`.

template<unsigned int Precision>
const ampf<Precision> twopi()
Returns `2*Pi`.

template<unsigned int Precision>
const ampf<Precision> sin(const ampf<Precision> &x)
Calculates the sine of a value.

template<unsigned int Precision>
const ampf<Precision> cos(const ampf<Precision> &x)
Calculates the cosine of a value.

template<unsigned int Precision>
const ampf<Precision> tan(const ampf<Precision> &x)
Calculates the tangent of a value.

template<unsigned int Precision>
const ampf<Precision> asin(const ampf<Precision> &x)
Calculates the arcsine of a value.

template<unsigned int Precision>
const ampf<Precision> acos(const ampf<Precision> &x)
Calculates the arccosine of a value.

template<unsigned int Precision>
const ampf<Precision> atan(const ampf<Precision> &x)
Calculates the arctangent of a value.

template<unsigned int Precision>
const ampf<Precision> atan2(const ampf<Precision> &y, const ampf<Precision> &x)
Calculates the arc tangent of y/x. It produces correct results even when the resulting angle is near pi/2 or -pi/2 (x near 0).

template<unsigned int Precision>
const ampf<Precision> log(const ampf<Precision> &x)
Calculates the natural logarithm of x.

template<unsigned int Precision>
const ampf<Precision> log2(const ampf<Precision> &x)
Calculates the binary logarithm of x.

template<unsigned int Precision>
const ampf<Precision> log10(const ampf<Precision> &x)
Calculates the base 10 logarithm of x.

template<unsigned int Precision>
const ampf<Precision> exp(const ampf<Precision> &_x)
Calculates the exponential e to the x.

template<unsigned int Precision>
const ampf<Precision> sinh(const ampf<Precision> &x)
Calculates the hyperbolic sine of a value.

template<unsigned int Precision>
const ampf<Precision> cosh(const ampf<Precision> &_x)
Calculates the hyperbolic cosine of a value.

template<unsigned int Precision>
const ampf<Precision> tanh(const ampf<Precision> &_x)
Calculates the hyperbolic tangent of a value.

template<unsigned int Precision>
const ampf<Precision> pow(const ampf<Precision> &x, const ampf<Precision> &y)
Calculates x to the power of y.

# Vectors, Matrices and Basic Linear Algebra Operations

## Introduction

The AP library, part of the ALGLIB project, includes vector and matrix template classes as well as subroutines performing basic linear algebra operations (Level 1 BLAS). Since the AP library implements matrix classes using templates, you can use these classes when working with matrices of multiple precision numbers. Therefore, the AMP library doesn't include vector and matrix classes. Basic linear algebra operations are also implemented by using templates in the AP library, but AMP includes its own classes specially designed to work with multiple precision numbers. The AMP library subroutines are optimized to reduce memory manager loading, thus it is better to use these subroutines when performing vector operations over multiple precision numbers.

## Description

template<unsigned int Precision>
ampf<Precision> vDotProduct(ap::const_raw_vector< ampf<Precision> > v1, ap::const_raw_vector< ampf<Precision> > v2)
The subroutine calculates the scalar product of two vectors. For more information about `ap::const_raw_vector` see description of AP library.

template<unsigned int Precision>
void vMove(ap::raw_vector< ampf<Precision> > vDst, ap::const_raw_vector< ampf<Precision> > vSrc)
This function copies `vSrc` vector to `vDst`.

template<unsigned int Precision>
void vMoveNeg(ap::raw_vector< ampf<Precision> > vDst, ap::const_raw_vector< ampf<Precision> > vSrc)
This function copies `-vSrc` vector to `vDst`.

template<unsigned int Precision, class T2>
void vMove(ap::raw_vector< ampf<Precision> > vDst, ap::const_raw_vector< ampf<Precision> > vSrc, T2 alpha)
This function copies `alpha*vSrc` vector to `vDst`.

template<unsigned int Precision>
void vAdd(ap::raw_vector< ampf<Precision> > vDst, ap::const_raw_vector< ampf<Precision> > vSrc)
This function adds `vSrc` vector to `vDst`.

template<unsigned int Precision, class T2>
void vAdd(ap::raw_vector< ampf<Precision> > vDst, ap::const_raw_vector< ampf<Precision> > vSrc, T2 alpha)
This function adds `alpha*vSrc` vector to `vDst`.

template<unsigned int Precision>
void vSub(ap::raw_vector< ampf<Precision> > vDst, ap::const_raw_vector< ampf<Precision> > vSrc)
This function subtracts `vSrc` vector from `vDst` vector.

template<unsigned int Precision, class T2>
void vSub(ap::raw_vector< ampf<Precision> > vDst, ap::const_raw_vector< ampf<Precision> > vSrc, T2 alpha)
This function subtracts `alpha*vSrc` from `vDst` vector.

template<unsigned int Precision, class T2>
void vMul(ap::raw_vector< ampf<Precision> > vDst, T2 alpha)
This function multiplies vector by a number and stores the result in the same place

# amp::campf class

## Key concepts

`amp::campf` class implements multiple precision complex number operations. Real and imaginary parts are stored in `x` and `y` fields of type `amp::ampf`. You can perform arithmetic and comparison operations over the objects of this class. Moreover, a number of standard mathematical functions are defined for this data type.

## Members of amp::ampf

        template<unsigned int Precision>
        class campf
        {
        public:
            campf():x(0),y(0);
            campf(long double v);
            campf(double v);
            campf(float v);
            campf(signed long v);
            campf(unsigned long v);
            campf(signed int v);
            campf(unsigned int v);
            campf(signed short v);
            campf(unsigned short v);
            campf(signed char v);
            campf(unsigned char v);
            campf(const ampf<Precision> &_x);
            campf(const ampf<Precision> &_x, const ampf<Precision> &_y);
            campf(const campf &z):x(z.x),y(z.y);
            template<unsigned int Prec2>
            campf(const campf<Prec2> &z);

            campf& operator= (long double v);
            campf& operator= (double v);
            campf& operator= (float v);
            campf& operator= (signed long v);
            campf& operator= (unsigned long v);
            campf& operator= (signed int v);
            campf& operator= (unsigned int v);
            campf& operator= (signed short v);
            campf& operator= (unsigned short v);
            campf& operator= (signed char v);
            campf& operator= (unsigned char v);
            campf& operator= (const char *s);
            campf& operator= (const std::string &s);
            campf& operator= (const campf& r);
            template<unsigned int Precision2>
            campf& operator= (const campf<Precision2>& r);

            ampf<Precision> x, y;
        };

## Arithmetic Operations

`amp::campf` class overrides comparison, addition, subtraction, multiplication and division operations. Two objects of `amp::campf` class of the same precision could be the operands. It is acceptable to use objects of `amp::campf` and `amp::ampf` types and variables of standard data types as operands. You can also use `amp::campf` instances with different precision, but they should be casted.

## Standard Functions

template<unsigned int Precision>
const ampf<Precision> abscomplex(const campf<Precision> &z)
Returns the modulus of complex number z. It should be noted that the modulus calculation is performed using so called "safe" algorithm, that could never cause overflow when calculating intermediate results.

template<unsigned int Precision>
const campf<Precision> conj(const campf<Precision> &z)
Returns complex conjugate of z.

template<unsigned int Precision>
const campf<Precision> csqr(const campf<Precision> &z)
Returns the square of z.