Skip to content

Commit

Permalink
Implement scientific notation for double-to-string
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Sandro Santilli committed Dec 17, 2012
1 parent 19522ac commit 17dd295
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 3 deletions.
75 changes: 72 additions & 3 deletions include/mapnik/util/conversions.hpp
Expand Up @@ -38,6 +38,7 @@
#if BOOST_VERSION >= 104500
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/math/special_functions/trunc.hpp> // trunc to avoid needing C++11
#else
#include <boost/lexical_cast.hpp>
#endif
Expand Down Expand Up @@ -67,12 +68,80 @@ template <typename T>
struct double_policy : boost::spirit::karma::real_policies<T>
{
typedef boost::spirit::karma::real_policies<T> 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<unsigned>(15 - boost::math::trunc(log10(traits::get_absolute_value(n))));
}

template <typename OutputIterator>
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 <typename OutputIterator>
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<T>::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 <typename CharEncoding, typename Tag, typename OutputIterator>
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<CharEncoding, Tag>::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<CharEncoding, Tag>::call(sink, '0');
return r && int_inserter<10>::call(sink, abs_n);
}

};


Expand Down
123 changes: 123 additions & 0 deletions tests/cpp_tests/conversions_test.cpp
@@ -0,0 +1,123 @@
#include <boost/version.hpp>
#include <mapnik/util/conversions.hpp>
#include <boost/detail/lightweight_test.hpp>
#include <iostream>

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();
}
}

0 comments on commit 17dd295

Please sign in to comment.