fix some issues handling infinities and NaNs

fixes #51
This commit is contained in:
Mark Gillard 2020-08-03 09:10:06 +03:00
parent f6450f6ff9
commit 82616e734c
6 changed files with 89 additions and 120 deletions

View File

@ -1037,14 +1037,8 @@ TOML_IMPL_NAMESPACE_START
if (cp && !is_value_terminator(*cp))
set_error_and_return_default("expected value-terminator, saw '"sv, to_sv(*cp), "'"sv);
// control for implementations that don't properly implement std::numeric_limits<double>::quiet_NaN()
// and/or std::numeric_limits<double>::infinity() (e.g. due to -ffast-math and friends)
constexpr uint64_t neg_inf = 0b1111111111110000000000000000000000000000000000000000000000000000ull;
constexpr uint64_t pos_inf = 0b0111111111110000000000000000000000000000000000000000000000000000ull;
constexpr uint64_t qnan = 0b1111111111111000000000000000000000000000000000000000000000000001ull;
double rval;
std::memcpy(&rval, inf ? (negative ? &neg_inf : &pos_inf) : &qnan, sizeof(double));
return rval;
return inf ? (negative ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity())
: std::numeric_limits<double>::quiet_NaN();
}
TOML_PUSH_WARNINGS

View File

@ -398,39 +398,14 @@ TOML_NAMESPACE_START
{
if constexpr (std::is_same_v<value_type, double>)
{
using namespace impl;
static constexpr auto pack = [](auto l, auto r) constexpr noexcept
{
return (static_cast<uint64_t>(unwrap_enum(l)) << 32)
| static_cast<uint64_t>(unwrap_enum(r));
};
switch (pack(impl::fpclassify(lhs.val_), impl::fpclassify(rhs)))
{
case pack(fp_class::pos_inf, fp_class::neg_inf): [[fallthrough]];
case pack(fp_class::pos_inf, fp_class::nan): [[fallthrough]];
case pack(fp_class::neg_inf, fp_class::pos_inf): [[fallthrough]];
case pack(fp_class::neg_inf, fp_class::nan): [[fallthrough]];
case pack(fp_class::nan, fp_class::pos_inf): [[fallthrough]];
case pack(fp_class::nan, fp_class::neg_inf):
return false;
case pack(fp_class::pos_inf, fp_class::pos_inf): [[fallthrough]];
case pack(fp_class::neg_inf, fp_class::neg_inf): [[fallthrough]];
case pack(fp_class::nan, fp_class::nan):
return true;
case pack(fp_class::ok, fp_class::ok):
return lhs.val_ == rhs;
TOML_NO_DEFAULT_CASE;
}
TOML_UNREACHABLE;
const auto lhs_class = impl::fpclassify(lhs.val_);
const auto rhs_class = impl::fpclassify(rhs);
if (lhs_class == impl::fp_class::nan && rhs_class == impl::fp_class::nan)
return true;
if ((lhs_class == impl::fp_class::nan) != (rhs_class == impl::fp_class::nan))
return false;
}
else
return lhs.val_ == rhs;
return lhs.val_ == rhs;
}
TOML_ASYMMETRICAL_EQUALITY_OPS(const value&, value_arg, )

View File

@ -35,9 +35,9 @@ def python_value_to_tomlpp(val):
return 'true' if val else 'false'
elif isinstance(val, float):
if math.isinf(val):
return 'make_infinity({})'.format('' if val >= 0.0 else '-1')
return f'{"-" if val < 0.0 else ""}std::numeric_limits<double>::infinity()'
elif math.isnan(val):
return 'make_nan()'
return 'std::numeric_limits<double>::quiet_NaN()'
else:
return str(val)
elif isinstance(val, int):

View File

