Skip to content

Commit

Permalink
Merge pull request #115 from cppalliance/114
Browse files Browse the repository at this point in the history
Fix `from_chars` handling of non-finite 80 and 128-bit values
  • Loading branch information
mborland authored Jan 9, 2024
2 parents 319b7c6 + 6eef3c2 commit b909fb8
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 2 deletions.
20 changes: 20 additions & 0 deletions include/boost/charconv/detail/from_chars_float_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ from_chars_result from_chars_float_impl(const char* first, const char* last, T&
std::int64_t exponent {};

auto r = boost::charconv::detail::parser(first, last, sign, significand, exponent, fmt);
if (r.ec == std::errc::value_too_large)
{
r.ec = std::errc();
value = sign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity();
return r;
}
else if (r.ec == std::errc::not_supported)
{
r.ec = std::errc();
if (significand == 0)
{
value = sign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<long double>::quiet_NaN();
}
else
{
value = sign ? -std::numeric_limits<T>::signaling_NaN() : std::numeric_limits<T>::signaling_NaN();
}

return r;
}
if (r.ec != std::errc())
{
return r;
Expand Down
56 changes: 56 additions & 0 deletions include/boost/charconv/detail/generate_nan.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_GENERATE_NAN_HPP
#define BOOST_GENERATE_NAN_HPP

#include <cstdint>
#include <cstring>

#ifdef BOOST_CHARCONV_HAS_FLOAT128

namespace boost {
namespace charconv {
namespace detail {

struct words
{
#if BOOST_CHARCONV_ENDIAN_LITTLE_BYTE
std::uint64_t lo;
std::uint64_t hi;
#else
std::uint64_t hi;
std::uint64_t lo;
#endif
};

inline __float128 nans BOOST_PREVENT_MACRO_SUBSTITUTION () noexcept
{
words bits;
bits.hi = UINT64_C(0x7FFF400000000000);
bits.lo = UINT64_C(0);

__float128 return_val;
std::memcpy(&return_val, &bits, sizeof(__float128));
return return_val;
}

inline __float128 nanq BOOST_PREVENT_MACRO_SUBSTITUTION () noexcept
{
words bits;
bits.hi = UINT64_C(0x7FFF800000000000);
bits.lo = UINT64_C(0);

__float128 return_val;
std::memcpy(&return_val, &bits, sizeof(__float128));
return return_val;
}

} //namespace detail
} //namespace charconv
} //namespace boost

#endif

#endif //BOOST_GENERATE_NAN_HPP
54 changes: 54 additions & 0 deletions include/boost/charconv/detail/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,60 @@ inline from_chars_result parser(const char* first, const char* last, bool& sign,
sign = false;
}

// Handle non-finite values
// Stl allows for string like "iNf" to return inf
//
// This is nested ifs rather than a big one-liner to ensure that once we hit an invalid character
// or an end of buffer we return the correct value of next
if (*next == 'i' || *next == 'I')
{
++next;
if (*next == 'n' || *next == 'N')
{
++next;
if (*next == 'f' || *next == 'F')
{
significand = 0;
return {next, std::errc::value_too_large};
}
}

return {next, std::errc::invalid_argument};
}
else if (*next == 'n' || *next == 'N')
{
++next;
if (*next == 'a' || *next == 'A')
{
++next;
if (*next == 'n' || *next == 'N')
{
++next;
if (*next == '(')
{
++next;
if (*next == 's' || *next == 'S')
{
significand = 1;
return {next, std::errc::not_supported};
}
else if (*next == 'i' || *next == 'I')
{
significand = 0;
return {next, std::errc::not_supported};
}
}
else
{
significand = 0;
return {next, std::errc::not_supported};
}
}
}

return {next, std::errc::invalid_argument};
}

// Ignore leading zeros (e.g. 00005 or -002.3e+5)
while (*next == '0' && next != last)
{
Expand Down
65 changes: 63 additions & 2 deletions src/from_chars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <boost/charconv/detail/from_chars_float_impl.hpp>
#include <boost/charconv/from_chars.hpp>
#include <boost/charconv/detail/bit_layouts.hpp>
#include <boost/charconv/detail/generate_nan.hpp>
#include <system_error>
#include <string>
#include <cstdlib>
Expand Down Expand Up @@ -97,7 +98,47 @@ boost::charconv::from_chars_result boost::charconv::from_chars(const char* first
#endif

auto r = boost::charconv::detail::parser(first, last, sign, significand, exponent, fmt);
if (r.ec != std::errc())
if (r.ec == std::errc::value_too_large)
{
r.ec = std::errc();

#if BOOST_CHARCONV_HAS_BUILTIN(__builtin_inf)
value = sign ? -static_cast<__float128>(__builtin_inf()) : static_cast<__float128>(__builtin_inf());
#else // Conversion from HUGE_VALL should work
value = sign ? -static_cast<__float128>(HUGE_VALL) : static_cast<__float128>(HUGE_VALL);
#endif

return r;
}
else if (r.ec == std::errc::not_supported)
{
r.ec = std::errc();
if (significand == 0)
{
#if BOOST_CHARCONV_HAS_BUILTIN(__builtin_nanq)
value = sign ? -static_cast<__float128>(__builtin_nanq("")) : static_cast<__float128>(__builtin_nanq(""));
#elif BOOST_CHARCONV_HAS_BUILTIN(__nanq)
value = sign ? -static_cast<__float128>(__nanq("")) : static_cast<__float128>(__nanq(""));
#else
value = boost::charconv::detail::nanq();
value = sign ? -value : value;
#endif
}
else
{
#if BOOST_CHARCONV_HAS_BUILTIN(__builtin_nansq)
value = sign ? -static_cast<__float128>(__builtin_nansq("")) : static_cast<__float128>(__builtin_nansq(""));
#elif BOOST_CHARCONV_HAS_BUILTIN(__nansq)
value = sign ? -static_cast<__float128>(__nansq("")) : static_cast<__float128>(__nansq(""));
#else
value = boost::charconv::detail::nans();
value = sign ? -value : value;
#endif
}

return r;
}
else if (r.ec != std::errc())
{
return r;
}
Expand Down Expand Up @@ -224,7 +265,27 @@ boost::charconv::from_chars_result boost::charconv::from_chars(const char* first
#endif

auto r = boost::charconv::detail::parser(first, last, sign, significand, exponent, fmt);
if (r.ec != std::errc())
if (r.ec == std::errc::value_too_large)
{
r.ec = std::errc();
value = sign ? -std::numeric_limits<long double>::infinity() : std::numeric_limits<long double>::infinity();
return r;
}
else if (r.ec == std::errc::not_supported)
{
r.ec = std::errc();
if (significand == 0)
{
value = sign ? -std::numeric_limits<long double>::quiet_NaN() : std::numeric_limits<long double>::quiet_NaN();
}
else
{
value = sign ? -std::numeric_limits<long double>::signaling_NaN() : std::numeric_limits<long double>::signaling_NaN();
}

return r;
}
else if (r.ec != std::errc())
{
return r;
}
Expand Down
79 changes: 79 additions & 0 deletions test/from_chars_float.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,36 @@ inline void spot_check(T expected_value, const std::string& buffer, boost::charc
spot_value(buffer, expected_value, fmt);
}

template <typename T>
void spot_check_nan(const std::string& buffer, boost::charconv::chars_format fmt = boost::charconv::chars_format::general)
{
T v {};
auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt);
if (!(BOOST_TEST(std::isnan(v)) && BOOST_TEST(r)))
{
std::cerr << "Test failure for: " << buffer << " got: " << v << std::endl; // LCOV_EXCL_LINE
}
}

template <typename T>
void spot_check_inf(const std::string& buffer, boost::charconv::chars_format fmt = boost::charconv::chars_format::general)
{
T v {};
auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt);
if (!(BOOST_TEST(std::isinf(v)) && BOOST_TEST(r)))
{
std::cerr << "Test failure for: " << buffer << " got: " << v << std::endl; // LCOV_EXCL_LINE
}
}

template <typename T>
void spot_check_bad_non_finite(const std::string& buffer, boost::charconv::chars_format fmt)
{
T v = static_cast<T>(5.0L);
auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt);
BOOST_TEST(r.ec == std::errc::invalid_argument);
}

void fc (const std::string& s)
{
char* str_end;
Expand Down Expand Up @@ -1817,5 +1847,54 @@ int main()
spot_check(170.0e-00, "170.0e+00", boost::charconv::chars_format::general);
spot_check(170.0000e-00, "170.0000e+00", boost::charconv::chars_format::general);

// https://github.com/cppalliance/charconv/issues/114
auto fmts = {boost::charconv::chars_format::general, boost::charconv::chars_format::scientific,
boost::charconv::chars_format::fixed ,boost::charconv::chars_format::hex};
for (const auto fmt : fmts)
{
spot_check_nan<float>("nan", fmt);
spot_check_nan<float>("-nan", fmt);
spot_check_nan<double>("nan", fmt);
spot_check_nan<double>("-nan", fmt);
spot_check_nan<long double>("nan", fmt);
spot_check_nan<long double>("-nan", fmt);

spot_check_inf<float>("inf", fmt);
spot_check_inf<float>("-inf", fmt);
spot_check_inf<double>("inf", fmt);
spot_check_inf<double>("-inf", fmt);
spot_check_inf<long double>("inf", fmt);
spot_check_inf<long double>("-inf", fmt);

spot_check_nan<float>("NAN", fmt);
spot_check_nan<float>("-NAN", fmt);
spot_check_nan<double>("NAN", fmt);
spot_check_nan<double>("-NAN", fmt);
spot_check_nan<long double>("NAN", fmt);
spot_check_nan<long double>("-NAN", fmt);

spot_check_inf<float>("INF", fmt);
spot_check_inf<float>("-INF", fmt);
spot_check_inf<double>("INF", fmt);
spot_check_inf<double>("-INF", fmt);
spot_check_inf<long double>("INF", fmt);
spot_check_inf<long double>("-INF", fmt);

spot_check_nan<float>("nan(snan)", fmt);
spot_check_nan<float>("-nan(snan)", fmt);
spot_check_nan<double>("nan(snan)", fmt);
spot_check_nan<double>("-nan(snan)", fmt);
spot_check_nan<long double>("nan(snan)", fmt);
spot_check_nan<long double>("-nan(snan)", fmt);

spot_check_nan<float>("-nan(ind)", fmt);
spot_check_nan<double>("-nan(ind)", fmt);
spot_check_nan<long double>("-nan(ind)", fmt);

spot_check_bad_non_finite<float>("na7", fmt);
spot_check_bad_non_finite<float>("na", fmt);
spot_check_bad_non_finite<float>("in", fmt);
}

return boost::report_errors();
}
Loading

0 comments on commit b909fb8

Please sign in to comment.