Yet Another Rational Number Library in C++

Bill Seymour
2013-05-08


Copyright Bill Seymour 2009, 2010, 2011, 2012, 2013.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

Contents:


Introduction:

This open-source library provides a class that represents a rational number, and several supporting functions, for use in applications where exact arithmetic must be done on non-integer values.

In addition to the usual arithmetic and comparison operations, the library provides:

Note that there is no implicit conversion from any integer type even though, mathematically, an integer is a rational number. Instead, a complete set of arithmetic and comparison operators taking both rational and integer arguments is provided. This is preferable to first converting the integers to rationals because the integer’s denominator is known to be 1 and so the functions can be written more efficiently.

Similar mixing of rationals and floating-point values in expressions is deliberately not allowed because the whole point of the rational class is to allow doing arithmetic exactly, but floating-point arithmetic is inexact in most cases. Explicit conversion from double is provided, however, because users who aren’t experts in numerics might not know how to write it. (Indeed, Yours Truly had no idea how to do it until Fred Tydeman directed him to the relevant section in [Knuth].) There’s also an explicit mechanism for calling functions that take double arguments just in case that’s needed.

Class invariants:  all operations eagerly normalize the fraction such that the denominator will be greater than zero and the fraction will be reduced to its lowest terms. For example, if some operation could yield 4/−6, the actual result would be −2/3. If the numerator is 0, the denominator is 1.

The rational class and all associated non-member functions and templates are defined in the rational_math namespace; and the library reserves all identifiers beginning with “RATIONAL_MATH_” for use as macros.

This open-source library is distributed under the Boost Software License (which isn’t viral like the GPL and others are believed to be). The distribution includes the files:


Synopsis:

#define RATIONAL_MATH_HPP_INCLUDED

#include <iosfwd>
#include <climits>
#include <cstdint> // only when using C++11 implementations

namespace rational_math {

typedef long, long long, or std::intmax_t int_type;

/* if int_type is std::intmax_t  */ #define RATIONAL_MATH_USES_INTMAX_T
/* else if int_type is long long */ #define RATIONAL_MATH_USES_LONG_LONG
/* else (int_type is long)       */ #define RATIONAL_MATH_USES_LONG

class rational
{
public:
    rational();
    explicit rational( /* overloads for built-in integers */ );
    rational(int_type, int_type);

  //
  // Compiler-supplied copy constructor, copy-assignment operator, trivial destructor
  //

    rational& operator=(int_type);
    rational& assign(int_type, int_type);

    int_type numer() const;
    int_type denom() const;

    rational& negate();
    rational& invert();

  #if __cplusplus >= 201103L
    explicit operator bool() const noexcept;
  #else
    operator safe_bool() const;
    bool operator!() const;
  #endif

    rational& operator++();
    rational& operator--();

    rational  operator++(int);
    rational  operator--(int);

    rational& operator+=(int_type);
    rational& operator-=(int_type);
    rational& operator*=(int_type);
    rational& operator/=(int_type);

