Skip to content

Commit

Permalink
Fix Issue nlohmann#186 - add overload wrappers for strto(f|d|ld)
Browse files Browse the repository at this point in the history
  • Loading branch information
Trevor Welsby committed Jan 24, 2016
1 parent 9de14a4 commit a1c6f16
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 28 deletions.
85 changes: 72 additions & 13 deletions src/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -7289,6 +7292,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
Expand All @@ -7306,13 +7366,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
Expand Down Expand Up @@ -7546,21 +7605,22 @@ 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");
}

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;
Expand All @@ -7570,7 +7630,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;
}
Expand Down
85 changes: 72 additions & 13 deletions src/json.hpp.re2c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -7228,21 +7287,22 @@ 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");
}

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;
Expand All @@ -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;
}
Expand Down
48 changes: 46 additions & 2 deletions test/unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}

0 comments on commit a1c6f16

Please sign in to comment.