@ -744,7 +744,7 @@ TEST_CASE("conformance - iarna/valid")
parsing_should_succeed(FILE_LINE_ARGS, spec_float_10, [](toml::table&& tbl)
{
auto expected = toml::table{{
{ R"(sf1)"sv, make_infinity() },
{ R"(sf1)"sv, std::numeric_limits<double>::infinity() },
}};
REQUIRE(tbl == expected);
});
@ -752,7 +752,7 @@ TEST_CASE("conformance - iarna/valid")
parsing_should_succeed(FILE_LINE_ARGS, spec_float_11, [](toml::table&& tbl)
{
auto expected = toml::table{{
{ R"(sf2)"sv, make_infinity() },
{ R"(sf2)"sv, std::numeric_limits<double>::infinity() },
}};
REQUIRE(tbl == expected);
});
@ -760,7 +760,7 @@ TEST_CASE("conformance - iarna/valid")
parsing_should_succeed(FILE_LINE_ARGS, spec_float_12, [](toml::table&& tbl)
{
auto expected = toml::table{{
{ R"(sf2)"sv, make_infinity(-1) },
{ R"(sf2)"sv, -std::numeric_limits<double>::infinity() },
}};
REQUIRE(tbl == expected);
});
@ -768,7 +768,7 @@ TEST_CASE("conformance - iarna/valid")
parsing_should_succeed(FILE_LINE_ARGS, spec_float_13, [](toml::table&& tbl)
{
auto expected = toml::table{{
{ R"(sf4)"sv, make_nan() },
{ R"(sf4)"sv, std::numeric_limits<double>::quiet_NaN() },
}};
REQUIRE(tbl == expected);
});
@ -776,7 +776,7 @@ TEST_CASE("conformance - iarna/valid")
parsing_should_succeed(FILE_LINE_ARGS, spec_float_14, [](toml::table&& tbl)
{
auto expected = toml::table{{
{ R"(sf5)"sv, make_nan() },
{ R"(sf5)"sv, std::numeric_limits<double>::quiet_NaN() },
}};
REQUIRE(tbl == expected);
});
@ -784,7 +784,7 @@ TEST_CASE("conformance - iarna/valid")
parsing_should_succeed(FILE_LINE_ARGS, spec_float_15, [](toml::table&& tbl)
{
auto expected = toml::table{{
{ R"(sf6)"sv, make_nan() },
{ R"(sf6)"sv, std::numeric_limits<double>::quiet_NaN() },
}};
REQUIRE(tbl == expected);
});

View File

@ -45,26 +45,17 @@ TOML_POP_WARNINGS
while (false)
#endif
[[nodiscard]]
TOML_ATTR(const)
inline double make_infinity(int sign = 1) noexcept
{
constexpr uint64_t pos_inf = 0b0111111111110000000000000000000000000000000000000000000000000000ull;
constexpr uint64_t neg_inf = 0b1111111111110000000000000000000000000000000000000000000000000000ull;
double val;
std::memcpy(&val, sign >= 0 ? &pos_inf : &neg_inf, sizeof(double));
return val;
}
#define CHECK_SYMMETRIC_RELOP(lhs, op, rhs, result) \
CHECK(((lhs) op (rhs)) == (result)); \
CHECK(((rhs) op (lhs)) == (result))
[[nodiscard]]
TOML_ATTR(const)
inline double make_nan() noexcept
{
constexpr uint64_t qnan = 0b1111111111111000000000000000000000000000000000000000000000000001ull;
double val;
std::memcpy(&val, &qnan, sizeof(double));
return val;
}
#define CHECK_SYMMETRIC_EQUAL(lhs, rhs) \
CHECK_SYMMETRIC_RELOP(lhs, ==, rhs, true); \
CHECK_SYMMETRIC_RELOP(lhs, !=, rhs, false)
#define CHECK_SYMMETRIC_INEQUAL(lhs, rhs) \
CHECK_SYMMETRIC_RELOP(lhs, ==, rhs, false); \
CHECK_SYMMETRIC_RELOP(lhs, !=, rhs, true)
// function_view - adapted from here: https://vittorioromeo.info/index/blog/passing_functions_to_functions.html
template <typename Func>
@ -230,21 +221,61 @@ inline bool parse_expected_value(
REQUIRE(tbl == table{ { { "val"sv, expected } } });
REQUIRE(!(tbl != table{ { { "val"sv, expected } } }));
// check the value relops
REQUIRE(*nv.as<value_type>() == expected);
REQUIRE(expected == *nv.as<value_type>());
REQUIRE(!(*nv.as<value_type>() != expected));
REQUIRE(!(expected != *nv.as<value_type>()));
// check the node_view relops
REQUIRE(nv == expected);
REQUIRE(expected == nv);
REQUIRE(!(nv != expected));
REQUIRE(!(expected != nv));
// check value/node relops
CHECK_SYMMETRIC_EQUAL(*nv.as<value_type>(), *nv.as<value_type>());
CHECK_SYMMETRIC_EQUAL(*nv.as<value_type>(), expected);
CHECK_SYMMETRIC_EQUAL(nv, expected);
// make sure source info is correct
REQUIRE(nv.node()->source().begin == begin);
REQUIRE(nv.node()->source().end == end);
CHECK_SYMMETRIC_EQUAL(nv.node()->source().begin, begin);
CHECK_SYMMETRIC_EQUAL(nv.node()->source().end, end);
// check float identities etc
if constexpr (std::is_same_v<value_type, double>)
{
auto& float_node = *nv.as<value_type>();
const auto fpcls = impl::fpclassify(*float_node);
if (fpcls == impl::fp_class::nan)
{
CHECK_SYMMETRIC_EQUAL(float_node, std::numeric_limits<double>::quiet_NaN());
CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::infinity());
CHECK_SYMMETRIC_INEQUAL(float_node, -std::numeric_limits<double>::infinity());
CHECK_SYMMETRIC_INEQUAL(float_node, 1.0);
CHECK_SYMMETRIC_INEQUAL(float_node, 0.0);
CHECK_SYMMETRIC_INEQUAL(float_node, -1.0);
}
else if (fpcls == impl::fp_class::neg_inf || fpcls == impl::fp_class::pos_inf)
{
CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::quiet_NaN());
if (fpcls == impl::fp_class::neg_inf)
{
CHECK_SYMMETRIC_EQUAL(float_node, -std::numeric_limits<double>::infinity());
CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::infinity());
}
else
{
CHECK_SYMMETRIC_EQUAL(float_node, std::numeric_limits<double>::infinity());
CHECK_SYMMETRIC_INEQUAL(float_node, -std::numeric_limits<double>::infinity());
}
CHECK_SYMMETRIC_INEQUAL(float_node, 1.0);
CHECK_SYMMETRIC_INEQUAL(float_node, 0.0);
CHECK_SYMMETRIC_INEQUAL(float_node, -1.0);
}
else
{
CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::quiet_NaN());
CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::infinity());
CHECK_SYMMETRIC_INEQUAL(float_node, -std::numeric_limits<double>::infinity());
CHECK_SYMMETRIC_EQUAL(float_node, *float_node);
if (std::abs(*float_node) <= 1e10)
{
CHECK_SYMMETRIC_INEQUAL(float_node, *float_node + 100.0);
CHECK_SYMMETRIC_INEQUAL(float_node, *float_node - 100.0);
}
CHECK(float_node < std::numeric_limits<double>::infinity());
CHECK(float_node > -std::numeric_limits<double>::infinity());
}
}
// steal the val for round-trip tests
if (!stolen_value)

