Merge pull request #191 from twelsby/issue186
Fixed Issue #186 - add strto(f|d|ld) overload wrappers, "-0.0" special case and FP trailing zero
This commit is contained in:
commit
ad5d1dabb2
3 changed files with 576 additions and 807 deletions
1243
src/json.hpp
1243
src/json.hpp
File diff suppressed because it is too large
Load diff
|
@ -5604,10 +5604,16 @@ class basic_json
|
||||||
|
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
{
|
{
|
||||||
// 15 digits of precision allows round-trip IEEE 754
|
// If the number is an integer then output as a fixed with with precision 1
|
||||||
// string->double->string; to be safe, we read this value from
|
// to output "0.0", "1.0" etc as expected for some round trip tests otherwise
|
||||||
// std::numeric_limits<number_float_t>::digits10
|
// 15 digits of precision allows round-trip IEEE 754 string->double->string;
|
||||||
o << std::setprecision(std::numeric_limits<number_float_t>::digits10) << m_value.number_float;
|
// to be safe, we read this value from std::numeric_limits<number_float_t>::digits10
|
||||||
|
if (std::fmod(m_value.number_float, 1) == 0) o << std::fixed << std::setprecision(1);
|
||||||
|
else {
|
||||||
|
o.unsetf(std::ios_base::floatfield); // std::defaultfloat not supported in gcc version < 5
|
||||||
|
o << std::setprecision(std::numeric_limits<double>::digits10);
|
||||||
|
}
|
||||||
|
o << m_value.number_float;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7036,6 +7042,63 @@ class basic_json
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse floating point number
|
||||||
|
|
||||||
|
This function (and its overloads) serves to select the most approprate
|
||||||
|
standard floating point number parsing function based on the type
|
||||||
|
supplied via the first parameter. Set this to
|
||||||
|
@a static_cast<number_float_t>(nullptr).
|
||||||
|
|
||||||
|
@param type the @ref number_float_t in use
|
||||||
|
|
||||||
|
@param endptr recieves a pointer to the first character after the number
|
||||||
|
|
||||||
|
@return the floating point number
|
||||||
|
*/
|
||||||
|
long double str_to_float_t(long double* /* type */, char** endptr) const
|
||||||
|
{
|
||||||
|
return std::strtold(reinterpret_cast<typename string_t::const_pointer>(m_start), endptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse floating point number
|
||||||
|
|
||||||
|
This function (and its overloads) serves to select the most approprate
|
||||||
|
standard floating point number parsing function based on the type
|
||||||
|
supplied via the first parameter. Set this to
|
||||||
|
@a static_cast<number_float_t>(nullptr).
|
||||||
|
|
||||||
|
@param type the @ref number_float_t in use
|
||||||
|
|
||||||
|
@param endptr recieves a pointer to the first character after the number
|
||||||
|
|
||||||
|
@return the floating point number
|
||||||
|
*/
|
||||||
|
double str_to_float_t(double* /* type */, char** endptr) const
|
||||||
|
{
|
||||||
|
return std::strtod(reinterpret_cast<typename string_t::const_pointer>(m_start), endptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse floating point number
|
||||||
|
|
||||||
|
This function (and its overloads) serves to select the most approprate
|
||||||
|
standard floating point number parsing function based on the type
|
||||||
|
supplied via the first parameter. Set this to
|
||||||
|
@a static_cast<number_float_t>(nullptr).
|
||||||
|
|
||||||
|
@param type the @ref number_float_t in use
|
||||||
|
|
||||||
|
@param endptr recieves a pointer to the first character after the number
|
||||||
|
|
||||||
|
@return the floating point number
|
||||||
|
*/
|
||||||
|
float str_to_float_t(float* /* type */, char** endptr) const
|
||||||
|
{
|
||||||
|
return std::strtof(reinterpret_cast<typename string_t::const_pointer>(m_start), endptr);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief return number value for number tokens
|
@brief return number value for number tokens
|
||||||
|
|
||||||
|
@ -7053,13 +7116,12 @@ class basic_json
|
||||||
|
|
||||||
@throw std::range_error if passed value is out of range
|
@throw std::range_error if passed value is out of range
|
||||||
*/
|
*/
|
||||||
long double get_number() const
|
number_float_t get_number() const
|
||||||
{
|
{
|
||||||
// conversion
|
// conversion
|
||||||
typename string_t::value_type* endptr;
|
typename string_t::value_type* endptr;
|
||||||
assert(m_start != nullptr);
|
assert(m_start != nullptr);
|
||||||
const auto float_val = std::strtold(reinterpret_cast<typename string_t::const_pointer>(m_start),
|
number_float_t float_val = str_to_float_t(static_cast<number_float_t*>(nullptr), &endptr);
|
||||||
&endptr);
|
|
||||||
|
|
||||||
// return float_val if the whole number was translated and NAN
|
// return float_val if the whole number was translated and NAN
|
||||||
// otherwise
|
// otherwise
|
||||||
|
@ -7293,11 +7355,11 @@ class basic_json
|
||||||
|
|
||||||
case lexer::token_type::value_number:
|
case lexer::token_type::value_number:
|
||||||
{
|
{
|
||||||
auto float_val = m_lexer.get_number();
|
result.m_value = m_lexer.get_number();
|
||||||
|
|
||||||
// NAN is returned if token could not be translated
|
// NAN is returned if token could not be translated
|
||||||
// completely
|
// completely
|
||||||
if (std::isnan(float_val))
|
if (std::isnan(result.m_value.number_float))
|
||||||
{
|
{
|
||||||
throw std::invalid_argument(std::string("parse error - ") +
|
throw std::invalid_argument(std::string("parse error - ") +
|
||||||
m_lexer.get_token() + " is not a number");
|
m_lexer.get_token() + " is not a number");
|
||||||
|
@ -7305,9 +7367,10 @@ class basic_json
|
||||||
|
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
// check if conversion loses precision
|
// check if conversion loses precision (special case -0.0 always loses precision)
|
||||||
const auto int_val = static_cast<number_integer_t>(float_val);
|
const auto int_val = static_cast<number_integer_t>(result.m_value.number_float);
|
||||||
if (float_val == static_cast<long double>(int_val))
|
if (result.m_value.number_float == static_cast<number_float_t>(int_val) &&
|
||||||
|
result.m_value.number_integer != json_value(-0.0f).number_integer)
|
||||||
{
|
{
|
||||||
// we would not lose precision -> return int
|
// we would not lose precision -> return int
|
||||||
result.m_type = value_t::number_integer;
|
result.m_type = value_t::number_integer;
|
||||||
|
@ -7317,7 +7380,6 @@ class basic_json
|
||||||
{
|
{
|
||||||
// we would lose precision -> return float
|
// we would lose precision -> return float
|
||||||
result.m_type = value_t::number_float;
|
result.m_type = value_t::number_float;
|
||||||
result.m_value = static_cast<number_float_t>(float_val);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11090,7 +11090,7 @@ TEST_CASE("compliance tests from nativejson-benchmark")
|
||||||
//"test/json_roundtrip/roundtrip18.json",
|
//"test/json_roundtrip/roundtrip18.json",
|
||||||
//"test/json_roundtrip/roundtrip19.json",
|
//"test/json_roundtrip/roundtrip19.json",
|
||||||
//"test/json_roundtrip/roundtrip20.json",
|
//"test/json_roundtrip/roundtrip20.json",
|
||||||
//"test/json_roundtrip/roundtrip21.json",
|
"test/json_roundtrip/roundtrip21.json",
|
||||||
"test/json_roundtrip/roundtrip22.json",
|
"test/json_roundtrip/roundtrip22.json",
|
||||||
"test/json_roundtrip/roundtrip23.json",
|
"test/json_roundtrip/roundtrip23.json",
|
||||||
//"test/json_roundtrip/roundtrip24.json",
|
//"test/json_roundtrip/roundtrip24.json",
|
||||||
|
@ -11407,7 +11407,8 @@ TEST_CASE("regression tests")
|
||||||
SECTION("issue #89 - nonstandard integer type")
|
SECTION("issue #89 - nonstandard integer type")
|
||||||
{
|
{
|
||||||
// create JSON class with nonstandard integer number type
|
// create JSON class with nonstandard integer number type
|
||||||
nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, float> j;
|
using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, float>;
|
||||||
|
custom_json j;
|
||||||
j["int_1"] = 1;
|
j["int_1"] = 1;
|
||||||
// we need to cast to int to compile with Catch - the value is int32_t
|
// we need to cast to int to compile with Catch - the value is int32_t
|
||||||
CHECK(static_cast<int>(j["int_1"]) == 1);
|
CHECK(static_cast<int>(j["int_1"]) == 1);
|
||||||
|
@ -11557,4 +11558,51 @@ TEST_CASE("regression tests")
|
||||||
// Const access with key as "static constexpr const char *"
|
// Const access with key as "static constexpr const char *"
|
||||||
CHECK(j_const[constexpr_ptr_key] == json(5));
|
CHECK(j_const[constexpr_ptr_key] == json(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("issue #186 miloyip/nativejson-benchmark: floating-point parsing")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
|
||||||
|
j = json::parse("-0.0");
|
||||||
|
CHECK(j.get<double>() == -0.0);
|
||||||
|
|
||||||
|
j = json::parse("2.22507385850720113605740979670913197593481954635164564e-308");
|
||||||
|
CHECK(j.get<double>() == 2.2250738585072009e-308);
|
||||||
|
|
||||||
|
j = json::parse("0.999999999999999944488848768742172978818416595458984374");
|
||||||
|
CHECK(j.get<double>() == 0.99999999999999989);
|
||||||
|
|
||||||
|
// Test fails under GCC/clang due to strtod() error (may originate in libstdc++
|
||||||
|
// but seems to have been fixed in the most current versions - just not on Travis)
|
||||||
|
#if !defined(__clang__) && !defined(__GNUC__) && !defined(__GNUG__)
|
||||||
|
j = json::parse("1.00000000000000011102230246251565404236316680908203126");
|
||||||
|
CHECK(j.get<double>() == 1.00000000000000022);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
j = json::parse("7205759403792793199999e-5");
|
||||||
|
CHECK(j.get<double>() == 72057594037927928.0);
|
||||||
|
|
||||||
|
j = json::parse("922337203685477529599999e-5");
|
||||||
|
CHECK(j.get<double>() == 9223372036854774784.0);
|
||||||
|
|
||||||
|
j = json::parse("1014120480182583464902367222169599999e-5");
|
||||||
|
CHECK(j.get<double>() == 10141204801825834086073718800384.0);
|
||||||
|
|
||||||
|
j = json::parse("5708990770823839207320493820740630171355185151999e-3");
|
||||||
|
CHECK(j.get<double>() == 5708990770823838890407843763683279797179383808.0);
|
||||||
|
|
||||||
|
// create JSON class with nonstandard float number type
|
||||||
|
|
||||||
|
// float
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, float> j_float = 1.23e25f;
|
||||||
|
CHECK(j_float.get<float>() == 1.23e25f);
|
||||||
|
|
||||||
|
// double
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, double> j_double = 1.23e45;
|
||||||
|
CHECK(j_double.get<double>() == 1.23e45);
|
||||||
|
|
||||||
|
// long double
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, long double> j_long_double = 1.23e45L;
|
||||||
|
CHECK(j_long_double.get<long double>() == 1.23e45L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue