Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r85514 - in sandbox/multiprecision.cpp_bin_float: boost/multiprecision boost/multiprecision/cpp_bin_float libs/multiprecision/test
From: john_at_[hidden]
Date: 2013-08-29 11:43:13


Author: johnmaddock
Date: 2013-08-29 11:43:13 EDT (Thu, 29 Aug 2013)
New Revision: 85514
URL: http://svn.boost.org/trac/boost/changeset/85514

Log:
Exact binary-decimal and decimal-binary conversion now works for all exponent values.

Text files modified:
   sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float.hpp | 10
   sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float/io.hpp | 275 ++++++++++++++++++++++++++++-----------
   sandbox/multiprecision.cpp_bin_float/libs/multiprecision/test/test_cpp_bin_float_io.cpp | 40 +++++
   3 files changed, 243 insertions(+), 82 deletions(-)

Modified: sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float.hpp
==============================================================================
--- sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float.hpp Thu Aug 29 11:35:02 2013 (r85513)
+++ sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float.hpp 2013-08-29 11:43:13 EDT (Thu, 29 Aug 2013) (r85514)
@@ -35,7 +35,7 @@
 private:
 
    rep_type m_data;
- int m_exponent;
+ boost::int32_t m_exponent;
    bool m_sign;
 public:
    cpp_bin_float() : m_data(), m_exponent(exponent_nan), m_sign(false) {}
@@ -264,8 +264,8 @@
 
    rep_type& bits() { return m_data; }
    const rep_type& bits()const { return m_data; }
- int& exponent() { return m_exponent; }
- const int& exponent()const { return m_exponent; }
+ boost::int32_t& exponent() { return m_exponent; }
+ const boost::int32_t& exponent()const { return m_exponent; }
    bool& sign() { return m_sign; }
    const bool& sign()const { return m_sign; }
    void check_invariants()
@@ -398,7 +398,7 @@
       return; // ault is a NaN.
    }
    
- int e_diff = a.exponent() - b.exponent();
+ boost::int32_t e_diff = a.exponent() - b.exponent();
    bool s = a.sign();
    if(e_diff >= 0)
    {
@@ -477,7 +477,7 @@
       return; // result is still a NaN.
    }
 
- int e_diff = a.exponent() - b.exponent();
+ boost::int32_t e_diff = a.exponent() - b.exponent();
    bool s = a.sign();
    if((e_diff > 0) || ((e_diff == 0) && a.bits().compare(b.bits()) >= 0))
    {

Modified: sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float/io.hpp
==============================================================================
--- sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float/io.hpp Thu Aug 29 11:35:02 2013 (r85513)
+++ sandbox/multiprecision.cpp_bin_float/boost/multiprecision/cpp_bin_float/io.hpp 2013-08-29 11:43:13 EDT (Thu, 29 Aug 2013) (r85514)
@@ -12,7 +12,7 @@
 // Multiplies a by b and shifts the result so it fits inside max_bits bits,
 // returns by how much the result was shifted.
 //
-inline int restricted_multiply(cpp_int& result, const cpp_int& a, const cpp_int& b, int max_bits, int& error)
+inline int restricted_multiply(cpp_int& result, const cpp_int& a, const cpp_int& b, int max_bits, boost::int64_t& error)
 {
    result = a * b;
    int gb = msb(result);
@@ -44,7 +44,7 @@
 // Computes a^e shifted to the right so it fits in max_bits, returns how far
 // to the right we are shifted.
 //
-inline int restricted_pow(cpp_int& result, const cpp_int& a, int e, int max_bits, int& error)
+inline int restricted_pow(cpp_int& result, const cpp_int& a, int e, int max_bits, boost::int64_t& error)
 {
    BOOST_ASSERT(&result != &a);
    int exp = 0;
@@ -72,7 +72,7 @@
    return exp;
 }
 
-inline int get_round_mode(const cpp_int& what, int location, int error)
+inline int get_round_mode(const cpp_int& what, int location, boost::int64_t error)
 {
    //
    // Can we round what at /location/, if the error in what is /error/ in
@@ -83,7 +83,9 @@
    // 1: tie.
    // 2: round up.
    //
- int error_radius = error & 1 ? (1 + error) / 2 : error / 2;
+ boost::int64_t error_radius = error & 1 ? (1 + error) / 2 : error / 2;
+ if(error_radius && ((int)msb(error_radius) >= location))
+ return -1;
    if(bit_test(what, location))
    {
       if(lsb(what) == location)
@@ -91,7 +93,7 @@
       if(!error)
          return 2; // no error, round up.
       cpp_int t = what - error_radius;
- if(lsb(t) >= location)
+ if((int)lsb(t) >= location)
          return -1;
       return 2;
    }
@@ -103,17 +105,24 @@
    return 0;
 }
 
-inline int get_round_mode(cpp_int& r, cpp_int& d, int error)
+inline int get_round_mode(cpp_int& r, cpp_int& d, boost::int64_t error, const cpp_int& q)
 {
    //
- // Lets suppose we have an inexact division by d, where the true
- // value for the divisor is d+error, then we have:
+ // Lets suppose we have an inexact division by d+delta, where the true
+ // value for the divisor is d, and with |delta| <= error/2, then
+ // we have calculated q and r such that:
    //
    // n r
    // --- = q + -----------
    // d + error d + error
    //
- // So rounding depends on whether 2r > d + error.
+ // Rearranging for n / d we get:
+ //
+ // n delta*q + r
+ // --- = q + -------------
+ // d d
+ //
+ // So rounding depends on whether 2r + error * q > d.
    //
    // We return:
    // 0 = down down.
@@ -125,16 +134,21 @@
    int c = r.compare(d);
    if(c == 0)
       return error ? -1 : 1;
- //
- // Error is in units of 0.5ulp, so figure out what radius on d that is:
- error = error & 1 ? (error + 1) / 2 : error / 2;
    if(c > 0)
    {
- d += error;
- return r.compare(d) > 0 ? 2 : -1;
+ if(error)
+ {
+ r -= error * q;
+ return r.compare(d) > 0 ? 2 : -1;
+ }
+ return 2;
+ }
+ if(error)
+ {
+ r += error * q;
+ return r.compare(d) < 0 ? 0 : -1;
    }
- d -= error;
- return r.compare(d) < 0 ? 0 : -1;
+ return 0;
 }
 
 } // namespace
@@ -144,9 +158,10 @@
 template <unsigned Bits>
 cpp_bin_float<Bits>& cpp_bin_float<Bits>::operator=(const char *s)
 {
- const char* org_s = s;
    cpp_int n;
    int decimal_exp = 0;
+ int digits_seen = 0;
+ static const int max_digits_seen = 4 + (Bits * 301L) / 1000;
    bool ss = false;
    //
    // Extract the sign:
@@ -179,6 +194,8 @@
    {
       n *= 10u;
       n += *s - '0';
+ if(digits_seen || (*s != '0'))
+ ++digits_seen;
       ++s;
    }
    // The decimal point (we really should localise this!!)
@@ -192,9 +209,18 @@
       n *= 10u;
       n += *s - '0';
       --decimal_exp;
+ if(digits_seen || (*s != '0'))
+ ++digits_seen;
       ++s;
+ if(digits_seen > max_digits_seen)
+ break;
    }
    //
+ // Digits we're skipping:
+ //
+ while(*s && (*s >= '0') && (*s <= '9'))
+ ++s;
+ //
    // See if there's an exponent:
    //
    if(*s && ((*s == 'e') || (*s == 'E')))
@@ -232,76 +258,146 @@
       *this = 0;
       return *this;
    }