    rational& operator+=(const rational&);
    rational& operator-=(const rational&);
    rational& operator*=(const rational&);
    rational& operator/=(const rational&);
};

rational operator+(const rational&);
rational operator-(const rational&);

rational operator+(const rational&, const rational&);
rational operator-(const rational&, const rational&);
rational operator*(const rational&, const rational&);
rational operator/(const rational&, const rational&);

bool operator==(const rational&, const rational&);
bool operator!=(const rational&, const rational&);
bool operator< (const rational&, const rational&);
bool operator> (const rational&, const rational&);
bool operator<=(const rational&, const rational&);
bool operator>=(const rational&, const rational&);


//
// Not shown:  arithmetic and comparison operators with
// int_type on either side and const rational& on the other.
//
rational abs(const rational&); rational reciprocal(const rational&); int_type floor(const rational&); int_type ceil (const rational&); int_type trunc(const rational&); enum rounding_mode { nearest_even, nearest_odd, toward_pos_inf, toward_neg_inf, toward_zero, away_from_zero }; int_type nearest(const rational&, rounding_mode = nearest_even); double to_double(const rational&); rational to_rational(double, double = 1e-6); rational ipow(const rational&, int); rational fmod(const rational&, const rational&); rational remainder(const rational&, const rational&, rounding_mode = nearest_even); template<class T> rational modf(const rational&, T*); rational modf(const rational&); template<class T> rational remquo(const rational&, const rational&, T*, rounding_mode = nearest_even); rational approx(const rational&, double (*)(double), double = 1e-6); rational approx(const rational&, const rational&, double (*)(double, double), double = 1e-6); template<class Ch, class Tr> std::basic_ostream<Ch,Tr>& showden1(std::basic_ostream<Ch,Tr>&); template<class Ch, class Tr> std::basic_ostream<Ch,Tr>& noshowden1(std::basic_ostream<Ch,Tr>&); template<class Ch, class Tr> std::basic_ostream<Ch,Tr>& divalign(std::basic_ostream<Ch,Tr>&); template<class Ch, class Tr> std::basic_ostream<Ch,Tr>& nodivalign(std::basic_ostream<Ch,Tr>&); template<class Ch> implementation-detail setdiv(Ch); #ifndef RATIONAL_MATH_EXPORT #define RATIONAL_MATH_EXPORT // empty definition by default #endif RATIONAL_MATH_EXPORT template<class Ch, class Tr> std::basic_istream<Ch,Tr>& operator>>(std::basic_istream<Ch,Tr>&, rational&); RATIONAL_MATH_EXPORT template<class Ch, class Tr> std::basic_ostream<Ch,Tr>& operator<<(std::basic_ostream<Ch,Tr>&, const rational&); } // namespace rational_math

Detailed explanations:


The internal integer type:

The simplest explanation is probably to show the actual code:
  #include <climits>
  #if __cplusplus >= 201103L || defined(RATIONAL_MATH_HAS_CSTDINT)
    #include <cstdint>
  #endif

  namespace rational_math
  {
    #if defined(INTMAX_MAX) && !defined(RATIONAL_MATH_JUST_USE_LONG)
      #define RATIONAL_MATH_USES_INTMAX_T
      typedef std::intmax_t int_type;
    #elif defined(LLONG_MAX) && !defined(RATIONAL_MATH_JUST_USE_LONG)
      #define RATIONAL_MATH_USES_LONG_LONG
      typedef long long int_type;
    #else
      #define RATIONAL_MATH_USES_LONG
      typedef long int_type;
    #endif
  }
By default, the rational class uses for its numerator and denominator the largest integer type that the library can easily prove is available. If you’re using a conforming C++11 implementation, that’s intmax_t; otherwise, it’s either long or long long depending on whether LLONG_MAX is defined in <climits>. If your implementation doesn’t claim full C++11 conformance but it does have conforming <cstdint> and <cinttypes> headers (the latter is needed in rational_math.cpp); you can #define RATIONAL_MATH_HAS_CSTDINT to allow using intmax_t.

If you switch between C++ implementations, make sure you recompile all your code to avoid violations of the one-definition rule (ODR). If the ODR is an issue because you’re linking to code that you can’t recompile, you can #define RATIONAL_MATH_JUST_USE_LONG to force using long.

The RATIONAL_MATH_USES_foo macros can be used as compile-time feature tests. Note that these macros are mutually exclusive; so, for example, using a C++11 implementation, even if intmax_t is just a typedef for long long, only RATIONAL_MATH_USES_INTMAX_T will be defined.


Member functions:


Constructors:


    rational();
The default contstructor constructs a rational with a numerator equal to 0 and a denominator equal to 1.


    explicit rational( /* overloads for various integer types */ value);

The one-argument constructors construct a rational with a numerator equal to value and a denominator equal to 1. Because they’re declared explicit to avoid unexpected type conversions, there are overloads for each of the built-in (“fundamental” in C++11) integer types.


