diff options
Diffstat (limited to 'include/jsoncons/detail/write_number.hpp')
-rw-r--r-- | include/jsoncons/detail/write_number.hpp | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/include/jsoncons/detail/write_number.hpp b/include/jsoncons/detail/write_number.hpp new file mode 100644 index 0000000..2613467 --- /dev/null +++ b/include/jsoncons/detail/write_number.hpp @@ -0,0 +1,567 @@ +// Copyright 2013 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DETAIL_WRITE_NUMBER_HPP +#define JSONCONS_DETAIL_WRITE_NUMBER_HPP + +#include <stdexcept> +#include <string> +#include <cmath> +#include <locale> +#include <limits> // std::numeric_limits +#include <exception> +#include <stdio.h> // snprintf +#include <jsoncons/config/jsoncons_config.hpp> +#include <jsoncons/json_options.hpp> +#include <jsoncons/detail/grisu3.hpp> +#include <jsoncons/detail/parse_number.hpp> +#include <jsoncons/more_type_traits.hpp> + +namespace jsoncons { +namespace detail { + + inline + char to_hex_character(uint8_t c) + { + return (char)((c < 10) ? ('0' + c) : ('A' - 10 + c)); + } + + // from_integer + + template<class Integer,class Result> + typename std::enable_if<type_traits::is_integer<Integer>::value,std::size_t>::type + from_integer(Integer value, Result& result) + { + using char_type = typename Result::value_type; + + char_type buf[255]; + char_type *p = buf; + const char_type* last = buf+255; + + bool is_negative = value < 0; + + if (value < 0) + { + do + { + *p++ = static_cast<char_type>(48 - (value % 10)); + } + while ((value /= 10) && (p < last)); + } + else + { + + do + { + *p++ = static_cast<char_type>(48 + value % 10); + } + while ((value /= 10) && (p < last)); + } + JSONCONS_ASSERT(p != last); + + std::size_t count = (p - buf); + if (is_negative) + { + result.push_back('-'); + ++count; + } + while (--p >= buf) + { + result.push_back(*p); + } + + return count; + } + + // integer_to_string_hex + + template<class Integer,class Result> + typename std::enable_if<type_traits::is_integer<Integer>::value,std::size_t>::type + integer_to_string_hex(Integer value, Result& result) + { + using char_type = typename Result::value_type; + + char_type buf[255]; + char_type *p = buf; + const char_type* last = buf+255; + + bool is_negative = value < 0; + + if (value < 0) + { + do + { + *p++ = to_hex_character(0-(value % 16)); + } + while ((value /= 16) && (p < last)); + } + else + { + + do + { + *p++ = to_hex_character(value % 16); + } + while ((value /= 16) && (p < last)); + } + JSONCONS_ASSERT(p != last); + + std::size_t count = (p - buf); + if (is_negative) + { + result.push_back('-'); + ++count; + } + while (--p >= buf) + { + result.push_back(*p); + } + + return count; + } + + // write_double + + // fast exponent + template <class Result> + void fill_exponent(int K, Result& result) + { + if (K < 0) + { + result.push_back('-'); + K = -K; + } + else + { + result.push_back('+'); // compatibility with sprintf + } + + if (K < 10) + { + result.push_back('0'); // compatibility with sprintf + result.push_back((char)('0' + K)); + } + else if (K < 100) + { + result.push_back((char)('0' + K / 10)); K %= 10; + result.push_back((char)('0' + K)); + } + else if (K < 1000) + { + result.push_back((char)('0' + K / 100)); K %= 100; + result.push_back((char)('0' + K / 10)); K %= 10; + result.push_back((char)('0' + K)); + } + else + { + jsoncons::detail::from_integer(K, result); + } + } + + template <class Result> + void prettify_string(const char *buffer, std::size_t length, int k, int min_exp, int max_exp, Result& result) + { + int nb_digits = (int)length; + int offset; + /* v = buffer * 10^k + kk is such that 10^(kk-1) <= v < 10^kk + this way kk gives the position of the decimal point. + */ + int kk = nb_digits + k; + + if (nb_digits <= kk && kk <= max_exp) + { + /* the first digits are already in. Add some 0s and call it a day. */ + /* the max_exp is a personal choice. Only 16 digits could possibly be relevant. + * Basically we want to print 12340000000 rather than 1234.0e7 or 1.234e10 */ + for (int i = 0; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + for (int i = nb_digits; i < kk; ++i) + { + result.push_back('0'); + } + result.push_back('.'); + result.push_back('0'); + } + else if (0 < kk && kk <= max_exp) + { + /* comma number. Just insert a '.' at the correct location. */ + for (int i = 0; i < kk; ++i) + { + result.push_back(buffer[i]); + } + result.push_back('.'); + for (int i = kk; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + } + else if (min_exp < kk && kk <= 0) + { + offset = 2 - kk; + + result.push_back('0'); + result.push_back('.'); + for (int i = 2; i < offset; ++i) + result.push_back('0'); + for (int i = 0; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + } + else if (nb_digits == 1) + { + result.push_back(buffer[0]); + result.push_back('e'); + fill_exponent(kk - 1, result); + } + else + { + result.push_back(buffer[0]); + result.push_back('.'); + for (int i = 1; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + result.push_back('e'); + fill_exponent(kk - 1, result); + } + } + + template<class Result> + void dump_buffer(const char *buffer, std::size_t length, char decimal_point, Result& result) + { + const char *sbeg = buffer; + const char *send = sbeg + length; + + if (sbeg != send) + { + bool needs_dot = true; + for (const char* q = sbeg; q < send; ++q) + { + switch (*q) + { + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + result.push_back(*q); + break; + case 'e': + case 'E': + result.push_back('e'); + needs_dot = false; + break; + default: + if (*q == decimal_point) + { + needs_dot = false; + result.push_back('.'); + } + break; + } + } + if (needs_dot) + { + result.push_back('.'); + result.push_back('0'); + needs_dot = true; + } + } + } + + template<class Result> + bool dtoa_scientific(double val, char decimal_point, Result& result) + { + if (val == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + jsoncons::detail::chars_to to_double_; + + char buffer[100]; + int precision = std::numeric_limits<double>::digits10; + int length = snprintf(buffer, sizeof(buffer), "%1.*e", precision, val); + if (length < 0) + { + return false; + } + if (to_double_(buffer, sizeof(buffer)) != val) + { + const int precision2 = std::numeric_limits<double>::max_digits10; + length = snprintf(buffer, sizeof(buffer), "%1.*e", precision2, val); + if (length < 0) + { + return false; + } + } + dump_buffer(buffer, static_cast<std::size_t>(length), decimal_point, result); + return true; + } + + template<class Result> + bool dtoa_general(double val, char decimal_point, Result& result, std::false_type) + { + if (val == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + jsoncons::detail::chars_to to_double_; + + char buffer[100]; + int precision = std::numeric_limits<double>::digits10; + int length = snprintf(buffer, sizeof(buffer), "%1.*g", precision, val); + if (length < 0) + { + return false; + } + if (to_double_(buffer, sizeof(buffer)) != val) + { + const int precision2 = std::numeric_limits<double>::max_digits10; + length = snprintf(buffer, sizeof(buffer), "%1.*g", precision2, val); + if (length < 0) + { + return false; + } + } + dump_buffer(buffer, length, decimal_point, result); + return true; + } + + template<class Result> + bool dtoa_general(double v, char decimal_point, Result& result, std::true_type) + { + if (v == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + int length = 0; + int k; + + char buffer[100]; + + double u = std::signbit(v) ? -v : v; + if (jsoncons::detail::grisu3(u, buffer, &length, &k)) + { + if (std::signbit(v)) + { + result.push_back('-'); + } + // min exp: -4 is consistent with sprintf + // max exp: std::numeric_limits<double>::max_digits10 + jsoncons::detail::prettify_string(buffer, length, k, -4, std::numeric_limits<double>::max_digits10, result); + return true; + } + else + { + return dtoa_general(v, decimal_point, result, std::false_type()); + } + } + + template<class Result> + bool dtoa_fixed(double val, char decimal_point, Result& result, std::false_type) + { + if (val == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + jsoncons::detail::chars_to to_double_; + + char buffer[100]; + int precision = std::numeric_limits<double>::digits10; + int length = snprintf(buffer, sizeof(buffer), "%1.*f", precision, val); + if (length < 0) + { + return false; + } + if (to_double_(buffer, sizeof(buffer)) != val) + { + const int precision2 = std::numeric_limits<double>::max_digits10; + length = snprintf(buffer, sizeof(buffer), "%1.*f", precision2, val); + if (length < 0) + { + return false; + } + } + dump_buffer(buffer, length, decimal_point, result); + return true; + } + + template<class Result> + bool dtoa_fixed(double v, char decimal_point, Result& result, std::true_type) + { + if (v == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + int length = 0; + int k; + + char buffer[100]; + + double u = std::signbit(v) ? -v : v; + if (jsoncons::detail::grisu3(u, buffer, &length, &k)) + { + if (std::signbit(v)) + { + result.push_back('-'); + } + jsoncons::detail::prettify_string(buffer, length, k, std::numeric_limits<int>::lowest(), (std::numeric_limits<int>::max)(), result); + return true; + } + else + { + return dtoa_fixed(v, decimal_point, result, std::false_type()); + } + } + + template<class Result> + bool dtoa_fixed(double v, char decimal_point, Result& result) + { + return dtoa_fixed(v, decimal_point, result, std::integral_constant<bool, std::numeric_limits<double>::is_iec559>()); + } + + template<class Result> + bool dtoa_general(double v, char decimal_point, Result& result) + { + return dtoa_general(v, decimal_point, result, std::integral_constant<bool, std::numeric_limits<double>::is_iec559>()); + } + + class write_double + { + private: + chars_to to_double_; + float_chars_format float_format_; + int precision_; + char decimal_point_; + public: + write_double(float_chars_format float_format, int precision) + : float_format_(float_format), precision_(precision), decimal_point_('.') + { + #if !defined(JSONCONS_NO_LOCALECONV) + struct lconv *lc = localeconv(); + if (lc != nullptr && lc->decimal_point[0] != 0) + { + decimal_point_ = lc->decimal_point[0]; + } + #endif + } + write_double(const write_double&) = default; + + write_double& operator=(const write_double&) = default; + + template<class Result> + std::size_t operator()(double val, Result& result) + { + std::size_t count = 0; + + char number_buffer[200]; + int length = 0; + + switch (float_format_) + { + case float_chars_format::fixed: + { + if (precision_ > 0) + { + length = snprintf(number_buffer, sizeof(number_buffer), "%1.*f", precision_, val); + if (length < 0) + { + JSONCONS_THROW(json_runtime_error<std::invalid_argument>("write_double failed.")); + } + dump_buffer(number_buffer, length, decimal_point_, result); + } + else + { + if (!dtoa_fixed(val, decimal_point_, result)) + { + JSONCONS_THROW(json_runtime_error<std::invalid_argument>("write_double failed.")); + } + } + } + break; + case float_chars_format::scientific: + { + if (precision_ > 0) + { + length = snprintf(number_buffer, sizeof(number_buffer), "%1.*e", precision_, val); + if (length < 0) + { + JSONCONS_THROW(json_runtime_error<std::invalid_argument>("write_double failed.")); + } + dump_buffer(number_buffer, length, decimal_point_, result); + } + else + { + if (!dtoa_scientific(val, decimal_point_, result)) + { + JSONCONS_THROW(json_runtime_error<std::invalid_argument>("write_double failed.")); + } + } + } + break; + case float_chars_format::general: + { + if (precision_ > 0) + { + length = snprintf(number_buffer, sizeof(number_buffer), "%1.*g", precision_, val); + if (length < 0) + { + JSONCONS_THROW(json_runtime_error<std::invalid_argument>("write_double failed.")); + } + dump_buffer(number_buffer, length, decimal_point_, result); + } + else + { + if (!dtoa_general(val, decimal_point_, result)) + { + JSONCONS_THROW(json_runtime_error<std::invalid_argument>("write_double failed.")); + } + } + break; + } + default: + JSONCONS_THROW(json_runtime_error<std::invalid_argument>("write_double failed.")); + break; + } + return count; + } + }; + +} // namespace detail +} // namespace jsoncons + +#endif |