- if(decimal_exp > 300)
- {
- //
- // TODO, FIXME, temporary hack!!
- boost::multiprecision::detail::convert_from_string(*this, org_s);
- }
- else if(decimal_exp >= 0)
+
+ static const unsigned limb_bits = sizeof(limb_type) * CHAR_BIT;
+ //
+ // Set our working precision - this is heuristic based, we want
+ // a value as small as possible > Bits to avoid large computations
+ // and excessive memory usage, but we also want to avoid having to
+ // up the computation and start again at a higher precision.
+ // So we round Bits up to the nearest whole number of limbs, and add
+ // one limb for good measure. This works very well for small exponents,
+ // but for larger exponents we may may need to restart, we could add some
+ // extra precision right from the start for larger exponents, but this
+ // seems to be slightly slower in the *average* case:
+ //
+#ifdef BOOST_MP_STRESS_IO
+ int max_bits = Bits + 32;
+#else
+ int max_bits = Bits + (Bits % limb_bits ? limb_bits - Bits % limb_bits : 0) + limb_bits;
+#endif
+ boost::int64_t error = 0;
+ int calc_exp = 0;
+
+ if(decimal_exp >= 0)
    {
       // Nice and simple, the result is an integer...
- n *= pow(cpp_int(5), decimal_exp);
- exponent() = (int)Bits - 1;
- exponent() += decimal_exp;
- copy_and_round(*this, n.backend());
+ do
+ {
+ cpp_int t;
+ if(decimal_exp)
+ {
+ calc_exp = boost::multiprecision::cpp_bf_io_detail::restricted_pow(t, cpp_int(5), decimal_exp, max_bits, error);
+ calc_exp += boost::multiprecision::cpp_bf_io_detail::restricted_multiply(t, t, n, max_bits, error);
+ }
+ exponent() = (int)Bits - 1;
+ exponent() += decimal_exp;
+ exponent() += calc_exp;
+ int rshift = msb(t) - Bits + 1;
+ if(rshift > 0)
+ {
+ exponent() += rshift;
+ int roundup = boost::multiprecision::cpp_bf_io_detail::get_round_mode(t, rshift - 1, error);
+ t >>= rshift;
+ if((roundup == 2) || ((roundup == 1) && t.backend().limbs()[0] & 1))
+ ++t;
+ else if(roundup < 0)
+ {
+#ifdef BOOST_MP_STRESS_IO
+ max_bits += 32;
+#else
+ max_bits *= 2;
+#endif
+ error = 0;
+ continue;
+ }
+ }
+ else
+ {
+ BOOST_ASSERT(!error);
+ }
+ copy_and_round(*this, t.backend());
+ break;
+ }
+ while(true);
+
       if(ss != sign())
          negate();
    }
