Boost logo

Boost :

From: Paul Moore (gustav_at_[hidden])
Date: 2001-02-07 15:27:04

On 6 Feb 2001, at 18:40, Daryle Walker wrote:

> The documentation should have a Contents section, since it's so long.

Good point. I'll add one. A diff for rational.html is attached.

> In the "Swap operation" section, you mention a (full) specialization of the
> "std::swap" function. Isn't specialization guaranteed just for class
> templates in the "std" namespace, with function templates in "std" still
> kind-of ambiguous? (When this came up before, I think some people here
> decided to screw it and define the specialization anyway, no matter its
> technical legality.)

I'm not 100% sure I understand what you're saying here, but I *am*
reasonably sure that what I said is right. The standard allows
additional specialisations to be added to the std namespace. (If
what you're saying is that for *functions* specialisations aren't
allowed, but only for *classes*, then I think you're wrong, but I can
no longer find who said that, so please shout if I am wrong...) The
problem arises with functions where there is an issue over what is
a specialisation and what is an overload (overloads *aren't* allowed).

What I said is my best attempt at wording this mess. If you (or
anyone else) can say it more clearly, that's great - please let me
know. But when it boils down to it, the section is really just saying
"There's a possible optimisation using std::swap(), but there are
issues why I'm not using it. This is an explanation of the issues."
so it's not critical...

> I have something like "boost::detail::resetter" in the in-progress
> "" library (in the vault). I guess you can update when that
> sub-library is finalized.

I thought that looked like a candidate for reuse :-) I'll do as you

> Shouldn't the I/O operators be templated on character and traits types?

I recall worrying about this in a different context, and I thing for
rational as well. The poblem is that I need a literal '/'. How do I spell
that for a general character type? (Hypothetical question)

There's a big issue here, in terms of writing generic code for I/O. It
came up slightly with IntType, where I needed zero and one values,
and spawned a thread on Algebraic concepts (which I avoided
getting sucked into...)

I have a nasty feeling that the "correct" solution to this sort of issue
is locales and facets - encapsulate the '/' as a facet (rational
separator) of a locale, much as the decimal separator, date
separator, etc are currently handled. That is *far* too much work
(and locales are not well documented anywhere I have found) for
me to even contemplate.

But it does reiterate the point that doing this sort of thing properly
is not trivial...

> You no longer allow a rational to be just a numerator (i.e. if there's no
> following '/' then use the first read number as the numerator with a
> denominator of 1), any particular reason? Should you put that reason in the
> notes?

I changed it because some of Stephen Silver's test cases looked
distinctly odd. Examples (using lexical_cast<> which makes the
oddity apparent, but assuming that lexical_cast silently ignores
trailing garbage like atol() does):

    lexical_cast<rat>("5A") --> 5/1 // 5, with trailing garbage
    lexical_cast<rat>("5/6") --> 5/6 // normal case
    lexical_cast<rat>("5?6") --> 5/1 // mistyped / as ?
    lexical_cast<rat>("5-6") --> 5/1 // should have been 5/-6?

It's not a big deal, but I feel it's more straightforward and
consistent. I didn't document the change because (a) I didn't see
the original behaviour as "by design", and (b) I don't want the
documentation to read like a changelog. (The header change
entries do mention the change - "tighten up operator>>" which is
minimal, but is how I saw the change...

> The output operator doesn't take item widths into account (i.e. it'll space
> just the numerator, not the whole fraction). You could do something like:

Wow! I'd totally missed this. On the other hand, there are more
complex issues here. How should precision be treated?
(Presumably applies to both numerator and denominator). What
about showpos? That should apply to the numerator, but not the
denominator. What about user-defined format flags for user-defined
integer types?

> template < typename IntType, typename Ch, class Tr >
> inline
> std::basic_ostream<Ch, Tr> &
> operator <<
> (
> std::basic_ostream<Ch, Tr> & os,
> const rational<IntType> & r
> )
> {
> std::basic_ostringstream<Ch, Tr> s;
> s.copyfmt( os );
> s.width( 0 );
> s << r.numerator() << '/' << r.denominator();
> return os << s.str();
> }
> This was basically ripped from Josuttis's C++ Standard Library book, which
> also has an input example.