    rational(int_type numerator, int_type denominator);
The two-argument constructor constructs a rational from numerator and denominator and then enforces the class invariants.


Destruction, copy construction, and assignment:

The class uses the compiler-supplied destructor, copy constructor, and copy-assignment operator. The internal representation is just a couple of int_types, so the destructor is trivial; and the author has no plan to add move semantics.


    rational& operator=(int_type value);
Assignment from int_types sets the numerator to value and the denominator to 1.


    rational& assign(int_type numerator, int_type denominator);
The assign member function will assign the specified numerator and denominator and then enforce the class invariants.


Access to internal representation:

    int_type numer() const;
    int_type denom() const;
“Getters” for the numerator and denominator are provided primarily as helpers for non-member functions and operators; but calling them directly in user code can do no harm.

“Setters” are deliberately not provided because, given functions that set the numerator and denominator independently, we couldn’t guarantee the class invariants. (We could write setter-like functions that do enforce the invariants; but, in general, they wouldn’t do what the user probably wants.)


Additive and multiplicative inverses:


    rational& negate();
This function changes the sign of the numerator.


    rational& invert();
This function swaps the numerator and denominator, possibly changing their signs to keep the denominator positive.


Implicit conversion to bool:

If you have a C++11 compiler, you get:
    explicit operator bool() const noexcept;
otherwise …

    operator safe_bool() const;
    bool operator!() const;
These operators are provided to allow using the “if (my_rat)” and “if (!my_rat)” idioms to check for equality to zero.

The first function actually returns a (possibly null) pointer to a member of a private class (after [V&J], §20.2.8). This, in turn, has an implicit conversion to bool; but nothing else can be done with it.

The separate operator!() isn’t really needed; but it’s provided because it might be a little more efficient on some C++ implementations, and there doesn’t seem to be any compelling reason not to provide it.


Increment and decrement operators:

    rational& operator++();
    rational& operator--();

    rational operator++(int);
    rational operator--(int);
The usual prefix and postfix increment and decrement operators are provided. As expected, they add or subtract unity; they don’t just increment or decrement the numerator.


The op= operators:

    rational& operator+=(int_type);
    rational& operator-=(int_type);
    rational& operator*=(int_type);
    rational& operator/=(int_type);

    rational& operator+=(const rational&);
    rational& operator-=(const rational&);
    rational& operator*=(const rational&);
    rational& operator/=(const rational&);
As expected, rationals can be added, subtracted, multiplied and divided by int_types or other rationals.

There’s no %= operator for the same reason that there isn’t one for floating-point types. Instead, following the lead of C99’s standard library, there are two functions that return remainders; and which one you call depends on what you mean by “remainder”.


Non-member functions:


Unary operators:

    rational operator+(const rational& value);
    rational operator-(const rational& value);
The + operator just returns a copy of value; the − operator returns a negated copy of value.


Binary operators:

    rational operator+(const rational&, const rational&);
    rational operator-(const rational&, const rational&);
    rational operator*(const rational&, const rational&);
    rational operator/(const rational&, const rational&);

    bool operator==(const rational&, const rational&);
    bool operator!=(const rational&, const rational&);
    bool operator< (const rational&, const rational&);
    bool operator> (const rational&, const rational&);
    bool operator<=(const rational&, const rational&);
    bool operator>=(const rational&, const rational&);
The usual arithmetic and comparison operators are provided.

Although not shown, a complete set of arithmetic and comparison operators taking int_type on either side and const rational& on the other is also provided.


Conversion to integers:


    int_type floor(const rational&);
    int_type ceil (const rational&);
These functions have the same semantics as std::floor(double) and std::ceil(double). They return, respectively, the greatest integer not greater than the value, and the least integer not less than the value. In other words, floor rounds toward negative infinity and ceil rounds toward positive infinity.


    int_type trunc(const rational&);
This function simply truncates any fractional part; that is, it rounds toward zero.