- else if(decimal_exp > -300)
+ else
    {
       // Result is the ratio of two integers: we need to organise the
       // division so as to produce at least an N-bit result which we can
       // round according to the remainder.
- cpp_int d = pow(cpp_int(5), -decimal_exp);
- int shift = (int)Bits - msb(n) + msb(d);
- exponent() = Bits - 1 + decimal_exp;
- if(shift > 0)
- {
- n <<= shift;
- exponent() -= shift;
- }
- cpp_int q, r;
- divide_qr(n, d, q, r);
- int gb = msb(q);
- BOOST_ASSERT(gb >= Bits - 1);
- //
- // Check for rounding conditions we have to
- // handle ourselves:
- //
- if(gb == Bits - 1)
+ //cpp_int d = pow(cpp_int(5), -decimal_exp);
+ do
       {
- // Exactly the right number of bits, use the remainder to round:
- r *= 2;
- int c = r.compare(d);
- if(c == 0)
- {
- // Tie:
- if(q.backend().limbs()[0] & 1)
- ++q;
+ cpp_int d;
+ calc_exp = boost::multiprecision::cpp_bf_io_detail::restricted_pow(d, cpp_int(5), -decimal_exp, max_bits, error);
+ int shift = (int)Bits - msb(n) + msb(d);
+ exponent() = Bits - 1 + decimal_exp - calc_exp;
+ if(shift > 0)
+ {
+ n <<= shift;
+ exponent() -= shift;
          }
- else if(c > 0)
- ++q;
- }
- else if(bit_test(q, gb - (int)Bits) && ((int)lsb(q) == (gb - (int)Bits)))
- {
- // Too many bits in q and the bits in q indicate a tie, but we can break that using r:
- q >>= gb - (int)Bits + 1;
- BOOST_ASSERT(msb(q) >= Bits - 1);
- if(r)
- ++q;
- else if(q.backend().limbs()[0] & 1)
+ cpp_int q, r;
+ divide_qr(n, d, q, r);
+ int gb = msb(q);
+ BOOST_ASSERT(gb >= Bits - 1);
+ //
+ // Check for rounding conditions we have to
+ // handle ourselves:
+ //
+ int roundup = 0;
+ if(gb == Bits - 1)
+ {
+ // Exactly the right number of bits, use the remainder to round:
+ roundup = boost::multiprecision::cpp_bf_io_detail::get_round_mode(r, d, error, q);
+ }
+ else if(bit_test(q, gb - (int)Bits) && ((int)lsb(q) == (gb - (int)Bits)))
+ {
+ // Too many bits in q and the bits in q indicate a tie, but we can break that using r,
+ // note that the radius of error in r is error/2 * q:
+ int shift = gb - (int)Bits + 1;
+ q >>= shift;
+ exponent() += shift;
+ BOOST_ASSERT(msb(q) >= Bits - 1);
+ if(error && (r < (error / 2) * q))
+ roundup = -1;
+ else if(error && (r + (error / 2) * q >= d))
+ roundup = -1;
+ else
+ roundup = r ? 2 : 1;
+ }
+ else if(error && (((error / 2) * q + r >= d) || (r < (error / 2) * q)))
+ {
+ // We might have been rounding up, or got the wrong quotient: can't tell!
+ roundup = -1;
+ }
+ if(roundup < 0)
+ {
+#ifdef BOOST_MP_STRESS_IO
+ max_bits += 32;
+#else
+ max_bits *= 2;
+#endif
+ error = 0;
+ if(shift > 0)
+ {
+ n >>= shift;
+ exponent() += shift;
+ }
+ continue;
+ }
+ else if((roundup == 2) || ((roundup == 1) && q.backend().limbs()[0] & 1))
             ++q;
- exponent() += gb - (int)Bits + 1;
+ copy_and_round(*this, q.backend());
+ if(ss != sign())
+ negate();
+ break;
       }
- copy_and_round(*this, q.backend());
- if(ss != sign())
- negate();
- }
- else
- {
- // TODO, FIXME, temporary hack!!!
- boost::multiprecision::detail::convert_from_string(*this, org_s);
+ while(true);
    }
 
    return *this;
@@ -355,10 +451,25 @@
       cpp_int i;
       int roundup = 0; // 0=no rounding, 1=tie, 2=up
       static const unsigned limb_bits = sizeof(limb_type) * CHAR_BIT;
+ //
+ // Set our working precision - this is heuristic based, we want
+ // a value as small as possible > Bits to avoid large computations
+ // and excessive memory usage, but we also want to avoid having to
+ // up the computation and start again at a higher precision.
+ // So we round Bits up to the nearest whole number of limbs, and add
+ // one limb for good measure. This works very well for small exponents,
+ // but for larger exponents we add a few extra limbs to max_bits:
+ //
+#ifdef BOOST_MP_STRESS_IO
+ int max_bits = Bits + 32;
+#else
       int max_bits = Bits + (Bits % limb_bits ? limb_bits - Bits % limb_bits : 0) + limb_bits;
+ if(power10)
+ max_bits += (msb(std::abs(power10)) / 8) * limb_bits;
+#endif
       do
       {
- int error = 0;
+ boost::int64_t error = 0;
          int calc_exp = 0;
          //
          // Our integer result is: bits() * 2^-shift * 5^power10
@@ -383,10 +494,14 @@
                i <<= -shift;
                cpp_int r;
                divide_qr(i, d, i, r);
- roundup = boost::multiprecision::cpp_bf_io_detail::get_round_mode(r, d, error);
+ roundup = boost::multiprecision::cpp_bf_io_detail::get_round_mode(r, d, error, i);
                if(roundup < 0)
                {
+#ifdef BOOST_MP_STRESS_IO
+ max_bits += 32;
+#else
                   max_bits *= 2;
+#endif
                   shift = (int)Bits - exponent() - 1 - power10;
                   continue;
                }
@@ -410,7 +525,11 @@
                {
                   // We only get here if we were asked for a crazy number of decimal digits -
                   // more than are present in a 2^max_bits number.
+#ifdef BOOST_MP_STRESS_IO
+ max_bits += 32;
+#else
                   max_bits *= 2;
+#endif
                   shift = (int)Bits - exponent() - 1 - power10;
                   continue;
                }
@@ -419,7 +538,11 @@
                   roundup = boost::multiprecision::cpp_bf_io_detail::get_round_mode(i, shift - 1, error);
                   if(roundup < 0)
                   {
+#ifdef BOOST_MP_STRESS_IO
+ max_bits += 32;
+#else
                      max_bits *= 2;
+#endif
                      shift = (int)Bits - exponent() - 1 - power10;
                      continue;
                   }

Modified: sandbox/multiprecision.cpp_bin_float/libs/multiprecision/test/test_cpp_bin_float_io.cpp
==============================================================================
--- sandbox/multiprecision.cpp_bin_float/libs/multiprecision/test/test_cpp_bin_float_io.cpp Thu Aug 29 11:35:02 2013 (r85513)
+++ sandbox/multiprecision.cpp_bin_float/libs/multiprecision/test/test_cpp_bin_float_io.cpp 2013-08-29 11:43:13 EDT (Thu, 29 Aug 2013) (r85514)
@@ -13,6 +13,7 @@
 
 #include <boost/random/mersenne_twister.hpp>
 #include <boost/random/uniform_int.hpp>
+#include <boost/chrono.hpp>
 #include "test.hpp"
 #include <boost/array.hpp>
 #include <iostream>
@@ -22,6 +23,28 @@
 #pragma warning(disable:4127)
 #endif
 
+template <class Clock>
+struct stopwatch
+{
+ typedef typename Clock::duration duration;
+ stopwatch()
+ {
+ m_start = Clock::now();
+ }
+ duration elapsed()
+ {
+ return Clock::now() - m_start;
+ }
+ void reset()
+ {
+ m_start = Clock::now();
+ }
+
+private:
+ typename Clock::time_point m_start;
+};
+
+
 void print_flags(std::ios_base::fmtflags f)
 {
    std::cout << "Formatting flags were: ";
@@ -135,6 +158,15 @@
       val = static_cast<T>("nan");
       BOOST_CHECK((boost::math::isnan)(val));
    }
+ //
+ // Min and max values:
+ //
+ T t((std::numeric_limits<T>::max)().str(std::numeric_limits<T>::max_digits10, std::ios_base::scientific));
+ BOOST_CHECK_EQUAL(t, (std::numeric_limits<T>::max)());
+ t = T((std::numeric_limits<T>::min)().str(std::numeric_limits<T>::max_digits10, std::ios_base::scientific));
+ BOOST_CHECK_EQUAL(t, (std::numeric_limits<T>::min)());
+ t = T((std::numeric_limits<T>::lowest)().str(std::numeric_limits<T>::max_digits10, std::ios_base::scientific));
+ BOOST_CHECK_EQUAL(t, (std::numeric_limits<T>::lowest)());
 }
 
 template <class T>
@@ -153,7 +185,7 @@
    e_type e;
    val = frexp(val, &e);
 
- static boost::random::uniform_int_distribution<e_type> ui(0, 250);
+ static boost::random::uniform_int_distribution<e_type> ui(0, std::numeric_limits<T>::max_exponent);
    return ldexp(val, ui(gen));
 }
 
@@ -186,9 +218,13 @@
 template <class T>
 void test_round_trip()
 {
+ std::cout << "Testing type " << typeid(T).name() << std::endl;
    std::cout << "digits = " << std::numeric_limits<T>::digits << std::endl;
    std::cout << "digits10 = " << std::numeric_limits<T>::digits10 << std::endl;
    std::cout << "max_digits10 = " << std::numeric_limits<T>::max_digits10 << std::endl;
+
+ stopwatch<boost::chrono::high_resolution_clock> w;
+
    for(unsigned i = 0; i < 10000; ++i)
    {
       T val = generate_random<T>();
@@ -197,6 +233,8 @@
       do_round_trip(T(1/val));
       do_round_trip(T(-1/val));
    }
+
+ std::cout << "Execution time = " << boost::chrono::duration_cast<boost::chrono::duration<double> >(w.elapsed()).count() << "s" << std::endl;
 }
 
 int main()


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk