Rational tutorial

rational is a class that builds upon integer to provide an implementation of rational numbers represented as a numerator-denominator integer pairs.

Like integer, rational is parametrised over an integral value SSize which represents the number of limbs that will be stored directly within both the numerator and the denominator before resorting to dynamic memory allocation (see the integer tutorial for a detailed explanation of the meaning of the SSize parameter).

The rational class adopts the usual canonical representation of rational numbers:

  • numerator and denominator are coprime,

  • the denominator is always strictly positive.

This representation is usually enforced automatically by the rational API (however, a couple of low-level functions are available to access and mutate directly the numerator and the denominator in performance-critical scenarios).

In addition to the features common to all mp++ classes, the rational API provides a few additional capabilities:

  • it is possible to construct a rational from an integral pair of arguments representing the numerator and the denominator:

    rat_t q1{3, 4};                // Construction from int numerator and denominator.
    
    rat_t q2{long(-6), short(-8)}; // Construction from mixed numerator and denominator types.
    
    assert(q1 == q2);              // Construction canonicalises the rational.
    
  • it is possible to get const and mutable references to the integer objects representing the numerator and the denominator via the mppp::rational::get_num(), mppp::rational::get_den(), mppp::rational::_get_num() and mppp::rational::_get_den() member functions. These accessors allow to use any function from the integer API on the numerator and on the denominator:

    rat_t q1{2, 5};
    assert(q1.get_num() == 2); // Const access to numerator and denominator.
    assert(q1.get_den() == 5);
    
    q1._get_num() = 1;         // Mutable access to numerator and denominator.
    q1._get_den() = 3;
    assert(q1 == rat_t{1, 3});
    
  • the mppp::rational::canonicalise() member function (and its free function counterpart) can be used to canonicalise a rational. This is normally not necessary, unless the numerator or the denominator have been changed manually via the mutable getters:

    rat_t q1{1, 6};
    q1._get_num() = 9;         // Change the numerator using the mutable getter.
                               // WARNING: q1 = 9/6, not in canonical form any more!
    
    q1.canonicalise();         // Canonicalise q1.
    assert(q1 == rat_t{3, 2});
    
  • a few additional arithmetic and comparison functions are available;

  • a few exponentiation and number-theoretic functions are available;

  • like integer, rational also provides a specialisation of std::hash for use in the standard unordered containers.

Many of these features, which are documented in detail in the rational reference, are available in multiple overloads, often both as free and member functions.

Interacting with the GMP API

Like integer, rational provides facilities for interfacing with the GMP library. Currently, the interoperability is limited to rational objects being able to be constructed and assigned from both mpz_t and mpq_t objects:

mpz_t m;
mpz_init_set_si(m, -4);      // Init an mpz_t with the value -4.

rat_t q1{m};                 // Init a rat_t from the mpz_t.
assert(q1 == -4);            // Verify that the value is correct.

mpq_t q;
mpq_init(q);
mpq_set_si(q, 3, 4);         // Init an mpq_t with the value 3/4.

rat_t q2{q};                 // Init a rat_t from the mpq_t.
assert((q2 == rat_t{3, 4})); // Verify that the value is correct.

rat_t q3;
q3 = m;                      // Assign the mpz_t to another rat_t.
assert(q3 == -4);            // Verify that the value is correct.

rat_t q4;
q4 = q;                      // Assign the mpq_t to another rat_t.
assert((q4 == rat_t{3, 4})); // Verify that the value is correct.

mpz_clear(m);                // Clear the mpz_t.
mpq_clear(q);                // Clear the mpq_t.