Today I want to warn you about a bug in Boost.Rational library. This illustrates a certain danger connected with allowing convenience conversions from other types.
The library is used to represent rational numbers. A rational number is a pair of integers, which are interpreted as the nominator and denominator:
boost::rational<int> r(1, 2);
This represents the value 1/2. Additionally, for convenience, you can pass it only one integer:
boost::rational<int> q = 3;
This represents the rational number 3/1 (the denominator is assumed to be 1). A convenience function, it uses the common intuition that integral numbers are a subset of rational numbers, that 3 has the same value as 3/1. This appears to be fine.
Today, when trying to figure out why my program produces wrong numbers, I found the following code:
boost::rational<int> s = 2.5;
I was surprised to find out that as the result, the value of thus initialized
s is 2 (2/1). Check it yourself. The documentation of Boost.Rational explains in detail that because it is impossible to convert a
rational<int> without the loss of information, the authors have decided not to define it (see here). Unfortunately, the compiler was clever enough to invent the conversion itself. It first converts
int, the fractional part is discarded, and then uses the
rational’s convenience converting constructor from
I leave the discussion on where the source of the problem is, and in what ways it could be fixed to the readers. One conclusion I draw from this example is that the interface of a library is not the set of the declarations of its member functions, but the set of expressions that are made valid. As we can see above these two sets can be different.