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:
#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. // |
#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
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.
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.
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.
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.)
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.
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
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.
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”.
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.
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.
int_type floor(const rational&); int_type ceil (const rational&);These functions have the same semantics as
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.
double to_double(const rational& value);This function
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
Specifying extremely accurate conversions
(for example, ifIn a debug build, the function will assert on overflow, or if the
numerator would be set to the most negative
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 valueexponent. 00 merrily yields 1.
Unlike for
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.
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
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-6or 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
“'/'” 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
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
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
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
cout << setdiv('\xF7') << rational(1, 3);yields the output:
1÷3As expected, the default division sign is
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
Possible future direction: support for strings as well as single characters.
The library does not support setting the numerator to the most
negative
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.
Unfortunately, our desire for a separate .cpp
file makes this problematic since we’d need to define the whole
library, and
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
The
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,
Note that these functions return
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:
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
But as usual, an additional level of indirection can solve our problem: in
namespace
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
The setdiv(Ch) manipulator is a function template that returns
an instance of the divsetter class which is defined in namespace
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,
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
Rationale for having a separate .cpp file:
[Josuttis] |
Nicolai M. Josuttis,
The C++ Standard Library,
A Tutorial and Reference, |
|
[Knuth] |
Donald E. Knuth,
The Art of Computer Programming,
Volume 2, Seminumerical Algorithms, Third Edition, |
|
[V&J] |
David Vandevoorde and Nicolai M. Josuttis,
C++ Templates - The Complete Guide,
|
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).
#ifndef RATIONAL_MATH_EXPORT #define RATIONAL_MATH_EXPORT #endifJust change that to
[Late-breaking news: C++’s export keyword seemed like a good idea when C++98 was being written, but that didn’t prove to be the case in the wild, and so its functionality was removed from C++11. (The keyword remains for backward compatibility, but implementors aren’t required to make it do anything.)]
// // my_rational_math.cpp // #include "rational_math.cpp" // You don’t need to rewrite it. namespace rational_math { template std::basic_istream<ucs2_t>& operator>>(std::basic_istream<ucs2_t>&, rational&); template std::basic_ostream<ucs2_t>& operator<<(std::basic_ostream<ucs2_t>&, const rational&); }You could also just add your explicit instantiation directives at the end of rational_math.cpp itself; but if the rational class is shared with other programs that won’t use the additional instantiations, that could cause unnecessary code bloat in those other programs.
It’s also possible to define RATIONAL_MATH_NO_NARROW_STREAM_IO with the obvious parallel effect just in case somebody actually wants to do that.
You can also define one or both of RATIONAL_MATH_NO_CHAR16_IO and RATIONAL_MATH_NO_CHAR32_IO if you have a C++11 implementation but don’t want instantiations for either or both of the charN_t types.