Skip to content

Commit

Permalink
🔀 merge #378 (for #362 and #454)
Browse files Browse the repository at this point in the history
  • Loading branch information
nlohmann committed Feb 16, 2017
2 parents 057b1e6 + 9490610 commit 6408402
Show file tree
Hide file tree
Showing 3 changed files with 346 additions and 81 deletions.
195 changes: 159 additions & 36 deletions src/json.hpp
Expand Up @@ -2646,14 +2646,6 @@ class basic_json
string_t dump(const int indent = -1) const
{
std::stringstream ss;
// fix locale problems
ss.imbue(std::locale::classic());

// 6, 15 or 16 digits of precision allows round-trip IEEE 754
// string->float->string, string->double->string or string->long
// double->string; to be safe, we read this value from
// std::numeric_limits<number_float_t>::digits10
ss.precision(std::numeric_limits<double>::digits10);

if (indent >= 0)
{
Expand Down Expand Up @@ -6214,10 +6206,6 @@ class basic_json
`std::setw(4)` on @a o sets the indentation level to `4` and the
serialization result is the same as calling `dump(4)`.
@note During serialization, the locale and the precision of the output
stream @a o are changed. The original values are restored when the
function returns.
@param[in,out] o stream to serialize to
@param[in] j JSON value to serialize
Expand All @@ -6239,22 +6227,9 @@ class basic_json
// reset width to 0 for subsequent calls to this stream
o.width(0);

// fix locale problems
const auto old_locale = o.imbue(std::locale::classic());
// set precision

// 6, 15 or 16 digits of precision allows round-trip IEEE 754
// string->float->string, string->double->string or string->long
// double->string; to be safe, we read this value from
// std::numeric_limits<number_float_t>::digits10
const auto old_precision = o.precision(std::numeric_limits<double>::digits10);

// do the actual serialization
j.dump(o, pretty_print, static_cast<unsigned int>(indentation));

// reset locale and precision
o.imbue(old_locale);
o.precision(old_precision);
return o;
}

Expand Down Expand Up @@ -8244,6 +8219,162 @@ class basic_json
return result;
}


/*!
@brief locale-independent serialization for built-in arithmetic types
*/
struct numtostr
{
public:
template<typename T>
numtostr(T value)
{
x_write(value, std::is_integral<T>());
}

operator const char* () const
{
return m_buf.data();
}

const char* c_str() const
{
return m_buf.data();
}

private:
static constexpr size_t s_capacity = 30;
std::array < char, s_capacity + 2 > m_buf{{}}; // +2 for leading '-'
// and trailing '\0'
template<typename T>
void x_write(T x, std::true_type)
{
static_assert(std::numeric_limits<T>::digits10 <= s_capacity, "");

const bool is_neg = x < 0;
size_t i = 0;

while (x and i < s_capacity)
{
const auto digit = std::labs(static_cast<long>(x % 10));
m_buf[i++] = static_cast<char>('0' + digit);
x /= 10;
}

assert(i < s_capacity);

if (i == 0)
{
m_buf[i++] = '0';
}

if (is_neg)
{
m_buf[i++] = '-';
}

std::reverse(m_buf.begin(), m_buf.begin() + i);
}

template<typename T>
void x_write(T x, std::false_type)
{
if (x == 0)
{
std::strcpy(m_buf.data(),
std::signbit(x) ? "-0.0" : "0.0");
return;
}

static constexpr auto d =
std::numeric_limits<number_float_t>::digits10;
static_assert(d == 6 or d == 15 or d == 16 or d == 17, "");

static constexpr auto fmt = d == 6 ? "%.7g"
: d == 15 ? "%.16g"
: d == 16 ? "%.17g"
: d == 17 ? "%.18g"
: "%.19g";
// I'm not sure why we need to +1 the precision,
// but without it there's a unit-test that fails
// that asserts precision of the output

snprintf(m_buf.data(), m_buf.size(), fmt, x);

#if 0
// C locales and C++ locales are similar but
// different.
//
// If working with C++ streams we'd've used
// these, but for C formatting functions we
// have to use C locales (setlocale / localeconv),
// rather than C++ locales (std::locale installed
// by std::locale::global()).
const std::locale loc;

const char thousands_sep =
std::use_facet< std::numpunct<char>>(
loc).thousands_sep();

const char decimal_point =
std::use_facet< std::numpunct<char>>(
loc).decimal_point();
#else
const auto loc = localeconv();
assert(loc != nullptr);
const char thousands_sep = !loc->thousands_sep ? '\0'
: loc->thousands_sep[0];

const char decimal_point = !loc->decimal_point ? '\0'
: loc->decimal_point[0];
#endif

// erase thousands separator
if (thousands_sep)
{
auto end = std::remove(m_buf.begin(),
m_buf.end(),
thousands_sep);

std::fill(end, m_buf.end(), '\0');
}

// convert decimal point to '.'
if (decimal_point and decimal_point != '.')
{
for (auto& c : m_buf)
{
if (c == decimal_point)
{
c = '.';
break;
}
}
}

// determine if need to apperd ".0"
auto data_end = m_buf.begin() + strlen(m_buf.data());

const bool value_is_int_like =
std::find_if(m_buf.begin(), data_end,
[](const char c)
{
return c == '.'
or c == 'e'
or c == 'E';
})
== data_end;

assert(data_end + 2 < m_buf.end());
if (value_is_int_like)
{
strcat(m_buf.data(), ".0");
}
}
};



/*!
@brief internal implementation of the serialization function
Expand Down Expand Up @@ -8363,27 +8494,19 @@ class basic_json

case value_t::number_integer:
{
o << m_value.number_integer;
o << numtostr(m_value.number_integer).c_str();
return;
}

case value_t::number_unsigned:
{
o << m_value.number_unsigned;
o << numtostr(m_value.number_unsigned).c_str();
return;
}

case value_t::number_float:
{
if (m_value.number_float == 0)
{
// special case for zero to get "0.0"/"-0.0"
o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
}
else
{
o << m_value.number_float;
}
o << numtostr(m_value.number_float).c_str();
return;
}

Expand Down

0 comments on commit 6408402

Please sign in to comment.