Opened 6 years ago

Closed 6 years ago

Last modified 6 years ago

#12327 closed Bugs (fixed)

cpp_rational::convert_to<double>() does not return the nearest number

Reported by: komakisen@… Owned by: John Maddock
Milestone: Boost 1.62.0 Component: multiprecision
Version: Boost 1.61.0 Severity: Problem
Keywords: Cc: komakisen@…

Description

Hello,

I tested with the following program to see if boost::multiprecision::cpp_rational::convert_to<double>() returns the nearest double number to the exact value. I expected it to print only "true"s, but it actually prints some "false"s too. Is it a bug or (un)documented behavior?

#include <boost/multiprecision/cpp_int.hpp>

typedef boost::multiprecision::cpp_rational NT;

void test_convert_to_double(double d1, double d2)
{
   NT r1(d1);
   NT r2(d2);

   NT two(2);
   NT three(3);

   NT r112 = (two * r1 + r2) / three;
   NT r12 = (r1 + r2) / two;
   NT r122 = (r1 + two * r2) / three;

   double rd1 = r1.convert_to<double>();
   double rd112 = r112.convert_to<double>();
   double rd12 = r12.convert_to<double>();
   double rd122 = r122.convert_to<double>();
   double rd2 = r2.convert_to<double>();

   std::cout << std::boolalpha;
   std::cout << (rd1 == d1) << std::endl;
   std::cout << (rd112 == d1) << std::endl;
   std::cout << (rd12 == d2) << std::endl;
   std::cout << (rd122 == d2) << std::endl;
   std::cout << (rd2 == d2) << std::endl;
}

int main()
{
   volatile double a = 0.099999999999999992;
   volatile double b = 0.10000000000000001;
   volatile double c = 0.10000000000000002;

   // prints true, false, true, true, true
   test_convert_to_double(a, b);

   // prints true, true, false, false, true
   test_convert_to_double(b, c);

   return 0;
}

I compiled this program with MSVC 14.0 and ICL 16.0.3, both for x64 Debug build. They give the same results.

Change History (4)

comment:1 by John Maddock, 6 years ago

I believe it's an error in your test program, taking the first example:

0.099999999999999992 = 7205759403792793/72057594037927936
0.10000000000000001  = 3602879701896397/36028797018963968

Note that these fractions are exact (in the binary, not decimal sense).

Then r112 becomes 5404319552844595/54043195528445952 which is 0.0999999999999999962992565845828115319212277730305989583... in decimal, which rounds up to 0.10000000000000001 and not down to 0.099999999999999992 due to the "6" after the string of 9's.

Last edited 6 years ago by John Maddock (previous) (diff)

in reply to:  1 comment:2 by komakisen@…, 6 years ago

Replying to johnmaddock:

Then r112 becomes 5404319552844595/54043195528445952 which is 0.0999999999999999962992565845828115319212277730305989583... in decimal, which rounds up to 0.10000000000000001 and not down to 0.099999999999999992 due to the "6" after the string of 9's.

Thank you for investigating my program, but I still don't understand this point.

To begin with, 0.099999999999999992 is actually 0.09999999999999999167332... and 0.10000000000000001 is actually 0.1000000000000000055511... (they differ by 1ulp)

So 0.09999999999999999629925... is less than their midpoint 0.09999999999999999861222...

Also, double is a binary floating-point number. Thus I believe round-off calculation on a decimal digit "6" explains nothing here.

If I run the following program, I get 0.099999999999999992.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
   double d = 0.09999999999999999629926;
   std::cout << std::setprecision(17) << d << std::endl;
   return 0;
}

So isn't 0.099999999999999992 the expected result? Or am I confusing myself?

comment:3 by John Maddock, 6 years ago

Milestone: To Be DeterminedBoost 1.62.0
Resolution: fixed
Status: newclosed

You're correct, I was the one confused!

Fixed in https://github.com/boostorg/multiprecision/commit/7ebd9dfd9b0bc463642b065036a3cd6947a74431.

However, your second test case still fails testing rd12, in this case I *think* the bug is yours: r12 is

14411518807585589/144115188075855872

which in binary is exactly the 54 bit quantity:

0.000 1100110011 0011001100 1100110011 0011001100 1100110011 010 1
                                                    rounded here ^

And in this case no rounding up occurs since we have a tie and round to even.

Please do correct me and re-open if I'm wrong! :)

Last edited 6 years ago by John Maddock (previous) (diff)

in reply to:  3 comment:4 by komakisen@…, 6 years ago

Great work! Thank you very much! :)

And in this case no rounding up occurs since we have a tie and round to even.

Yes, you're right! Sorry for messing up.

Note: See TracTickets for help on using tickets.