Fix Issue #186 - add overload wrappers for strto(f|d|ld)

This commit is contained in:
Trevor Welsby 2016-01-24 17:00:11 +10:00
parent 9de14a4861
commit a1c6f16bd3
3 changed files with 190 additions and 28 deletions

View File

@ -5539,10 +5539,13 @@ class basic_json
case value_t::number_float:
{
// 15 digits of precision allows round-trip IEEE 754
// string->double->string; to be safe, we read this value from
// std::numeric_limits<number_float_t>::digits10
o << std::setprecision(std::numeric_limits<number_float_t>::digits10) << m_value.number_float;
// If the number is an integer then output as a fixed with with precision 1
// to output "0.0", "1.0" etc as expected for some round trip tests otherwise
// 15 digits of precision allows round-trip IEEE 754 string->double->string;
// 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 << std::defaultfloat << std::setprecision(std::numeric_limits<double>::digits10);
o << m_value.number_float;
return;
}
@ -7289,6 +7292,63 @@ basic_json_parser_64:
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
@ -7306,13 +7366,12 @@ basic_json_parser_64:
@throw std::range_error if passed value is out of range
*/
long double get_number() const
number_float_t get_number() const
{
// conversion
typename string_t::value_type* endptr;
assert(m_start != nullptr);
const auto float_val = std::strtold(reinterpret_cast<typename string_t::const_pointer>(m_start),
&endptr);
number_float_t float_val = str_to_float_t(static_cast<number_float_t*>(nullptr), &endptr);
// return float_val if the whole number was translated and NAN
// otherwise
@ -7546,11 +7605,11 @@ basic_json_parser_64:
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
// completely
if (std::isnan(float_val))
if (std::isnan(result.m_value.number_float))
{
throw std::invalid_argument(std::string("parse error - ") +
m_lexer.get_token() + " is not a number");
@ -7558,9 +7617,10 @@ basic_json_parser_64:
get_token();
// check if conversion loses precision
const auto int_val = static_cast<number_integer_t>(float_val);
if (approx(float_val, static_cast<long double>(int_val)))
// check if conversion loses precision (special case -0.0 always loses precision)
const auto int_val = static_cast<number_integer_t>(result.m_value.number_float);
if (approx(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
result.m_type = value_t::number_integer;
@ -7570,7 +7630,6 @@ basic_json_parser_64:
{
// we would lose precision -> return float
result.m_type = value_t::number_float;
result.m_value = static_cast<number_float_t>(float_val);
}
break;
}

View File

@ -5539,10 +5539,13 @@ class basic_json
case value_t::number_float:
{
// 15 digits of precision allows round-trip IEEE 754
// string->double->string; to be safe, we read this value from
// std::numeric_limits<number_float_t>::digits10
o << std::setprecision(std::numeric_limits<number_float_t>::digits10) << m_value.number_float;
// If the number is an integer then output as a fixed with with precision 1
// to output "0.0", "1.0" etc as expected for some round trip tests otherwise
// 15 digits of precision allows round-trip IEEE 754 string->double->string;
// 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 << std::defaultfloat << std::setprecision(std::numeric_limits<double>::digits10);
o << m_value.number_float;
return;
}
@ -6971,6 +6974,63 @@ class basic_json
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
@ -6988,13 +7048,12 @@ class basic_json
@throw std::range_error if passed value is out of range
*/
long double get_number() const
number_float_t get_number() const
{
// conversion
typename string_t::value_type* endptr;
assert(m_start != nullptr);
const auto float_val = std::strtold(reinterpret_cast<typename string_t::const_pointer>(m_start),
&endptr);
number_float_t float_val = str_to_float_t(static_cast<number_float_t*>(nullptr), &endptr);
// return float_val if the whole number was translated and NAN
// otherwise
@ -7228,11 +7287,11 @@ class basic_json
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
// completely
if (std::isnan(float_val))
if (std::isnan(result.m_value.number_float))
{
throw std::invalid_argument(std::string("parse error - ") +
m_lexer.get_token() + " is not a number");
@ -7240,9 +7299,10 @@ class basic_json
get_token();
// check if conversion loses precision
const auto int_val = static_cast<number_integer_t>(float_val);
if (approx(float_val, static_cast<long double>(int_val)))
// check if conversion loses precision (special case -0.0 always loses precision)
const auto int_val = static_cast<number_integer_t>(result.m_value.number_float);
if (approx(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
result.m_type = value_t::number_integer;
@ -7252,7 +7312,6 @@ class basic_json
{
// we would lose precision -> return float
result.m_type = value_t::number_float;
result.m_value = static_cast<number_float_t>(float_val);
}
break;
}

View File

@ -11085,7 +11085,7 @@ TEST_CASE("compliance tests from nativejson-benchmark")
//"test/json_roundtrip/roundtrip18.json",
//"test/json_roundtrip/roundtrip19.json",
//"test/json_roundtrip/roundtrip20.json",
//"test/json_roundtrip/roundtrip21.json",
"test/json_roundtrip/roundtrip21.json",
"test/json_roundtrip/roundtrip22.json",
"test/json_roundtrip/roundtrip23.json",
//"test/json_roundtrip/roundtrip24.json",
@ -11402,7 +11402,8 @@ TEST_CASE("regression tests")
SECTION("issue #89 - nonstandard integer 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;
// we need to cast to int to compile with Catch - the value is int32_t
CHECK(static_cast<int>(j["int_1"]) == 1);
@ -11504,4 +11505,47 @@ TEST_CASE("regression tests")
{
CHECK(json::parse("\"\\ud80c\\udc60abc\"").get<json::string_t>() == u8"\U00013060abc");
}
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);
j = json::parse("1.00000000000000011102230246251565404236316680908203126");
CHECK(j.get<double>() == 1.00000000000000022);
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);
}
}