json/include/nlohmann/detail/output/denumerizer.hpp
KeXu001 26cdbe0564
Fix workflows cppcheck (#2)
* change parser template args order

* amalgamate for previous commit

* add newlines at end of file

* incremental changes

* incremental changes

* incremental changes

* incremental changes

* incremental changes (code style)

* incremental changes

* incremental changes amalgamate

* incremental changes (code style)

* add missing include guards

* incremental changes (code style)

* incremental changes (code style)

* incremental changes (amalgamate)

* incremental changes

* incremental changes

* code style change

* clean up includes

* try using vector_of_uint8_t

* add missing include

* add missing include

* add missing includes

* add comment
2021-12-08 01:17:17 -05:00

286 lines
11 KiB
C++

#pragma once
#include <algorithm> // reverse, remove, fill, find, none_of
#include <array> // array
#include <clocale> // localeconv, lconv
#include <cmath> // labs, isfinite, isnan, signbit
#include <cstddef> // size_t, ptrdiff_t
#include <cstdio> // snprintf
#include <limits> // numeric_limits
#include <string> // char_traits
#include <nlohmann/detail/conversions/to_chars.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
namespace nlohmann
{
namespace detail
{
struct denumerizer
{
using number_buffer_t = std::array<char, 64>;
/*!
@brief count digits
Count the number of decimal (base 10) digits for an input unsigned integer.
@param[in] x unsigned integer number to count its digits
@return number of decimal digits
*/
template <typename number_unsigned_t>
static unsigned int count_digits(number_unsigned_t x) noexcept
{
unsigned int n_digits = 1;
for (;;)
{
if (x < 10)
{
return n_digits;
}
if (x < 100)
{
return n_digits + 1;
}
if (x < 1000)
{
return n_digits + 2;
}
if (x < 10000)
{
return n_digits + 3;
}
x = static_cast<number_unsigned_t>(x / 10000u);
n_digits += 4;
}
}
/*
* Overload to make the compiler happy while it is instantiating
* dump_integer for number_unsigned_t.
* Must never be called.
*/
template <typename number_unsigned_t,
detail::enable_if_t<
std::is_unsigned<number_unsigned_t>::value, int> = 0>
static number_unsigned_t remove_sign(number_unsigned_t x) noexcept
{
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return x; // LCOV_EXCL_LINE
}
/*
* Helper function for dump_integer
*
* This function takes a negative signed integer and returns its absolute
* value as unsigned integer. The plus/minus shuffling is necessary as we can
* not directly remove the sign of an arbitrary signed integer as the
* absolute values of INT_MIN and INT_MAX are usually not the same. See
* #1708 for details.
*/
template <typename number_integer_t,
detail::enable_if_t<
std::is_signed<number_integer_t>::value, int> = 0,
typename number_unsigned_t =
typename std::make_unsigned<number_integer_t>::type>
static number_unsigned_t remove_sign(number_integer_t x) noexcept
{
JSON_ASSERT(x < 0 && x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
}
/*!
@brief dump an integer
Dump a given integer to output stream @a o.
@param[in] x integer number (signed or unsigned) to dump
*/
template <typename OutputAdapterType, typename number_integral_t,
typename number_unsigned_t = typename std::make_unsigned<
number_integral_t>::type>
static void dump_integer(OutputAdapterType& o, number_integral_t x)
{
static constexpr std::array<std::array<char, 2>, 100> digits_to_99
{
{
{{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}},
{{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}},
{{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}},
{{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}},
{{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}},
{{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}},
{{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}},
{{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}},
{{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}},
{{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}},
}
};
// special case for "0"
if (x == 0)
{
o->write_character('0');
return;
}
number_buffer_t number_buffer{{}};
// use a pointer to fill the buffer
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
const bool is_negative = std::is_signed<number_integral_t>::value && !(x >= 0); // see issue #755
number_unsigned_t abs_value;
unsigned int n_chars{};
if (is_negative)
{
*buffer_ptr = '-';
abs_value = remove_sign(static_cast<number_integral_t>(x));
// account one more byte for the minus sign
n_chars = 1 + count_digits(abs_value);
}
else
{
abs_value = static_cast<number_unsigned_t>(x);
n_chars = count_digits(abs_value);
}
// spare 1 byte for '\0'
JSON_ASSERT(n_chars < number_buffer.size() - 1);
// jump to the end to generate the string from backward
// so we later avoid reversing the result
buffer_ptr += n_chars;
// Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
// See: https://www.youtube.com/watch?v=o4-CwDo2zpg
while (abs_value >= 100)
{
const auto digits_index = static_cast<unsigned>((abs_value % 100));
abs_value /= 100;
*(--buffer_ptr) = digits_to_99[digits_index][1];
*(--buffer_ptr) = digits_to_99[digits_index][0];
}
if (abs_value >= 10)
{
const auto digits_index = static_cast<unsigned>(abs_value);
*(--buffer_ptr) = digits_to_99[digits_index][1];
*(--buffer_ptr) = digits_to_99[digits_index][0];
}
else
{
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
}
o->write_characters(number_buffer.data(), n_chars);
}
/*!
@brief dump a floating-point number
Dump a given floating-point number to output stream @a o.
@param[in] x floating-point number to dump
*/
template <typename OutputAdapterType, typename number_float_t,
detail::enable_if_t<
std::is_floating_point<number_float_t>::value, int> = 0>
static void dump_float(OutputAdapterType& o, number_float_t x)
{
// NaN / inf
if (!std::isfinite(x))
{
o->write_characters("null", 4);
return;
}
// If number_float_t is an IEEE-754 single or double precision number,
// use the Grisu2 algorithm to produce short numbers which are
// guaranteed to round-trip, using strtof and strtod, resp.
//
// NB: The test below works if <long double> == <double>.
static constexpr bool is_ieee_single_or_double
= (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) ||
(std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024);
dump_float(o, x, std::integral_constant<bool, is_ieee_single_or_double>());
}
template <typename OutputAdapterType, typename number_float_t,
detail::enable_if_t<
std::is_floating_point<number_float_t>::value, int> = 0>
static void dump_float(OutputAdapterType& o, number_float_t x, std::true_type /*is_ieee_single_or_double*/)
{
number_buffer_t number_buffer{{}};
auto* begin = number_buffer.data();
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
o->write_characters(begin, static_cast<size_t>(end - begin));
}
template <typename OutputAdapterType, typename number_float_t,
detail::enable_if_t<
std::is_floating_point<number_float_t>::value, int> = 0>
static void dump_float(OutputAdapterType& o, number_float_t x, std::false_type /*is_ieee_single_or_double*/)
{
number_buffer_t number_buffer{{}};
const std::lconv* loc(std::localeconv());
const char thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)));
const char decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)));
// get number of digits for a float -> text -> float round-trip
static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
// the actual conversion
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x);
// negative value indicates an error
JSON_ASSERT(len > 0);
// check if buffer was large enough
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
// erase thousands separator
if (thousands_sep != '\0')
{
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
std::fill(end, number_buffer.end(), '\0');
JSON_ASSERT((end - number_buffer.begin()) <= len);
len = (end - number_buffer.begin());
}
// convert decimal point to '.'
if (decimal_point != '\0' && decimal_point != '.')
{
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
if (dec_pos != number_buffer.end())
{
*dec_pos = '.';
}
}
o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));
// determine if need to append ".0"
const bool value_is_int_like =
std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
[](char c)
{
return c == '.' || c == 'e';
});
if (value_is_int_like)
{
o->write_characters(".0", 2);
}
}
};
} // namespace detail
} // namespace nlohmann