From 3913771ea0984d7bbd43d00a27afb520ad615743 Mon Sep 17 00:00:00 2001 From: John Maddock Date: Thu, 29 Mar 2012 18:00:06 +0000 Subject: [PATCH] Change cpp_dec_float string conversion to generate a better error message when conversion fails. Add generic inter-conversions and tests for them. Fix bug in cpp_int convert_to. [SVN r77630] --- .../boost/multiprecision/cpp_dec_float.hpp | 14 +- include/boost/multiprecision/cpp_int.hpp | 4 +- .../detail/generic_interconvert.hpp | 221 ++++++++++++++++++ include/boost/multiprecision/mp_number.hpp | 21 +- test/Jamfile.v2 | 11 + test/test_generic_conv.cpp | 148 ++++++++++++ 6 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 include/boost/multiprecision/detail/generic_interconvert.hpp create mode 100644 test/test_generic_conv.cpp diff --git a/include/boost/multiprecision/cpp_dec_float.hpp b/include/boost/multiprecision/cpp_dec_float.hpp index da5dfe56..91a82f24 100644 --- a/include/boost/multiprecision/cpp_dec_float.hpp +++ b/include/boost/multiprecision/cpp_dec_float.hpp @@ -1804,6 +1804,8 @@ std::string cpp_dec_float::str(boost::intmax_t number_of_digits, std:: template bool cpp_dec_float::rd_string(const char* const s) { + try{ + std::string str(s); // Get a possible exponent and remove it. @@ -1849,7 +1851,7 @@ bool cpp_dec_float::rd_string(const char* const s) *this = this->inf(); return true; } - if((str.size() >= 3) && ((str.substr(0, 3) == "nan") || (str.substr(0, 3) == "NAN"))) + if((str.size() >= 3) && ((str.substr(0, 3) == "nan") || (str.substr(0, 3) == "NAN") || (str.substr(0, 3) == "NaN"))) { *this = this->nan(); return true; @@ -2056,6 +2058,16 @@ bool cpp_dec_float::rd_string(const char* const s) } } + } + catch(const bad_lexical_cast&) + { + // Rethrow with better error message: + std::string msg = "Unable to parse the string \""; + msg += s; + msg += "\" as a floating point value."; + throw std::runtime_error(msg); + } + return true; } diff --git a/include/boost/multiprecision/cpp_int.hpp b/include/boost/multiprecision/cpp_int.hpp index c1024a75..a72008c4 100644 --- a/include/boost/multiprecision/cpp_int.hpp +++ b/include/boost/multiprecision/cpp_int.hpp @@ -2025,12 +2025,10 @@ inline typename enable_if, void>::type eval_convert_to(R* result, { *result = static_cast(backend.limbs()[0]); unsigned shift = cpp_int_backend::limb_bits; - for(unsigned i = 1; i < backend.size(); ++i) + for(unsigned i = 1; (i < backend.size()) && (shift < static_cast(std::numeric_limits::digits)); ++i) { *result += static_cast(backend.limbs()[i]) << shift; shift += cpp_int_backend::limb_bits; - if(shift > static_cast(std::numeric_limits::digits)) - break; } if(backend.sign()) { diff --git a/include/boost/multiprecision/detail/generic_interconvert.hpp b/include/boost/multiprecision/detail/generic_interconvert.hpp new file mode 100644 index 00000000..4356e522 --- /dev/null +++ b/include/boost/multiprecision/detail/generic_interconvert.hpp @@ -0,0 +1,221 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2011 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_MP_GENERIC_INTERCONVERT_HPP +#define BOOST_MP_GENERIC_INTERCONVERT_HPP + +#include + +namespace boost{ namespace multiprecision{ namespace detail{ + +template +void generic_interconvert(To& to, const From& from, const mpl::int_& /*to_type*/, const mpl::int_& /*from_type*/) +{ + using default_ops::eval_get_sign; + using default_ops::eval_bitwise_and; + using default_ops::eval_convert_to; + using default_ops::eval_right_shift; + using default_ops::eval_ldexp; + using default_ops::eval_add; + // smallest unsigned type handled natively by "From" is likely to be it's limb_type: + typedef typename canonical::type limb_type; + // get the corresponding type that we can assign to "To": + typedef typename canonical::type to_type; + From t(from); + bool is_neg = eval_get_sign(t) < 0; + if(is_neg) + t.negate(); + // Pick off the first limb: + limb_type limb; + limb_type mask = ~static_cast(0); + From fl; + eval_bitwise_and(fl, t, mask); + eval_convert_to(&limb, fl); + to = static_cast(limb); + eval_right_shift(t, std::numeric_limits::digits); + // + // Then keep picking off more limbs until "t" is zero: + // + To l; + unsigned shift = std::numeric_limits::digits; + while(!eval_is_zero(t)) + { + eval_bitwise_and(fl, t, mask); + eval_convert_to(&limb, fl); + l = static_cast(limb); + eval_right_shift(t, std::numeric_limits::digits); + eval_ldexp(l, l, shift); + eval_add(to, l); + shift += std::numeric_limits::digits; + } + // + // Finish off by setting the sign: + // + if(is_neg) + to.negate(); +} + +template +void generic_interconvert(To& to, const From& from, const mpl::int_& /*to_type*/, const mpl::int_& /*from_type*/) +{ + using default_ops::eval_get_sign; + using default_ops::eval_bitwise_and; + using default_ops::eval_convert_to; + using default_ops::eval_right_shift; + using default_ops::eval_left_shift; + using default_ops::eval_bitwise_or; + // smallest unsigned type handled natively by "From" is likely to be it's limb_type: + typedef typename canonical::type limb_type; + // get the corresponding type that we can assign to "To": + typedef typename canonical::type to_type; + From t(from); + bool is_neg = eval_get_sign(t) < 0; + if(is_neg) + t.negate(); + // Pick off the first limb: + limb_type limb; + limb_type mask = ~static_cast(0); + From fl; + eval_bitwise_and(fl, t, mask); + eval_convert_to(&limb, fl); + to = static_cast(limb); + eval_right_shift(t, std::numeric_limits::digits); + // + // Then keep picking off more limbs until "t" is zero: + // + To l; + unsigned shift = std::numeric_limits::digits; + while(!eval_is_zero(t)) + { + eval_bitwise_and(fl, t, mask); + eval_convert_to(&limb, fl); + l = static_cast(limb); + eval_right_shift(t, std::numeric_limits::digits); + eval_left_shift(l, shift); + eval_bitwise_or(to, l); + shift += std::numeric_limits::digits; + } + // + // Finish off by setting the sign: + // + if(is_neg) + to.negate(); +} + +template +void generic_interconvert(To& to, const From& from, const mpl::int_& /*to_type*/, const mpl::int_& /*from_type*/) +{ + // + // The code here only works when the radix of "From" is 2, we could try shifting by other + // radixes but it would complicate things.... use a string convertion when the radix is other + // than 2: + // + if(std::numeric_limits >::radix != 2) + { + to = from.str(0, std::ios_base::fmtflags()).c_str(); + return; + } + + + typedef typename canonical::type ui_type; + + using default_ops::eval_fpclassify; + using default_ops::eval_add; + using default_ops::eval_subtract; + using default_ops::eval_convert_to; + + // + // First classify the input, then handle the special cases: + // + int c = eval_fpclassify(from); + + if(c == FP_ZERO) + { + to = ui_type(0); + return; + } + else if(c == FP_NAN) + { + to = "nan"; + return; + } + else if(c == FP_INFINITE) + { + to = "inf"; + if(eval_get_sign(from) < 0) + to.negate(); + return; + } + + typename From::exponent_type e; + From f, term; + to = ui_type(0); + + eval_frexp(f, from, &e); + + static const int shift = std::numeric_limits::digits - 1; + + while(!eval_is_zero(f)) + { + // extract int sized bits from f: + eval_ldexp(f, f, shift); + eval_floor(term, f); + e -= shift; + eval_ldexp(to, to, shift); + long long ll; + eval_convert_to(&ll, term); + eval_add(to, ll); + eval_subtract(f, term); + } + typedef typename To::exponent_type to_exponent; + if((e > (std::numeric_limits::max)()) || (e < (std::numeric_limits::min)())) + { + to = "inf"; + if(eval_get_sign(from) < 0) + to.negate(); + return; + } + eval_ldexp(to, to, static_cast(e)); +} + +template +void generic_interconvert(To& to, const From& from, const mpl::int_& /*to_type*/, const mpl::int_& /*from_type*/) +{ + typedef typename component_type >::type from_component_type; + typedef typename component_type >::type to_component_type; + + mp_number t(from); + to_component_type n(numerator(t)), d(denominator(t)); + using default_ops::assign_components; + assign_components(to, n.backend(), d.backend()); +} + +template +void generic_interconvert(To& to, const From& from, const mpl::int_& /*to_type*/, const mpl::int_& /*from_type*/) +{ + typedef typename component_type >::type to_component_type; + + mp_number t(from); + to_component_type n(t), d(1); + using default_ops::assign_components; + assign_components(to, n.backend(), d.backend()); +} + +template +void generic_interconvert(To& to, const From& from, const mpl::int_& /*to_type*/, const mpl::int_& /*from_type*/) +{ + typedef typename component_type >::type from_component_type; + using default_ops::eval_divide; + + mp_number t(from); + from_component_type n(numerator(t)), d(denominator(t)); + mp_number fn(n), fd(d); + eval_divide(to, fn.backend(), fd.backend()); +} + +}}} // namespaces + +#endif // BOOST_MP_GENERIC_INTERCONVERT_HPP + diff --git a/include/boost/multiprecision/mp_number.hpp b/include/boost/multiprecision/mp_number.hpp index 98df86ec..41f9106c 100644 --- a/include/boost/multiprecision/mp_number.hpp +++ b/include/boost/multiprecision/mp_number.hpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include namespace boost{ namespace multiprecision{ @@ -63,6 +63,14 @@ public: { m_backend = val.backend(); } + template + mp_number(const mp_number& val, typename disable_if >::type* dummy1 = 0) + { + // + // Attempt a generic interconvertion: + // + detail::generic_interconvert(backend(), val.backend(), number_category(), number_category()); + } template mp_number(V v1, V v2, typename enable_if, is_same, is_convertible > >::type* dummy1 = 0) { @@ -125,6 +133,17 @@ public: return *this; } + template + typename disable_if, mp_number& >::type + operator=(const mp_number& v) + { + // + // Attempt a generic interconvertion: + // + detail::generic_interconvert(backend(), v.backend(), number_category(), number_category()); + return *this; + } + template mp_number(const detail::mp_exp& e) { diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index eeed5577..1e3087ee 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -626,6 +626,17 @@ run test_rational_io.cpp gmp [ check-target-builds ../config//has_gmp : : no ] : test_rational_io_mpz ; +run test_generic_conv.cpp + : # command line + : # input files + : # requirements + [ check-target-builds ../config//has_gmp : TEST_GMP gmp : ] + [ check-target-builds ../config//has_tommath : TEST_TOMMATH $(TOMMATH) : ] + [ check-target-builds ../config//has_mpfr : TEST_MPFR mpfr : ] + release + ; + + run ../example/gmp_snips.cpp gmp : # command line : # input files diff --git a/test/test_generic_conv.cpp b/test/test_generic_conv.cpp new file mode 100644 index 00000000..df033f83 --- /dev/null +++ b/test/test_generic_conv.cpp @@ -0,0 +1,148 @@ +/////////////////////////////////////////////////////////////// +// Copyright 2012 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_ +// + +#ifdef _MSC_VER +# define _SCL_SECURE_NO_WARNINGS +#endif + +#include +#include +#include "test.hpp" + +#include +#include +#include + +#ifdef TEST_GMP +#include +#endif +#ifdef TEST_TOMMATH +#include +#endif +#ifdef TEST_MPFR +#include +#endif + +int main() +{ + using namespace boost::multiprecision; + using namespace boost::random; + + independent_bits_engine gen; + + for(unsigned i = 0; i < 100; ++i) + { + cpp_int c = gen(); + // + // Integer to integer conversions first: + // +#ifdef TEST_GMP + mpz_int z(c); + cpp_int t(z); + BOOST_CHECK_EQUAL(t, c); + z = -c; + t = -z; + BOOST_CHECK_EQUAL(t, c); +#endif +#ifdef TEST_TOMMATH + tom_int tom(c); + cpp_int t2(tom); + BOOST_CHECK_EQUAL(t2, c); + tom = -c; + t2 = -tom; + BOOST_CHECK_EQUAL(t2, c); +#endif + // + // Now integer to float: + // + typedef mp_number > dec_float_500; + dec_float_500 df(c); + dec_float_500 df2(c.str()); + BOOST_CHECK_EQUAL(df, df2); + df = -c; + df2 = -df2; + BOOST_CHECK_EQUAL(df, df2); +#ifdef TEST_GMP + typedef mp_number > mpf_type; + mpf_type mpf(c); + mpf_type mpf2(c.str()); + BOOST_CHECK_EQUAL(mpf, mpf2); + mpf = -c; + mpf2 = -mpf2; + BOOST_CHECK_EQUAL(mpf, mpf2); +#endif +#ifdef TEST_MPFR + typedef mp_number > mpfr_type; + mpfr_type mpfr(c); + mpfr_type mpfr2(c.str()); + BOOST_CHECK_EQUAL(mpfr, mpfr2); + mpfr = -c; + mpfr2 = -mpfr2; + BOOST_CHECK_EQUAL(mpfr, mpfr2); +#endif + // + // Now float to float: + // + df = c; + df /= dec_float_500(gen()); + dec_float_500 tol("1e-500"); +#ifdef TEST_GMP + mpf = df; + mpf2 = df.str(); + BOOST_CHECK_EQUAL(mpf, mpf2); + df = mpf; + df2 = mpf.str(); + BOOST_CHECK(fabs((df - df2) / df) < tol); +#endif +#ifdef TEST_MPFR + mpfr = df; + mpfr2 = df.str(); + BOOST_CHECK_EQUAL(mpfr, mpfr2); + df = mpfr; + df2 = mpfr.str(); + BOOST_CHECK(fabs((df - df2) / df) < tol); +#endif + // + // Rational to rational conversions: + // + cpp_rational cppr(c, gen()); +#ifdef TEST_GMP + mpq_rational mpq(cppr); + cpp_rational cppr2(mpq); + BOOST_CHECK_EQUAL(cppr, cppr2); +#endif +#ifdef TEST_TOMMATH + tom_rational tr(cppr); + cpp_rational cppr3(tr); + BOOST_CHECK_EQUAL(cppr, cppr3); +#endif + // + // Integer to rational conversions: + // +#ifdef TEST_GMP + mpq = c; + mpq_rational mpq2 = c.str(); + BOOST_CHECK_EQUAL(mpq, mpq2); +#endif +#ifdef TEST_TOMMATH + tr = c; + tom_rational tr2 = c.str(); + BOOST_CHECK_EQUAL(tr, tr2); +#endif + // + // Rational to float: + // + df = cppr; + df2 = numerator(cppr); + df2 /= dec_float_500(denominator(cppr)); + BOOST_CHECK(fabs(df - df2) / df2 < tol); + } + + return boost::report_errors(); +} + + +