    enum rounding_mode
    {
        nearest_even, nearest_odd,
        toward_pos_inf, toward_neg_inf,
        toward_zero, away_from_zero
    };
    int_type nearest(const rational&, rounding_mode = nearest_even);
This function rounds to the nearest integer. The optional second argument specifies the behavior in the special case where the denominator is equal to 2, and so neither integer above or below the value is nearer than the other. By default, the function rounds to the nearest even integer.

All six possible rounding modes probably aren’t needed, but there doesn’t seem to be any compelling reason not to provide them.


Conversion to and from double:


    double to_double(const rational& value);
This function returns static_cast<double>(value.numer()) / value.denom().


    rational to_rational(double value, double accuracy = 1e-6);
This function returns a rational that’s approximately equal to value. The optional second argument specifies the accuracy required: if ±10% of value is good enough, pass 0.1; if ±1% of value is needed, pass 0.01, and so on. By default, you get  ±(value × 10−6), which gets you, for example, 355/113 for π.

Specifying extremely accurate conversions (for example, if value * accuracy rounds to zero) can lead to overflow of the calculated numerator or denominator on implementations where the number of significand bits in a double is not less than the number of value bits in an int_type (probably so if int_type is long, but probably not if int_type is long long). There are other ways the conversion can overflow as well; for example, 1020 will certainly fit in a double, but probably won’t fit in an int_type.

In a debug build, the function will assert on overflow, or if the numerator would be set to the most negative two’s-complement value. In a production build, overflow will just get you a wrong answer at worst.


Miscellaneous functions:


    rational abs(const rational&);
This function returns the absolute value.


    rational reciprocal(const rational& value);
This function returns the reciprocal of value.


    rational ipow(const rational& value, int exponent);
This function returns valueexponent00 merrily yields 1.

Unlike for std::pow(double,double), the exponent must be an integer since raising to a non-integer power gives an irrational answer in general. Note the name change to “ipow” to draw attention to the integer-exponent requirement.


    rational fmod(const rational& dividend, const rational& divisor);

    rational remainder(const rational& dividend, const rational& divisor,
                       rounding_mode mode = nearest_even);
These functions return the remainder of dividend/divisor.

fmod is analagous to std::fmod(double,double). It truncates the quotient toward zero, multiplies that result by divisor, then subtracts that product from dividend.

remainder is analagous to std::remainder(double,double) which returns the IEEE-754 (a.k.a., IEC 60559) remainder. It differs from fmod in that the quotient is first rounded to the nearest integer rather than being truncated toward zero. The optional third argument has the same meaning and default value as the second argument to the nearest function.


    template<class T>
    rational modf(const rational& value, T* int_part);

    rational modf(const rational& value);

    template<class T>
    rational remquo(const rational& dividend, const rational& divisor, T* int_quo,
                    rounding_mode mode = nearest_even);
modf is analogous to std::modf(double,double*). It returns the fractional part of value; and if int_part is not a null pointer, *int_part is set to the integer part of value. There’s also a non-template one-argument version for use when you want just the fractional part and don’t want to bother explicitly passing a null pointer.

remquo is analogous to std::remquo(double,double,int*). It returns remainder(dividend,divisor,mode); and if int_quo is not a null pointer, *int_quo is set to nearest(dividend/divisor,mode). Unlike with std::remquo, *int_quo will be exact.

For both templates, the intent is that T be either int_type or rational. In fact, T can be anything that can have an int_type assigned to it; but the result might not be meaningful if T is smaller than int_type.


    rational approx(const rational& value, double (*func)(double), double accuracy = 1e-6);
    rational approx(const rational&, const rational&, double (*)(double, double), double = 1e-6);
These functions are provided to make somewhat less painful those times when you really need to escape into the irrational domain for a particular calculation, but you can live with a rational answer that’s close enough. For example:
    rational val(5, 7);
    rational root = approx(val, std::sqrt); // square root ±10-6
or maybe:
    rational val(5, 7);
    rational exp(1, 3);
    rational root = approx(val, exp, std::pow, 0.001); // cube root ±0.1%
They’re written with the <cmath> functions in mind; but they’ll work with any function that takes one or two double arguments and returns a double.

The optional final argument has the same meaning and default value as to_rational(double,double)’s second argument.


I/O operations:

In the following descriptions, to “follow immediately” means to follow without any intervening characters including whitespace.

'/'” means whatever character, narrow or wide, is currently being used for the division sign.


    template<class Ch, class Tr>
    std::basic_istream<Ch,Tr>&
      operator>>(std::basic_istream<Ch,Tr>&, rational&);
The extraction operator reads an integer value which it takes to be a numerator, and if that is immediately followed by a '/', reads another integer value which it takes to be a denominator. If no '/' immediately follows the numerator, the denominator is assumed to be 1.

If the operator reads a value of zero when expecting a denominator, it will set the stream’s failbit which could throw an ios_base::failure if the user has set up the stream to do so. If the exception is thrown, or if the stream is not good() when the operator returns, the operator might have removed a number of characters from the stream; but the rational on the right-hand side will be untouched.

If the operator is successful, it will have enforced the class invariants.

The stream’s locale and all its format flags except skipws apply separately to the numerator and the denominator. skipws applies to the numerator only; so if a denominator is present, the '/' must immediately follow the numerator, and the denominator must immediately follow the '/'.

The library provides instantiations for all the standard character types. If that’s either too much or too little, see Appendix A.


    template<class Ch, class Tr>
    std::basic_ostream<Ch,Tr>&
      operator<<(std::basic_ostream<Ch,Tr>&, const rational&);
The insertion operator writes a rational to the specified output stream; and the numerator and denominator will be written in whatever format is appropriate given the stream’s flags and locale. The showpos and showbase flags affect the numerator only.

The output will be an optional sign or base, followed by the numerator, followed (usually immediately) by a '/', followed immediately by the denominator. If the denominator is 1, output of  /1 can be suppressed with the noshowden1 manipulator described below.

The library provides instantiations for all the standard character types. If that’s either too much or too little, see Appendix A.


Two new pairs of no-argument output manipulators provide additional formatting options when writing rationals.

showden1 and noshowden1 control whether a denominator equal to 1 is written. If noshowden1 is in effect and the denominator is 1, neither the '/' nor the '1' will be written; so the output will be just the numerator written as an ordinary int_type.

divalign and nodivalign control whether the stream’s width and adjustfield flags affect the numerator only (divalign) or the whole fraction (nodivalign). The former is intended for printing fractions in columns by lining up the '/' characters. (The division signs get aligned, thus “divalign”.)

The defaults are noshowden1 and nodivalign.

For example:

    rational r1(5);
    rational r2(24, 17);
    cout << r1 << '\n'
         << r2 << '\n'
         << showden1
         << setfill('*')
         << setw(6) << r1 << '\n'
         << setw(6) << r2 << '\n'
         << divalign
         << setw(6) << r1 << '\n'
         << setw(6) << r2 << '\n'
         << left << setfill(' ') << setw(6) << r2
         << " (You might want to avoid left with divalign.)\n";
yields the output:
    5
    24/17
    ***5/1
    *24/17
    *****5/1
    ****24/17
    24    /17 (You might want to avoid left with divalign.)
Note that the compiler can’t find these manipulators by argument-dependent lookup (ADL); so you’ll need to either explicitly qualify their use with the rational_math namespace or have a using declaration for them (or, perhaps, a using directive for the whole rational_math namespace).

These manipulators call ios_base::iword() and have no additional effect if that function fails and sets badbit.


