.. _fundamental_types:
Fundamental numerical types in Piranha
======================================
Any computer algebra system needs to be able to represent fundamental numerical types such as
integers, rationals, (approximations of) reals, etc. C++ and Python provide basic numerical types
that can be used with Piranha's generic algorithms and data structures. However, especially in
C++, the numerical types provided by the language - while having the advantage of providing
very high performance - can be unsuitable for certain applications (e.g., all the standard integral
types provided by C++ have a finite range).
On the C++ side Piranha provides a set of additional fundamental numerical types that can interoperate
with the C++ fundamental types and that can be used with Piranha's generic algorithms and data structures
(e.g., as coefficient types in a polynomial). On the Python side, Piranha's fundamental types are
automatically translated to/from corresponding Python types whenever Pyranha's routine and data
structures are used.
The ``integer`` type
--------------------
Piranha's ``integer`` type is used to represent signed integers of arbitrary size (that is, the range
of the type is limited only by the available memory). The type is based on the ``mpz_t`` type from the
`GMP library `__. As an optimisation, for small numbers ``integer`` avoids
using dynamic memory allocation and can thus have better performance than a straightforward ``mpz_t``
wrapper.
``integer`` behaves much like a standard C++ integral type:
* a default-constructed ``integer`` object is initialised to zero;
* an ``integer`` object can be constructed from all the fundamental C++ types (construction from floating-point
types results in the truncation of the original value);
* an ``integer`` object can be converted to all the the fundamental C++ types (if the conversion to a C++ integral
type overflows the target type, an error is thrown);
* the division operator performs truncated division;
* in mixed-mode operations, the rank of ``integer`` is higher than any other C++ integral type but still
lower than floating-point types (e.g., ``integer + long`` results in an ``integer``, ``integer + float``
results in a ``float``).
The following C++ code illustrates some features of the ``integer`` type:
.. literalinclude:: ../../tutorials/integer.cpp
:language: c++
:linenos:
In the first lines, a few possible ways of constructing ``integer`` objects are shown, including a constructor
from string that allows to initialise an ``integer`` with arbitrarily large values. In the following lines,
some examples of arithmetics with basic C++ types are illustrated.
On line 28, we can see how the ``math::pow()`` function can be invoked in order to compute the exponentiation of
an ``integer``. C++ lacks a dedicated exponentiation operator and thus the functionality is provided by a function
instead. The ``math::pow()`` function is part of Piranha's mathematical library.
Line 38 highlights a couple of handy C++11 features. The snippet ``42_z`` is a *user-defined literal*: it results
in the construction of an ``integer`` object via the string ``"42"``. The constructed temporary object
is then assigned to a variable ``n`` in the statement ``auto n = 42_z``. The type of ``n`` is automatically
deduced via the keyword ``auto`` from the right-hand side of the assignment, thus ``n`` is an ``integer`` object.
This is arguably the C++ syntax that most closely matches Python's syntax.
On line 42, we use the ``math::binomial()`` function from the math library to compute the binomial coefficient
:math:`{42 \choose 21}`, passing the two arguments as ``integer`` objects created via the user-defined literal ``_z``.
As expected, on the Python side things look simpler:
.. _integer_py:
.. literalinclude:: ../../pyranha/tutorial/integer.py
:language: python
:linenos:
Python indeed provides multiprecision integers as basic types. Some notable differences exist between Python 2.x and
Python 3.x in this regard:
* Python 2.x has two different integral types, ``int`` and ``long``, representing small and large integers
respectively. Python 3.x instead has a single integral type named ``int``. Piranha is able to deal with both
cases;
* in Python 2.x, integer division is truncated and returns an integral; in Python 3.x, integral division is a true division
returning a ``float``.
In the first lines of the :ref:`Python code `, a few ways of constructing an integral object are shown.
Contrary to C++, big integers can be constructed directly without passing through a string constructor. Additionally,
Python has a dedicated exponentiation operator called ``**``, thus it is not necessary to resort to a separate
function for this operation.
On the last line, we see the first real example of using a Piranha function from Python. The :py:func:`pyranha.math.binomial`
function will take Python integers as arguments, convert them to C++ ``integer`` objects and pass them to the C++ function
``math::binomial()``. The output value of the C++ function will be converted back to a Python integer which will then
be returned by the :py:func:`pyranha.math.binomial` function.
The ``rational`` type
---------------------
A second fundamental type provided by Piranha is the ``rational`` class, which represents arbitrary-precision
rational numbers as ``integer`` numerator-denominator pairs. The ``rational`` type in Piranha extends smoothly
the standard C++ numerical hierarchy, and it obeys the following basic rules:
* a default-constructed ``rational`` object is initialised to zero;
* a ``rational`` object is always kept in the usual canonical form consisting of coprime numerator and denominator, with
the denominator always strictly positive. Zero is always represented as ``0/1``;
* a ``rational`` object can be converted to/from all the basic C++ numerical types and ``integer`` (the conversion to an integral
type computes the truncated division of numerator and denominator);
* in mixed-mode operations, the rank of ``rational`` is higher than that of ``integer`` but lower than that of floating-point
types.
The following C++ code showcases a few features of the ``rational`` class:
.. literalinclude:: ../../tutorials/rational.cpp
:language: c++
:linenos:
In the first code block, a few ways of constructing a ``rational`` are shown. In addition to the constructors from interoperable
types, a ``rational`` can also be constructed from an integral numerator-denominator pair. The string representation for a ``rational``
consists, intuitively, of the representation of numerator and denominator in base 10, separated by a ``/`` sign. The denominator can be
omitted if it is unitary.
In the second code block, some examples of arithmetic and logical operations involving ``rational`` and interoperable types are
displayed.
On lines 33-34, we use the ``math::pow()`` function to compute integral powers of a rational. ``math::pow()`` is able to accept
many different combinations of argument types. In this specific case, with a ``rational`` base and an integral exponent, the result
will be exact and its type will be ``rational``.
In the fourth code block, a couple of examples of conversion from ``rational`` are shown. Conversion to C++ integral types can fail
in case of overflow, whereas conversion to ``integer`` will never fail.
In the fifth code block, a few usages of the user-defined literal ``_q`` are displayed. ``_q`` can be appended to an integer literal
to signal that a ``rational`` object is to be constructed using that literal. This can be combined with the division operator
to yield a user-friendly syntax to initialise rational objects, as shown on line 51:
.. code-block:: c++
r = 42/13_q;
This line is effectively interpreted by the compiler as:
.. code-block:: c++
r = 42/rational{"13"};
In the last code block, we can see another invocation of the ``math::binomial()`` function. This time the top argument is a ``rational``,
wheras the bottom argument is an ``integer``. The specialisation of the binomial function for these two types will yield the exact
result as a ``rational``.
On the Python side things are again simpler:
.. _rational_py:
.. literalinclude:: ../../pyranha/tutorial/rational.py
:language: python
:linenos:
Although Python does not provide a rational type as a builtin, a rational class named ``Fraction`` is available in the standard ``fractions``
module since Python 2.6. A few simple examples of usage of the ``Fraction`` class are shown in the :ref:`Python code `.
``Fraction`` instances are automatically converted to/from ``rational`` by Pyranha as needed. For instance, on the last line of the
:ref:`Python code ` we see another usage of the :py:func:`pyranha.math.binomial` function. This time the arguments, of type
``Fraction`` and ``int``, are automatically converted to ``rational`` and ``integer`` before being passed to the ``math::binomial()`` C++ function.
The ``rational`` result of the C++ function is then converted back to ``Fraction`` and returned.
The ``real`` type
-----------------
The third basic numerical type provided by Piranha is called ``real``, and it represents arbitrary-precision floating-point numbers. It consists
of a thin wrapper around the ``mpfr_t`` type from the `GNU MPFR `__ library. ``real`` is essentially a floating-point type
whose number of significant digits can be selected at runtime.
The ``real`` type obeys the following basic rules:
* a default-constructed ``real`` object is initialised to zero;
* the precision (i.e., the number of significant digits) is measured in bits, and it can be set at different values for different instances of
``real``. The default value is 113 bits (similar to IEEE 754 quadruple-precision);
* a ``real`` object can be converted to/from all the basic C++ numerical types, ``integer`` and ``rational`` (the conversion to an integral
type truncates the original value);
* in mixed-mode operations, the rank of ``real`` is higher than that of ``integer``, ``rational`` and any numeric C++ type;
* operations involving multiple ``real`` instances with different precisions will produce a result with the highest precision among the operands.
The following C++ code showcases a few features of the ``real`` class:
.. literalinclude:: ../../tutorials/real_.cpp
:language: c++
:linenos:
In the first block, a few ways of constructing ``real`` objects are shown: from C++ arithmetic types, from string, from ``integer`` and ``rational``.
The two-arguments constructors allow construction with custom precision. It is important to note that construction from a C++ floating-point
type is different from string construction. That is,
.. code-block:: c++
real{1.23};
is in general different from
.. code-block:: c++
real{"1.23"};
In the first case, the ``1.23`` literal is first converted to a ``double`` by the compiler, and the ``double`` is then used to initialise the ``real`` object.
In the second case, the ``real`` object is initialised directly with the string ``"1.23"``. On a typical desktop machine, in the first case printing to screen
the constructed ``real`` object will produce
.. code-block:: console
1.22999999999999998223643160599749535
In the second case the output will be:
.. code-block:: console
1.22999999999999999999999999999999998
In the second code block, a few examples of arithmetic operations involving ``real`` are shown. Operations involving multiple ``real`` objects with different
precision will produce a result with the highest precision among the operands.
In the third code block, a few usages of the explicit conversion operator are shown.
In the fourth code block, the ``_r`` string literal is introduced. Similarly to ``_z`` and ``_q``, it can be used to construct a ``real`` object with default
precision from a floating-point literal. Construction via ``_r`` is equivalent to construction from string.
Finally, in the last block, we can see another invocation of the ``math::binomial()`` function. This time the top argument is a ``real``,
wheras the bottom argument is an ``int``. The specialisation of the binomial function for these two types will yield the approximate
result as a ``real``, computed with the default precision of 113 bits.
On the Python side, C++ ``real`` objects are automatically converted to/from ``mpf`` objects from the `mpmath `__ Python library.
.. note:: The mpmath module needs not to be installed when compiling Pyranha. Its presence is detected by Pyranha at runtime.
Whereas in the case of integers and rationals the conversion to/from Python is straightforward and unambiguous, in the case of ``real`` things are slightly
more complicated. In mpmath, there is a global precision setting which is user-configurable (either via ``mpmath.mp.prec`` or ``mpmath.mp.dps``) and
one works without specifying different precisions for different ``mpf`` objects.
The rules of conversion between ``real`` and ``mpf`` are the following:
* when a ``real`` object is returned to Python by some C++ function exposed in Pyranha, it will be converted to an ``mpf`` object with the current
``mpmath`` global precision;
* when an ``mpf`` object is passed from Python into a C++ function exposed in Pyranha accepting a ``real`` argument, the ``mpf`` will be converted
to a ``real`` which will have the same precision as the global ``mpmath`` precision.
Potential pitfalls
------------------
TLDR
----