blob: aa0748698720bc67d7e1d88424497a95573bd605 [file] [log] [blame]
// std::from_chars implementation for floating-point types -*- C++ -*-
// Copyright (C) 2020-2021 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
//
// ISO C++ 14882:2017
// 23.2.9 Primitive numeric input conversion [utility.from.chars]
//
// Prefer to use std::pmr::string if possible, which requires the cxx11 ABI.
#define _GLIBCXX_USE_CXX11_ABI 1
#include <charconv>
#include <string>
#include <memory_resource>
#include <cfenv>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <locale.h>
#include <bits/functexcept.h>
#if _GLIBCXX_HAVE_XLOCALE_H
# include <xlocale.h>
#endif
#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
#ifndef __LONG_DOUBLE_IBM128__
#error "floating_from_chars.cc must be compiled with -mabi=ibmlongdouble"
#endif
// strtold for __ieee128
extern "C" __ieee128 __strtoieee128(const char*, char**);
#endif
#if _GLIBCXX_HAVE_USELOCALE
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
namespace
{
// A memory resource with a static buffer that can be used for small
// allocations. At most one allocation using the freestore can be done
// if the static buffer is insufficient. The callers below only require
// a single allocation, so there's no need for anything more complex.
struct buffer_resource : pmr::memory_resource
{
~buffer_resource() { if (m_ptr) operator delete(m_ptr, m_bytes); }
void*
do_allocate(size_t bytes, size_t alignment [[maybe_unused]]) override
{
// Allocate from the buffer if it will fit.
if (m_bytes < sizeof(m_buf) && (m_bytes + bytes) <= sizeof(m_buf))
return m_buf + std::__exchange(m_bytes, m_bytes + bytes);
__glibcxx_assert(m_ptr == nullptr);
__glibcxx_assert(alignment != 1);
m_ptr = operator new(bytes);
m_bytes = bytes;
return m_ptr;
}
void
do_deallocate(void*, size_t, size_t) noexcept override
{ /* like pmr::monotonic_buffer_resource, do nothing here */ }
bool
do_is_equal(const pmr::memory_resource& other) const noexcept override
{ return &other == this; }
static constexpr int guaranteed_capacity() { return sizeof(m_buf); }
private:
char m_buf[512];
size_t m_bytes = 0;
void* m_ptr = nullptr;
};
#if _GLIBCXX_USE_CXX11_ABI
using buffered_string = std::pmr::string;
#else
using buffered_string = std::string;
#endif
inline bool valid_fmt(chars_format fmt)
{
return fmt != chars_format{}
&& ((fmt & chars_format::general) == fmt
|| (fmt & chars_format::hex) == fmt);
}
constexpr char hex_digits[] = "abcdefABCDEF0123456789";
constexpr auto dec_digits = hex_digits + 12;
// Find initial portion of [first, last) containing a floating-point number.
// The string `digits` is either `dec_digits` or `hex_digits`
// and `exp` is 'e' or 'p' or '\0'.
const char*
find_end_of_float(const char* first, const char* last, const char* digits,
char exp)
{
while (first < last && strchr(digits, *first) != nullptr)
++first;
if (first < last && *first == '.')
{
++first;
while (first < last && strchr(digits, *first))
++first;
}
if (first < last && exp != 0 && std::tolower((unsigned char)*first) == exp)
{
++first;
if (first < last && (*first == '-' || *first == '+'))
++first;
while (first < last && strchr(dec_digits, *first) != nullptr)
++first;
}
return first;
}
// Determine the prefix of [first, last) that matches the pattern
// corresponding to `fmt`.
// Returns a NTBS containing the pattern, using `buf` to allocate
// additional storage if needed.
// Returns a nullptr if a valid pattern is not present.
const char*
pattern(const char* const first, const char* last,
chars_format& fmt, buffered_string& buf)
{
// fmt has the value of one of the enumerators of chars_format.
__glibcxx_assert(valid_fmt(fmt));
string_view res;
if (first == last || *first == '+') [[unlikely]]
return nullptr;
const int neg = (*first == '-');
if (std::memchr("iInN", (unsigned char)first[neg], 4))
{
ptrdiff_t len = last - first;
if (len < (3 + neg))
return nullptr;
// possible infinity or NaN, let strtod decide
if (first[neg] == 'i' || first[neg] == 'I')
{
// Need at most 9 chars for "-INFINITY", ignore anything after it.
len = std::min(len, ptrdiff_t(neg + 8));
}
else if (len > (neg + 3) && first[neg + 3] == '(')
{
// Look for end of "NAN(n-char-sequence)"
if (void* p = std::memchr(const_cast<char*>(first)+4, ')', len-4))
len = static_cast<char*>(p) + 1 - first;
#ifndef __cpp_exceptions
if (len > buffer_resource::guaranteed_capacity())
{
// The character sequence is too large for the buffer.
// Allocation failure could terminate the process,
// so just return an error via the fmt parameter.
fmt = chars_format{};
return nullptr;
}
#endif
}
else // Only need 4 chars for "-NAN"
len = neg + 3;
buf.assign(first, 0, len);
// prevent make_result correcting for "0x"
fmt = chars_format::general;
return buf.c_str();
}
const char* digits;
char* ptr;
// Assign [first,last) to a std::string to get a NTBS that can be used
// with strspn, strtod etc.
// If the string would be longer than the fixed buffer inside the
// buffer_resource type use find_end_of_float to try to reduce how
// much memory is needed, to reduce the chance of std::bad_alloc.
if (fmt == chars_format::hex)
{
digits = hex_digits;
if ((last - first + 2) > buffer_resource::guaranteed_capacity())
{
last = find_end_of_float(first + neg, last, digits, 'p');
#ifndef __cpp_exceptions
if ((last - first + 2) > buffer_resource::guaranteed_capacity())
{
// The character sequence is still too large for the buffer.
// Allocation failure could terminate the process,
// so just return an error via the fmt parameter.
fmt = chars_format{};
return nullptr;
}
#endif
}
buf = "-0x" + !neg;
buf.append(first + neg, last);
ptr = buf.data() + neg + 2;
}
else
{
digits = dec_digits;
if ((last - first) > buffer_resource::guaranteed_capacity())
{
last = find_end_of_float(first + neg, last, digits,
"e"[fmt == chars_format::fixed]);
#ifndef __cpp_exceptions
if ((last - first) > buffer_resource::guaranteed_capacity())
{
// The character sequence is still too large for the buffer.
// Allocation failure could terminate the process,
// so just return an error via the fmt parameter.
fmt = chars_format{};
return nullptr;
}
#endif
}
buf.assign(first, last);
ptr = buf.data() + neg;
}
// "A non-empty sequence of decimal digits" or
// "A non-empty sequence of hexadecimal digits"
size_t len = std::strspn(ptr, digits);
// "possibly containing a radix character,"
if (ptr[len] == '.')
{
const size_t len2 = std::strspn(ptr + len + 1, digits);
if (len + len2)
ptr += len + 1 + len2;
else
return nullptr;
}
else if (len == 0) [[unlikely]]
return nullptr;
else
ptr += len;
if (fmt == chars_format::fixed)
{
// Truncate the string to stop strtod parsing past this point.
*ptr = '\0';
}
else if (fmt == chars_format::scientific)
{
// Check for required exponent part which starts with 'e' or 'E'
if (*ptr != 'e' && *ptr != 'E')
return nullptr;
// then an optional plus or minus sign
const int sign = (ptr[1] == '-' || ptr[1] == '+');
// then a nonempty sequence of decimal digits
if (!std::memchr(dec_digits, (unsigned char)ptr[1+sign], 10))
return nullptr;
}
else if (fmt == chars_format::general)
{
if (*ptr == 'x' || *ptr == 'X')
*ptr = '\0';
}
return buf.c_str();
}
// Convert the NTBS `str` to a floating-point value of type `T`.
// If `str` cannot be converted, `value` is unchanged and `0` is returned.
// Otherwise, let N be the number of characters consumed from `str`.
// On success `value` is set to the converted value and N is returned.
// If the converted value is out of range, `value` is unchanged and
// -N is returned.
template<typename T>
ptrdiff_t
from_chars_impl(const char* str, T& value, errc& ec) noexcept
{
if (locale_t loc = ::newlocale(LC_ALL_MASK, "C", (locale_t)0)) [[likely]]
{
locale_t orig = ::uselocale(loc);
#if _GLIBCXX_USE_C99_FENV_TR1 && defined(FE_TONEAREST)
const int rounding = std::fegetround();
if (rounding != FE_TONEAREST)
std::fesetround(FE_TONEAREST);
#endif
const int save_errno = errno;
errno = 0;
char* endptr;
T tmpval;
#if _GLIBCXX_USE_C99_STDLIB
if constexpr (is_same_v<T, float>)
tmpval = std::strtof(str, &endptr);
else if constexpr (is_same_v<T, double>)
tmpval = std::strtod(str, &endptr);
else if constexpr (is_same_v<T, long double>)
tmpval = std::strtold(str, &endptr);
# ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
else if constexpr (is_same_v<T, __ieee128>)
tmpval = __strtoieee128(str, &endptr);
# endif
#else
tmpval = std::strtod(str, &endptr);
#endif
const int conv_errno = std::__exchange(errno, save_errno);
#if _GLIBCXX_USE_C99_FENV_TR1 && defined(FE_TONEAREST)
if (rounding != FE_TONEAREST)
std::fesetround(rounding);
#endif
::uselocale(orig);
::freelocale(loc);
const ptrdiff_t n = endptr - str;
if (conv_errno == ERANGE) [[unlikely]]
{
if (__builtin_isinf(tmpval)) // overflow
ec = errc::result_out_of_range;
else // underflow (LWG 3081 wants to set value = tmpval here)
ec = errc::result_out_of_range;
}
else if (n)
{
value = tmpval;
ec = errc();
}
return n;
}
else if (errno == ENOMEM)
ec = errc::not_enough_memory;
return 0;
}
inline from_chars_result
make_result(const char* str, ptrdiff_t n, chars_format fmt, errc ec) noexcept
{
from_chars_result result = { str, ec };
if (n != 0)
{
if (fmt == chars_format::hex)
n -= 2; // correct for the "0x" inserted into the pattern
result.ptr += n;
}
else if (fmt == chars_format{}) [[unlikely]]
{
// FIXME: the standard does not allow this result.
ec = errc::not_enough_memory;
}
return result;
}
#if ! _GLIBCXX_USE_CXX11_ABI
inline bool
reserve_string(std::string& s) noexcept
{
__try
{
s.reserve(buffer_resource::guaranteed_capacity());
}
__catch (const std::bad_alloc&)
{
return false;
}
return true;
}
#endif
} // namespace
// FIXME: This should be reimplemented so it doesn't use strtod and newlocale.
// That will avoid the need for any memory allocation, meaning that the
// non-conforming errc::not_enough_memory result cannot happen.
from_chars_result
from_chars(const char* first, const char* last, float& value,
chars_format fmt) noexcept
{
errc ec = errc::invalid_argument;
#if _GLIBCXX_USE_CXX11_ABI
buffer_resource mr;
pmr::string buf(&mr);
#else
string buf;
if (!reserve_string(buf))
return make_result(first, 0, {}, ec);
#endif
size_t len = 0;
__try
{
if (const char* pat = pattern(first, last, fmt, buf)) [[likely]]
len = from_chars_impl(pat, value, ec);
}
__catch (const std::bad_alloc&)
{
fmt = chars_format{};
}
return make_result(first, len, fmt, ec);
}
from_chars_result
from_chars(const char* first, const char* last, double& value,
chars_format fmt) noexcept
{
errc ec = errc::invalid_argument;
#if _GLIBCXX_USE_CXX11_ABI
buffer_resource mr;
pmr::string buf(&mr);
#else
string buf;
if (!reserve_string(buf))
return make_result(first, 0, {}, ec);
#endif
size_t len = 0;
__try
{
if (const char* pat = pattern(first, last, fmt, buf)) [[likely]]
len = from_chars_impl(pat, value, ec);
}
__catch (const std::bad_alloc&)
{
fmt = chars_format{};
}
return make_result(first, len, fmt, ec);
}
from_chars_result
from_chars(const char* first, const char* last, long double& value,
chars_format fmt) noexcept
{
errc ec = errc::invalid_argument;
#if _GLIBCXX_USE_CXX11_ABI
buffer_resource mr;
pmr::string buf(&mr);
#else
string buf;
if (!reserve_string(buf))
return make_result(first, 0, {}, ec);
#endif
size_t len = 0;
__try
{
if (const char* pat = pattern(first, last, fmt, buf)) [[likely]]
len = from_chars_impl(pat, value, ec);
}
__catch (const std::bad_alloc&)
{
fmt = chars_format{};
}
return make_result(first, len, fmt, ec);
}
#ifdef _GLIBCXX_LONG_DOUBLE_COMPAT
// Make std::from_chars for 64-bit long double an alias for the overload
// for double.
extern "C" from_chars_result
_ZSt10from_charsPKcS0_ReSt12chars_format(const char* first, const char* last,
long double& value,
chars_format fmt) noexcept
__attribute__((alias ("_ZSt10from_charsPKcS0_RdSt12chars_format")));
#endif
#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
from_chars_result
from_chars(const char* first, const char* last, __ieee128& value,
chars_format fmt) noexcept
{
buffer_resource mr;
pmr::string buf(&mr);
size_t len = 0;
errc ec = errc::invalid_argument;
__try
{
if (const char* pat = pattern(first, last, fmt, buf)) [[likely]]
len = from_chars_impl(pat, value, ec);
}
__catch (const std::bad_alloc&)
{
fmt = chars_format{};
}
return make_result(first, len, fmt, ec);
}
#endif
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#endif // _GLIBCXX_HAVE_USELOCALE