From 17dd295a1d6b78387bd6e9df4ff6558b6fb038a9 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Mon, 17 Dec 2012 19:29:44 +0100 Subject: [PATCH] Implement scientific notation for double-to-string Also fixes tests for 1e5 expecting fixed precision rather than scientific notation (stringstream gives scientific notation indeed) The only still failing test now is the one having less than 16 significant digits of precision, due to the boost bug: https://svn.boost.org/trac/boost/ticket/7785 --- include/mapnik/util/conversions.hpp | 75 +++++++++++++++- tests/cpp_tests/conversions_test.cpp | 123 +++++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 tests/cpp_tests/conversions_test.cpp diff --git a/include/mapnik/util/conversions.hpp b/include/mapnik/util/conversions.hpp index ad15627ae6..c32afc317f 100644 --- a/include/mapnik/util/conversions.hpp +++ b/include/mapnik/util/conversions.hpp @@ -38,6 +38,7 @@ #if BOOST_VERSION >= 104500 #include #include +#include // trunc to avoid needing C++11 #else #include #endif @@ -67,12 +68,80 @@ template struct double_policy : boost::spirit::karma::real_policies { typedef boost::spirit::karma::real_policies base_type; - static int floatfield(T n) { return base_type::fmtflags::fixed; } - static unsigned precision(T n) { return 15 - trunc(log10(n)); } + + static int floatfield(T n) { + using namespace boost::spirit; // for traits + + if (traits::test_zero(n)) + return base_type::fmtflags::fixed; + + T abs_n = traits::get_absolute_value(n); + return (abs_n >= 1e16 || abs_n < 1e-4) + ? base_type::fmtflags::scientific : base_type::fmtflags::fixed; + } + + static unsigned precision(T n) { + if ( n == 0.0 ) return 0; + using namespace boost::spirit; // for traits + return static_cast(15 - boost::math::trunc(log10(traits::get_absolute_value(n)))); + } + template static bool dot(OutputIterator& sink, T n, unsigned precision) { - return n ? *sink = '.', true : false; + if (n == 0.0) return true; // avoid trailing zeroes + return base_type::dot(sink, n, precision); + } + + template + static bool fraction_part (OutputIterator& sink, T n + , unsigned precision_, unsigned precision) + { + // NOTE: copied from karma only to avoid trailing zeroes + // (maybe a bug ?) + + // allow for ADL to find the correct overload for floor and log10 + using namespace std; + + using namespace boost::spirit; // for traits + using namespace boost::spirit::karma; // for char_inserter + using namespace boost; // for remove_const + + if ( traits::test_zero(n) ) return true; // this part added to karma + + // The following is equivalent to: + // generate(sink, right_align(precision, '0')[ulong], n); + // but it's spelled out to avoid inter-modular dependencies. + + typename remove_const::type digits = + (traits::test_zero(n) ? 0 : floor(log10(n))) + 1; + bool r = true; + for (/**/; r && digits < precision_; digits = digits + 1) + r = char_inserter<>::call(sink, '0'); + if (precision && r) + r = int_inserter<10>::call(sink, n); + return r; } + + template + static bool exponent (OutputIterator& sink, long n) + { + // NOTE: copied from karma to force sign in exponent + const bool force_sign = true; + + using namespace boost::spirit; // for traits + using namespace boost::spirit::karma; // for char_inserter, sign_inserter + + long abs_n = traits::get_absolute_value(n); + bool r = char_inserter::call(sink, 'e') && + sign_inserter::call(sink, traits::test_zero(n) + , traits::test_negative(n), force_sign); + + // the C99 Standard requires at least two digits in the exponent + if (r && abs_n < 10) + r = char_inserter::call(sink, '0'); + return r && int_inserter<10>::call(sink, abs_n); + } + }; diff --git a/tests/cpp_tests/conversions_test.cpp b/tests/cpp_tests/conversions_test.cpp new file mode 100644 index 0000000000..362e090673 --- /dev/null +++ b/tests/cpp_tests/conversions_test.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include + +int main( int, char*[] ) +{ + using mapnik::util::to_string; + + try + { + std::string out; + + // Test double + to_string(out, double(0)); + BOOST_TEST_EQ( out, "0" ); + out.clear(); + + to_string(out, double(1)); + BOOST_TEST_EQ( out, "1" ); + out.clear(); + + to_string(out, double(-1)); + BOOST_TEST_EQ( out, "-1" ); + out.clear(); + + to_string(out, double(0.1)); + BOOST_TEST_EQ( out, "0.1" ); + out.clear(); + + to_string(out, double(-0.1)); + BOOST_TEST_EQ( out, "-0.1" ); + out.clear(); + + to_string(out, double(0.123)); + BOOST_TEST_EQ( out, "0.123" ); + out.clear(); + + to_string(out, double(-0.123)); + BOOST_TEST_EQ( out, "-0.123" ); + out.clear(); + + to_string(out, double(1e-06)); + BOOST_TEST_EQ( out, "1e-06" ); + out.clear(); + + to_string(out, double(-1e-06)); + BOOST_TEST_EQ( out, "-1e-06" ); + out.clear(); + + to_string(out, double(1e-05)); + BOOST_TEST_EQ( out, "1e-05" ); + out.clear(); + + to_string(out, double(-1e-05)); + BOOST_TEST_EQ( out, "-1e-05" ); + out.clear(); + + to_string(out, double(0.0001)); + BOOST_TEST_EQ( out, "0.0001" ); + out.clear(); + + to_string(out, double(-0.0001)); + BOOST_TEST_EQ( out, "-0.0001" ); + out.clear(); + + to_string(out, double(0.0001234567890123456)); + BOOST_TEST_EQ( out, "0.0001234567890123456" ); + out.clear(); + + to_string(out, double(-0.0001234567890123456)); + BOOST_TEST_EQ( out, "-0.0001234567890123456" ); + out.clear(); + + to_string(out, double(1000000000000000)); + BOOST_TEST_EQ( out, "1000000000000000" ); + out.clear(); + + to_string(out, double(-1000000000000000)); + BOOST_TEST_EQ( out, "-1000000000000000" ); + out.clear(); + + to_string(out, double(100000000000000.1)); + BOOST_TEST_EQ( out, "100000000000000.1" ); + out.clear(); + + to_string(out, double(1.00001)); + BOOST_TEST_EQ( out, "1.00001" ); + out.clear(); + + to_string(out, double(1234000000000000)); + BOOST_TEST_EQ( out, "1234000000000000" ); + out.clear(); + + to_string(out, double(1.234e+16)); + BOOST_TEST_EQ( out, "1.234e+16" ); + out.clear(); + + to_string(out, double(-1.234e+16)); + BOOST_TEST_EQ( out, "-1.234e+16" ); + out.clear(); + + // Test int + + to_string(out, int(2)); + BOOST_TEST_EQ( out, "2" ); + out.clear(); + } + catch (std::exception const & ex) + { + std::clog << "C++ type conversions problem: " << ex.what() << "\n"; + BOOST_TEST(false); + } + + if (!::boost::detail::test_errors()) { + std::clog << "C++ type conversions: \x1b[1;32m✓ \x1b[0m\n"; +#if BOOST_VERSION >= 104600 + ::boost::detail::report_errors_remind().called_report_errors_function = true; +#endif + } else { + return ::boost::report_errors(); + } +}