conversion error from boost cpp_rational to int

1.7k views Asked by At

boost cpp_rational seems to convert wrong to int when the numerator and denominator have many digits.

#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>

using namespace boost::multiprecision;
using namespace std;

int main() {
  cpp_rational a("4561231231235/123123123123");
  std::cout << "bad convert: " << a << ' '  << 
     float(a) << ' ' << int(a) << ' ' << 
     a.convert_to<int>() << endl;
  a = (cpp_rational)"456/123";
  std::cout << "good convert: " << a << ' '  <<
     float(a) << ' ' << int(a) << ' ' << 
     a.convert_to<int>() << endl;
}

The output is:

bad convert: 651604461605/17589017589 37.0461 -3 -3
good convert: 152/41 3.70732 3 3

Also, attempts to convert cpp_rational to cpp_int fail to compile, e.g., using

cpp_int b = static_cast<cpp_int> (a);
cpp_int b = a.convert_to<cpp_int>();

What I want to happen is to divide and round down and never get it wrong even close to an int.

Help? Thanks.

1

There are 1 answers

3
sehe On

The docs say

Conversions are also allowed:

d = a; // OK, widening conversion.
d = a * b;  // OK, can convert from an expression template too.

However conversions that are inherently lossy are either declared explicit or else forbidden altogether:

d = 3.14;  // Error implicit conversion from float not allowed.
d = static_cast<mp::int512_t>(3.14);  // OK explicit construction is allowed

So, what you're seeing is a lossy conversion. Demonstrating that the conversion would be disallowed if you hadn't specified it explicitly:

cpp_rational a("4561231231235/123123123123");
int i   = a;        // error: cannot convert `cpp_rational` to `int` in initialization
float f = a;        // error: cannot convert `cpp_rational` to `float` in initialization
long double ld = a; // error: cannot convert `cpp_rational` to `long double` in initialization
long long   ll = a; // error: cannot convert `cpp_rational` to `long long int` in initialization

So, what you're doing with all the explicit casts, is telling the compiler to "Shut up; I know what I'm doing". Hence why you didn't get the warning.

Now, how to fix things: keep the division in the multi-precision domain:

cpp_int v = numerator(a)/denominator(a);

Your sample fixed: Live On Coliru

#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>

using namespace boost::multiprecision;

int main() {
    cpp_rational a("4561231231235/123123123123");
    cpp_int v = numerator(a)/denominator(a);
    std::cout << "convert: " << v.convert_to<int>() << "\n";

    a = cpp_rational("456/123");
    v = numerator(a)/denominator(a);
    std::cout << "convert: " << v.convert_to<int>() << "\n";
}