This includes a dependency on ostringstream, which I don't like
given that the library supplied with gcc 2.95.2 doesn't support
stringstreams :-( (And of course it doesn't cater for the other issues
I mentioned).

There's a whole big area here, at least as messy as the Koenig
lookup threads, on implemention I/O operators for user-defined
types. To be honest, I don't have the time to thrash out all the
complexities just now. I'm going to leave the I/O for rationals as it
stands, and add a note to the TODO file to look at these issues at
a later date.

I attach an updated TODO file.

> Thoughts on file "rational_example.cpp":
> 1. You have an option on the BOOST_NO_LIMITS macro that uses <limits> if
> the macro isn't defined and <limits.h> if it is. Couldn't you just use the
> latter header all the time (in its <climits> form)?

Possibly. But then I may have to worry about whether <climits>
puts its values in std - are they defined to be macros and hence
namespace-unaware, or can they be constants in std? Will all
vendors do this right? Better the devil I know...

> 2. You use the _MSC_VER macro to option on the Microsoft Visual C++
> compiler being used, due its infamous shortcomings. You should use the
> BOOST_MSVC macro, since other Windows compilers may also define the former
> macro, making it ambiguous. (Those other compilers may not have [all of]
> MSVC++'s shortcomings.)

the latest CVS which I'll use once it's released (I don't work from
the CVS). Until then, the exact workaround isn't too crucial.

> You have a problem similar to [2] above in "rational_test.cpp," line 197.
> (I guess the even better BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP macro isn't
> finalized yet.)

Again, I'll not worry but get the macro changed once the CVS
version is released. I asked one of the CVS maintainers to make
this change anyway.

Thanks for the comments,

--- rational.html.orig Mon Feb 05 17:59:45 2001
+++ rational.html Wed Feb 07 19:13:43 2001
@@ -8,7 +8,38 @@
      align="center" WIDTH="277" HEIGHT="86">
 Rational Numbers</h1>
-<h2>Class rational synopsis</h2>
+<h2><a name="Contents">Contents</h2>
+ <li>Class rational synopsis</li>
+ <li>Rationale</li>
+ <li>Background</li>
+ <li>Integer Type Requirements</li>
+ <li>Interface</li>
+ <ul>
+ <li>Utility functions</li>
+ <li>Constructors</li>
+ <li>Arithmetic operations</li>
+ <li>Input and Output</li>
+ <li>In-place assignment</li>
+ <li>Conversions</li>
+ <li>Numerator and Denominator</li>
+ </ul>
+ <li>Performance</li>
+ <li>Exceptions</li>
+ <li>Internal representation</li>
+ <li>Design notes</li>
+ <ul>
+ <li>Limited-range integer types</li>
+ <li>Conversion from floating point</li>
+ <li>Absolute Value</li>
+ <li>Swap operation</li>
+ </ul>
+ <li>References</li>
+ <li>History and Acknowledgements</li>
+<h2><a name="Class rational synopsis">Class rational synopsis</h2>
 #include &lt;boost/rational.hpp&gt;
@@ -90,7 +121,7 @@
 template &lt;typename T, typename I&gt; T rational_cast (const rational&lt;I&gt;&amp; r);
+<h2><a name="Rationale">Rationale</h2>
 Numbers come in many different forms. The most basic forms are natural numbers
 (non-negative "whole" numbers), integers and real numbers. These types are
@@ -105,7 +136,7 @@
 <p>The <b>rational</b> class is actually a implemented as a template, in a
 similar manner to the standard <b>complex</b> class.
+<h2><a name="Background">Background</h2>
 The mathematical concept of a rational number is what is commonly thought of
 as a fraction - that is, a number which can be represented as the ratio of two
@@ -135,7 +166,7 @@
 available, rational numbers based on it will never overflow and will provide
 exact calculations in all circumstances.
-<h2><a name="Requirements">Integer Type Requirements</h2>
+<h2><a name="Integer Type Requirements">Integer Type Requirements</h2>
 <p> The rational type takes a single template type parameter I. This is the
 <em>underlying integer type</em> for the rational type. Any of the built-in
@@ -201,9 +232,9 @@
 and output operators for type I.
+<h2><a name="Interface">Interface</h2>
-<h3>Utility functions</h3>
+<h3><a name="Utility functions">Utility functions</h3>
 Two utility functions are provided, which work on any type I for which the
 following operations are defined: <tt>=, +=, *=, /=, %, &lt;</tt>, and a zero
 value accessible as I(0)
@@ -226,7 +257,7 @@
 <p><em>Note:</em> In the future, these functions may be moved into a separate
 boost utility library.
+<h3><a name="Constructors">Constructors</h3>
 Rationals can be constructed from a pair (numerator, denominator) of
 integers, or a single integer. There is also a default constructor, which
 initialises the rational to a value of zero.
@@ -244,7 +275,7 @@
 there is an implicit conversion from the underlying integer type to the
 rational type.
-<h3>Arithmetic operations</h3>
+<h3><a name="Arithmetic operations">Arithmetic operations</h3>
 All of the standard numeric operators are defined for the <b>rational</b>
 class. These include:
@@ -260,7 +291,7 @@
     &lt;= &gt;=
-<h3>Input and Output</h3>
+<h3><a name="Input and Output">Input and Output</h3>
 Input and output operators <tt>&lt;&lt;</tt> and <tt>&gt;&gt;</tt>
 are provided. The external representation of a rational is
 two integers, separated by a slash (<tt>/</tt>). On input, the format must be
@@ -269,14 +300,14 @@
 external representation of an integer is defined by the undelying integer
-<h3>In-place assignment</h3>
+<h3><a name="In-place assignment">In-place assignment</h3>
 For any <tt>rational&lt;I&gt; r</tt>, <tt>r.assign(n, m)</tt> provides a
 fast equivalent of <tt>r = rational&lt;I&gt;(n, m);</tt>, without the
 construction of a temporary. While this is probably unnecessary for rationals
 based on machine integer types, it could offer a saving for rationals based on
 unlimited-precision integers, for example.
+<h3><a name="Conversions">Conversions</h3>
 There are <em>no</em> implicit conversions from rationals to any other
 type. However, there is an explicit type-conversion function,
 <tt>rational_cast&lt;T&gt;(r)</tt>. This can be used as follows:
@@ -286,7 +317,7 @@
     double nearly_pi = boost::rational_cast&lt;double&gt;(r);
-<h3>Numerator and Denominator</h3>
+<h3><a name="Numerator and Denominator">Numerator and Denominator</h3>
 Finally, access to the internal representation of rationals is provided by
 the two member functions <tt>numerator()</tt> and <tt>denominator()</tt>.
@@ -294,8 +325,9 @@
 The rational class has been designed with the implicit assumption that the
 underlying integer type will act "like" the built in integer types. The
 behavioural aspects of this assumption have been explicitly described above,
-in the Requirements section. However, in addition
-to behavioural assumptions, there are implicit performance assumptions.
+in the Integer Type Requirements
+section. However, in addition to behavioural assumptions, there are implicit
+performance assumptions.
 <p> No attempt will be made to provide detailed performance guarantees for the
 operations available on the rational class. While it is possible for such
@@ -366,7 +398,7 @@
 particularly effective as the underlying integer type for the rational class.
 Specifically, it is likely that performance will be severely sub-optimal.
+<h2><a name="Exceptions">Exceptions</h2>
 Rationals can never have a denominator of zero. (This library does not support
 representations for infinity or NaN). Should a rational result ever generate a
 denominator of zero, the exception <tt>boost::bad_rational</tt> (a subclass of
@@ -384,7 +416,7 @@
 only throw exceptions which can be thrown by the destructor of the underlying
 integer type (usually none).
-<h2>Internal representation</h2>
+<h2><a name="Internal representation">Internal representation</h2>
 <em>Note:</em> This information is for information only. Programs should not
 be written in such a way as to rely on these implementation details.
@@ -393,8 +425,8 @@
 rational type). Rationals are always stored in fully normalized form (ie,
 gcd(numerator,denominator) = 1, and the denominator is always positive).
-<h2>Design notes</h2>
-<h3>Limited-range integer types</h3>
+<h2><a name="Design notes">Design notes</h2>
+<h3><a name="Limited-range integer types">Limited-range integer types</h3>
 The rational number class is designed for use in conjunction with an
 unlimited precision integer class. With such a class, rationals are always
 exact, and no problems arise with precision loss, overflow or underflow.
@@ -424,7 +456,7 @@
 <p>It is up to the user of a rational type based on a limited-precision integer
 type to be aware of, and code in anticipation of, such issues.
-<h3>Conversion from floating point</h3>
+<h3><a name="Conversion from floating point">Conversion from floating point</h3>
 The library does not offer a conversion function from floating point to
 rational. A number of requests were received for such a conversion, but
 extensive discussions on the boost list reached the conclusion that there was
@@ -478,7 +510,7 @@
 understanding of the application requirements. All of these factors make such
 a function unsuitable for a general-purpose library such as this.
-<h3><a name="Abs">Absolute Value</h3>
+<h3><a name="Absolute Value">Absolute Value</h3>
 In the first instance, it seems logical to implement
 abs(rational&lt;IntType&gt;) in terms of abs(IntType).
 However, there are a number of issues which arise with doing so.
@@ -515,7 +547,7 @@
 <p>The same arguments imply that where the absolute value of an IntType is
 required elsewhere, the calculation is performed inline.
-<h3><a name="Swap">Swap operation</h3>
+<h3><a name="Swap operation">Swap operation</h3>
 Many user-defined types will implement a specialised swap operation, which is
 optimised for efficiency, and which can be guaranteed not to throw exceptions.
 Indeed, the std::swap() function is defined as the canonical form of such a
@@ -611,14 +643,14 @@
 implementation, for the same reasons as above, because we cannot find an
 appropriate swap() function for IntType).
+<h2><a name="References">References</h2>
 <li>The rational number header itself: rational.hpp
 <li>Some example code: rational_example.cpp
 <li>The regression test: rational_test.cpp
-<h2>History and Acknowledgements</h2>
+<h2><a name="History and Acknowledgements">History and Acknowledgements</h2>
 In December, 1999, I implemented the initial version of the rational number
 class, and submitted it to the
@@ -645,8 +677,8 @@
 <p>Discussion of the issues surrounding Koenig lookup and std::swap, as
-covered in the rationale sections on Absolute Value and
-Swap operation above, took place on the boost list in
+covered in the rationale sections on Absolute Value
+and Swap Operation above, took place on the boost list in
 January 2001.
 <p>Revised&nbsp; February 5, 2001</p>

* Number concept discussions.
* Review and possibly use the Boost testing framework
* Review issue of std::swap again. In particular I cannot currently provide a
  useful swap() for rational<>.
* Consider adding a numeric_limits specialisation.
* Review I/O operations. Use locales for the '/' character? What about
  manipulators? (width, precision, showpos, user-defined ones...)

Boost list run by bdawes at, gregod at, cpdaniel at, john at