View File

@ -2838,39 +2838,14 @@ TOML_NAMESPACE_START
{
if constexpr (std::is_same_v<value_type, double>)
{
using namespace impl;
static constexpr auto pack = [](auto l, auto r) constexpr noexcept
{
return (static_cast<uint64_t>(unwrap_enum(l)) << 32)
| static_cast<uint64_t>(unwrap_enum(r));
};
switch (pack(impl::fpclassify(lhs.val_), impl::fpclassify(rhs)))
{
case pack(fp_class::pos_inf, fp_class::neg_inf): [[fallthrough]];
case pack(fp_class::pos_inf, fp_class::nan): [[fallthrough]];
case pack(fp_class::neg_inf, fp_class::pos_inf): [[fallthrough]];
case pack(fp_class::neg_inf, fp_class::nan): [[fallthrough]];
case pack(fp_class::nan, fp_class::pos_inf): [[fallthrough]];
case pack(fp_class::nan, fp_class::neg_inf):
return false;
case pack(fp_class::pos_inf, fp_class::pos_inf): [[fallthrough]];
case pack(fp_class::neg_inf, fp_class::neg_inf): [[fallthrough]];
case pack(fp_class::nan, fp_class::nan):
return true;
case pack(fp_class::ok, fp_class::ok):
return lhs.val_ == rhs;
TOML_NO_DEFAULT_CASE;
}
TOML_UNREACHABLE;
const auto lhs_class = impl::fpclassify(lhs.val_);
const auto rhs_class = impl::fpclassify(rhs);
if (lhs_class == impl::fp_class::nan && rhs_class == impl::fp_class::nan)
return true;
if ((lhs_class == impl::fp_class::nan) != (rhs_class == impl::fp_class::nan))
return false;
}
else
return lhs.val_ == rhs;
return lhs.val_ == rhs;
}
TOML_ASYMMETRICAL_EQUALITY_OPS(const value&, value_arg, )
[[nodiscard]] friend bool operator < (const value& lhs, value_arg rhs) noexcept { return lhs.val_ < rhs; }
@ -9619,14 +9594,8 @@ TOML_IMPL_NAMESPACE_START
if (cp && !is_value_terminator(*cp))
set_error_and_return_default("expected value-terminator, saw '"sv, to_sv(*cp), "'"sv);
// control for implementations that don't properly implement std::numeric_limits<double>::quiet_NaN()
// and/or std::numeric_limits<double>::infinity() (e.g. due to -ffast-math and friends)
constexpr uint64_t neg_inf = 0b1111111111110000000000000000000000000000000000000000000000000000ull;
constexpr uint64_t pos_inf = 0b0111111111110000000000000000000000000000000000000000000000000000ull;
constexpr uint64_t qnan = 0b1111111111111000000000000000000000000000000000000000000000000001ull;
double rval;
std::memcpy(&rval, inf ? (negative ? &neg_inf : &pos_inf) : &qnan, sizeof(double));
return rval;
return inf ? (negative ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity())
: std::numeric_limits<double>::quiet_NaN();
}
TOML_PUSH_WARNINGS