    template<class Ch>
    implementation-detail setdiv(Ch);
This one-argument I/O manipulator sets the character to be used for the division sign. For example (assuming that chars hold Latin-1 code points):
    cout << setdiv('\xF7') << rational(1, 3);
yields the output:
    1÷3
As expected, the default division sign is stream.widen('/').

Note that the compiler can’t find this template by argument-dependent lookup (ADL); so you’ll need to either explicitly qualify calls to it with the rational_math namespace or have a using declaration for it (or, perhaps, a using directive for the whole rational_math namespace).

This manipulator calls ios_base::iword() and has no additional effect if that function fails and sets badbit.

Possible future direction:  support for strings as well as single characters.


Caveats


Overflow considerations:

The library tries mightily to avoid overflow; but in cases when the various numerators and denominators are all relatively prime, there’s really nothing that can be done; and numerators and denominators can get big in a hurry when doing rational arithmetic because almost every operation requires multiplication behind the scenes. Users who find this library too subject to signed integer overflow might prefer Paul Moore’s boost::rational<> which allows specifying the internal integer type as a template argument, and so allows instantiating rational objects with a user-defined bignum.

The library does not support setting the numerator to the most negative two’s-complement value. That’s not technically overflow yet; but you’d get overflow shortly if you tried to use such an object for anything interesting except output; so the author has no plan to change that.


Negative zero:

Nothing in the library will create a negative zero on its own; but it does nothing special to guard against negative zero arguments passed by the user. If your C++ implementation’s int_types have one’s-complement or sign-magnitude representations, you might want to be careful that you don’t pass a negative zero as any function’s int_type argument.


Assertions and exceptions:

In general, overflow and division by zero are treated as contract violations:  they can cause assertions in debug builds, but in production builds, they just leave the user hanging out to dry. There are no assertions in the library’s header, only in rational_math.cpp, so the presence or absence of the NDEBUG macro won’t cause a violation of the one-definition rule (ODR).


Thread safety:

The library provides no synchronization of its own; but except for I/O, you have exactly the same thread-safety issues you’d have when using non-atomic built-in types.

The library does do some non-thread-safe initialization in support of its I/O manipulators; but the standard iostreams aren’t required to be thread-safe anyway, so that’s not really an issue.


Miscellaneous internal documentation and rationale


The internal integer type:

We might specify the internal integer type as a template argument, which is indeed just what boost::rational<> does. This would allow instantiating a rational with some user-defined bignum to lessen the probability of overflow…certainly a worthy goal.

Unfortunately, our desire for a separate .cpp file makes this problematic since we’d need to define the whole library, and #include <the world>, in the header file. We’d also need to add conditional support for move semantics (for implementations that have rvalue references) since copying and destructing instantiated objects might be expensive.

The author is thinking about this, but not favorably at present.


Normalization:

The op= operators use algorithms in [Knuth], §4.5.1, which necessarily leave the fraction in lowest terms. The conversion from double to rational, described shortly, also uses a mechanism that results in a fraction in lowest terms. That leaves only the private init() function, and maybe fixing the signs in invert(), where we need to do anything special to enforce the class invariants.

The gcd() function in rational_math.cpp computes the greatest common divisor of two integers using Euclid’s algorithm ([Knuth], §4.5.2), which we modern folk can reorganize a bit because we understand that zero is a number. 8-)


Non-member arithmetic operators and similar functions:

Most functions that return rationals by value (including the postfix increment and decrement operators which are defined in-class) are written to allow the compiler to apply the named return value optimization (NRVO). Users of compilers that can’t do that are out of luck; but copying a rational is as fast as copying a couple of int_types, and rational’s destructor is trivial.

Three exceptions, the unary – operator, abs(const rational&), and the conversion from double, all return the result of a constructor call and so allow the anonymous RVO.

Note that these functions return non-const rational. The author is aware of the argument (e.g., Sutter, GotW #6, answer 4) that letting temporaries be modified is sometimes asking for trouble; but he finds more persuasive the argument that returning const values could interfere with template instantiation. (An additional argument which applies to C++11 is that const could cause copying when moving would do; but that doesn’t actually matter for the rational class unless we decide to make rational a template.)


Conversion from double:

The loop in to_rational(double,double) performs successive approximation of continued fractions ([Knuth], §4.5.3; Rusin).

There are two conditions that make us think that we’re headed for overflow:

In the first case above, overflow would leave us in an endless loop, which is why we prefer to predict overflow rather than just let it happen. This, in turn, is why we do unsigned arithmetic internally. (Unsigned integer arithmetic is guaranteed to just modwrap, but signed integer overflow makes demons fly out of your nose.)


Raising to an integer power:

ipow(const rational&, int) uses Algorithm A in [Knuth], §4.6.3.

Note that, since the value is known to be in lowest terms to begin with, any integer power of it necessarily is, too; so we can do raw multiplications of the numerator and denominator with confidence that we’re not violating the class invariants.


The ostream insertion operator:

If the user selects nodivalign (or lets it default to that), we first write the fraction to a std::basic_ostringstream<> set up to have the same locale and format as the desired output stream, but with a width of zero (after [Josuttis], §13.12.1) and the showpos and showbase flags cleared; so the only tasks that remain are getting the sign, fill and base characters right.


The I/O manipulators:

We just store the necessary information in the stream’s ios_base::iword array of longs. That, by itself, is easy enough, except that iword() can fail and set the stream’s badbit, which we ought to check since we don’t want to actually do anything if that happens. Unfortunately, we can’t get the stream’s state from ios_base, but rather need to use basic_ios<>; and now we’re in the template world and so would need to either include <iostream> in the header (which we’d rather not do) or create explicit instantiations for a whole lot more stuff in rational_math.cpp.

But as usual, an additional level of indirection can solve our problem:  in namespace rational_math::detail, we have a non-template abstract base class called ioswrap and derive an iosleaf<> template from it that we pass to the non-template functions in rational_math.cpp as an ioswrap&. The pure-virtual iword(int) and bad() functions eventually call the corresponding functions in the actual stream.

We’d like to use just one long from the stream’s array; but the division sign needs a long of its own since we need to support char32_ts and a long probably just has 32 bits of its own; so we make two calls to ios_base::xalloc(), one to get the index for the two bits we need for the no-argument output manipulators, and one to get the index for storing the division sign.

The setdiv(Ch) manipulator is a function template that returns an instance of the divsetter class which is defined in namespace rational_math::detail. As expected, << and >> operators for that class are also defined in that namespace so that the compiler can find them by argument-dependent lookup (ADL).

The character is retrieved by the divsign function template in rational_math.cpp. If the retrieved value is zero, which it will be before it gets explicitly set to anything else, divsign<>() defaults it to stream.widen('/').

Support for strings as well as single characters, principally to allow for multi-code-unit characters, is comming Real Soon Now. The author has a design in mind, but it’s not clear that allowing strings as division signs is really worth doing since that would provide another way for the >> operator to fail:  if the code unit following the numerator matches the first code unit of the division sign, but a subsequent code unit doesn’t, we can’t recover because we can’t call putback() more than once without an intervening read, and so our only choice would be to set ios_base::failbit. It’s not clear that that’s a Good Thing for the user given the small additional functionality.


Rationale for having a separate .cpp file:


Printed References:

[Josuttis]   Nicolai M. Josuttis, The C++ Standard Library, A Tutorial and Reference, ISBN 0-201-37926-0
[Knuth]   Donald E. Knuth, The Art of Computer Programming, Volume 2, Seminumerical Algorithms, Third Edition, ISBN 0-201-89684-2
[V&J]   David Vandevoorde and Nicolai M. Josuttis, C++ Templates - The Complete Guide, ISBN 0-201-73484-2


Appendix A: Extending the I/O operators

The library puts the definitions of the << and >> operator templates in a separate translation unit. This lets us #include just <iosfwd> in the header, which can significantly reduce build times for large projects.

The library assumes that your C++ implementation doesn’t support the export keyword, or you just don’t want to use it (as of this writing, that’s a good guess); and in rational_math.cpp, it explicitly instantiates the I/O operators for char and wchar_t, and if you have a C++11 implementation or you define the RATIONAL_MATH_HAS_CHAR1632_IO macro, char16_t and char32_t (and, as expected, std::char_traits<> in all cases).

Except for RATIONAL_MATH_EXPORT, all the macros described in this appendix affect rational_math.cpp only and so won’t result in ODR violations.


All suggestions and corrections will be welcome; all flames will be amusing.
Mail to was at pobox dot com.