Fix floating point from_chars implementation

- Do not assume a zero-byte terminator exists in the source
- Ignore exponent if format does not contain scientific
- Require exponent if format does not contain fixed
- Add support for hexfloat parsing
- Fail if a plus sign is present
- Add bitwise operations to chars_format
- Avoid use of unnamed namespace in header file
- Detect whether charconv is present and if floating point charconv is supported
pull/1251/head
Andrew Tribick 2021-12-15 21:22:53 +01:00 committed by ajtribick
parent 63d4de9d81
commit b94d084af3
11 changed files with 670 additions and 78 deletions

View File

@ -365,6 +365,16 @@ endif()
try_compile(HAVE_STRING_VIEW ${CMAKE_BINARY_DIR} "${CMAKE_SOURCE_DIR}/checks/cxxsv.cpp")
try_compile(HAVE_EXPERIMENTAL_STRING_VIEW ${CMAKE_BINARY_DIR} "${CMAKE_SOURCE_DIR}/checks/cxxsvexp.cpp")
try_compile(HAVE_CHARCONV ${CMAKE_BINARY_DIR} "${CMAKE_SOURCE_DIR}/checks/cxxccint.cpp")
if(HAVE_CHARCONV)
try_compile(HAVE_FLOAT_CHARCONV ${CMAKE_BINARY_DIR} "${CMAKE_SOURCE_DIR}/checks/cxxccfloat.cpp")
if(NOT HAVE_FLOAT_CHARCONV)
message(WARNING "C++ charconv lacks floating point support!\nWill use own implementation.")
endif()
else()
message(WARNING "C++ charconv is unusable!\nWill use own implementation.")
endif()
include(TestBigEndian)
test_big_endian(WORDS_BIGENDIAN)

View File

@ -0,0 +1,8 @@
#include <charconv>
int main()
{
const char* src = "123";
float x;
std::from_chars_result result = std::from_chars(src, src + 3, x);
}

View File

@ -0,0 +1,8 @@
#include <charconv>
int main()
{
const char* src = "123";
int x;
std::from_chars_result result = std::from_chars(src, src + 3, x, 10);
}

View File

@ -1,4 +1,6 @@
#cmakedefine HAVE_BYTESWAP_H
#cmakedefine HAVE_CHARCONV
#cmakedefine HAVE_FLOAT_CHARCONV
#cmakedefine HAVE_FILESYSTEM
#cmakedefine HAVE_EXPERIMENTAL_FILESYSTEM
#cmakedefine HAVE_STRING_VIEW

View File

@ -1,7 +1,10 @@
if (NOT HAVE_FLOAT_CHARCONV)
set(CELCOMPAT_SOURCES
cc.cpp
cc.h
)
endif()
if ((NOT HAVE_EXPERIMENTAL_FILESYSTEM) AND (NOT HAVE_FILESYSTEM))
set(CELCOMPAT_SOURCES ${CELCOMPAT_SOURCES}
fs.cpp
@ -9,4 +12,6 @@ if ((NOT HAVE_EXPERIMENTAL_FILESYSTEM) AND (NOT HAVE_FILESYSTEM))
)
endif()
add_library(celcompat OBJECT ${CELCOMPAT_SOURCES})
if (CELCOMPAT_SOURCES)
add_library(celcompat OBJECT ${CELCOMPAT_SOURCES})
endif()

View File

@ -9,66 +9,232 @@
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cstdlib>
#include <cstring>
#include <config.h>
#include "cc.h"
namespace celestia
{
namespace compat
{
auto from_chars(const char* first, const char* last, float &value,
celestia::compat::chars_format fmt)
-> celestia::compat::from_chars_result
namespace
{
char *end;
errno = 0;
float result = strtof(first, &end);
if (result == 0.0f && end == first)
return { first, std::errc::invalid_argument };
constexpr std::size_t buffer_size = 512;
constexpr std::size_t inf_length = 3;
constexpr std::size_t infinity_length = 8;
constexpr std::size_t nan_length = 3;
constexpr std::size_t hex_prefix_length = 2;
enum class State
{
Start,
Fraction,
ExponentStart,
Exponent,
};
from_chars_result
write_buffer(const char* first, const char* last, chars_format fmt, char* buffer, bool& hex_prefix)
{
hex_prefix = false;
const char* ptr = first;
char* buffer_end = buffer + buffer_size - 1; // allow space for zero byte terminator
if (*ptr == '-')
{
*(buffer++) = *(ptr++);
if (ptr == last) { return { first, std::errc::invalid_argument }; }
}
if (*ptr == 'i' || *ptr == 'I')
{
if (last - ptr < inf_length) { return { first, std::errc::invalid_argument }; }
std::size_t length = std::min(static_cast<std::size_t>(infinity_length), static_cast<std::size_t>(last - ptr));
std::memcpy(buffer, ptr, length);
buffer += length;
}
else if (*ptr == 'n' || *ptr == 'N')
{
if (last - ptr < nan_length) { return { first, std::errc::invalid_argument }; }
std::memcpy(buffer, ptr, nan_length);
ptr += nan_length;
buffer += nan_length;
if (ptr < last && *ptr == '(')
{
*(buffer++) = *(ptr++);
while (ptr < last)
{
if (buffer == buffer_end)
{
// non-standard return code: no space to store entire bracket contents
return { first, std::errc::not_enough_memory };
}
if (*ptr == ')')
{
*(buffer++) = *(ptr++);
break;
}
else if (std::isalnum(static_cast<unsigned char>(*ptr))
|| *ptr == '_')
{
*(buffer++) = *(ptr++);
}
else
{
break;
}
}
}
}
else
{
if (fmt == chars_format::hex)
{
hex_prefix = true;
*(buffer++) = '0';
*(buffer++) = 'x';
}
State state = State::Start;
while (ptr < last)
{
if (*ptr == '.')
{
if (state != State::Start) { break; }
state = State::Fraction;
}
else if (*ptr == '+' || *ptr == '-')
{
if (state != State::ExponentStart) { break; }
state = State::Exponent;
}
else if (fmt == chars_format::hex)
{
if (*ptr == 'p' || *ptr == 'P')
{
if (state != State::Start && state != State::Fraction) { break; }
state = State::ExponentStart;
}
else if (std::isxdigit(static_cast<unsigned char>(*ptr)))
{
if (state == State::ExponentStart) { state = State::Exponent; }
}
else
{
break;
}
}
else if (*ptr == 'e' || *ptr == 'E')
{
if ((fmt & chars_format::scientific) != chars_format::scientific) { break; }
state = State::ExponentStart;
}
else if (std::isdigit(static_cast<unsigned char>(*ptr)))
{
if (state == State::ExponentStart) { state = State::Exponent; }
}
else
{
break;
}
if (buffer == buffer_end) {
// non-standard return code: no space to keep entire literal
return { first, std::errc::not_enough_memory };
}
*(buffer++) = *(ptr++);
}
if (state == State::ExponentStart) { --buffer; state = State::Fraction; }
if (fmt == chars_format::scientific && state != State::Exponent)
{
return { first, std::errc::invalid_argument };
}
}
*buffer = '\0';
return { ptr, std::errc{} };
}
inline void
parse_value(const char* start, char** end, float& value)
{
value = std::strtof(start, end);
}
inline void
parse_value(const char* start, char** end, double& value)
{
value = std::strtod(start, end);
}
inline void
parse_value(const char* start, char** end, long double& value)
{
value = std::strtold(start, end);
}
template<typename T>
from_chars_result
from_chars_impl(const char* first, const char* last, T& value, chars_format fmt)
{
char buffer[buffer_size];
bool hex_prefix;
from_chars_result result = write_buffer(first, last, fmt, buffer, hex_prefix);
if (result.ec != std::errc{}) { return result; }
const char* savedLocale = std::setlocale(LC_NUMERIC, nullptr);
std::setlocale(LC_NUMERIC, "C");
char* end;
T parsed;
parse_value(buffer, &end, parsed);
if (errno == ERANGE)
{
errno = 0;
return { first, std::errc::invalid_argument };
result = { first + (end - buffer), std::errc::result_out_of_range };
}
value = result;
return { end, std::errc() };
}
auto from_chars(const char* first, const char* last, double &value,
celestia::compat::chars_format fmt)
-> celestia::compat::from_chars_result
{
char *end;
errno = 0;
double result = strtod(first, &end);
if (result == 0.0 && end == first)
return { first, std::errc::invalid_argument };
if (errno == ERANGE)
else if (end == buffer)
{
errno = 0;
return { first, std::errc::invalid_argument };
result = { first, std::errc::invalid_argument };
}
value = result;
return { end, std::errc() };
}
auto from_chars(const char* first, const char* last, long double &value,
celestia::compat::chars_format fmt)
-> celestia::compat::from_chars_result
{
char *end;
errno = 0;
double result = strtold(first, &end);
if (result == 0.0l && end == first)
return { first, std::errc::invalid_argument };
if (errno == ERANGE)
else
{
errno = 0;
return { first, std::errc::invalid_argument };
value = parsed;
if (hex_prefix) { end -= hex_prefix_length; }
result = { first + (end - buffer), std::errc{} };
}
value = result;
return { end, std::errc() };
std::setlocale(LC_NUMERIC, savedLocale);
return result;
}
} // end unnamed namespace
from_chars_result
from_chars(const char* first, const char* last, float &value, chars_format fmt)
{
return from_chars_impl(first, last, value, fmt);
}
from_chars_result
from_chars(const char* first, const char* last, double &value, chars_format fmt)
{
return from_chars_impl(first, last, value, fmt);
}
from_chars_result
from_chars(const char* first, const char* last, long double &value, chars_format fmt)
{
return from_chars_impl(first, last, value, fmt);
}
} // end namespace compat
} // end namespace celestia

View File

@ -11,6 +11,9 @@
#pragma once
#ifdef HAVE_CHARCONV
#include <charconv>
#endif
#include <limits>
#include <system_error>
#include <type_traits>
@ -19,13 +22,21 @@ namespace celestia
{
namespace compat
{
#ifdef HAVE_CHARCONV
using std::from_chars_result;
#else
struct from_chars_result
{
const char* ptr;
std::errc ec;
};
#endif
enum class chars_format
template <typename T, typename std::enable_if<std::numeric_limits<T>::is_integer, T>::type = 0>
from_chars_result
from_chars(const char* first, const char* last, T& value, int base = 10);
enum class chars_format : unsigned int
{
scientific = 1,
fixed = 2,
@ -33,27 +44,77 @@ enum class chars_format
general = fixed | scientific
};
template <typename T, typename std::enable_if<std::numeric_limits<T>::is_integer, T>::type = 0>
auto from_chars(const char* first, const char* last, T& value, int base = 10)
-> celestia::compat::from_chars_result;
constexpr chars_format
operator&(chars_format lhs, chars_format rhs) noexcept
{
return static_cast<chars_format>(
static_cast<unsigned int>(lhs) & static_cast<unsigned int>(rhs));
}
auto from_chars(const char* first, const char* last, float &value,
celestia::compat::chars_format fmt = celestia::compat::chars_format::general)
-> celestia::compat::from_chars_result;
constexpr chars_format
operator|(chars_format lhs, chars_format rhs) noexcept
{
return static_cast<chars_format>(
static_cast<unsigned int>(lhs) | static_cast<unsigned int>(rhs));
}
auto from_chars(const char* first, const char* last, double &value,
celestia::compat::chars_format fmt = celestia::compat::chars_format::general)
-> celestia::compat::from_chars_result;
constexpr chars_format
operator^(chars_format lhs, chars_format rhs) noexcept
{
return static_cast<chars_format>(
static_cast<unsigned int>(lhs) ^ static_cast<unsigned int>(rhs));
}
auto from_chars(const char* first, const char* last, long double &value,
celestia::compat::chars_format fmt = celestia::compat::chars_format::general)
-> celestia::compat::from_chars_result;
constexpr chars_format
operator~(chars_format rhs) noexcept
{
return static_cast<chars_format>((~static_cast<unsigned int>(rhs)) & 7);
}
namespace
constexpr chars_format&
operator&=(chars_format& lhs, chars_format rhs) noexcept
{
return lhs = lhs & rhs;
}
constexpr chars_format&
operator|=(chars_format& lhs, chars_format rhs) noexcept
{
return lhs = lhs | rhs;
}
constexpr chars_format&
operator^=(chars_format& lhs, chars_format rhs) noexcept
{
return lhs = lhs ^ rhs;
}
from_chars_result
from_chars(const char* first, const char* last, float &value,
chars_format fmt = chars_format::general);
from_chars_result
from_chars(const char* first, const char* last, double &value,
chars_format fmt = chars_format::general);
from_chars_result
from_chars(const char* first, const char* last, long double &value,
chars_format fmt = chars_format::general);
#ifdef HAVE_CHARCONV
template <typename T, typename std::enable_if<std::numeric_limits<T>::is_integer, T>::type>
from_chars_result
from_chars(const char* first, const char* last, T& value, int base)
{
return std::from_chars(first, last, value, base);
}
#else
namespace detail
{
template <typename T>
auto int_from_chars(const char* first, const char* last, T& value, int base) noexcept
-> celestia::compat::from_chars_result
from_chars_result
int_from_chars(const char* first, const char* last, T& value, int base) noexcept
{
auto p = first;
bool has_minus = false;
@ -123,13 +184,15 @@ auto int_from_chars(const char* first, const char* last, T& value, int base) noe
return { p, std::errc() };
}
} // anon namespace
} // end namespace detail
template <typename T, typename std::enable_if<std::numeric_limits<T>::is_integer, T>::type>
auto from_chars(const char* first, const char* last, T& value, int base)
-> celestia::compat::from_chars_result
from_chars_result
from_chars(const char* first, const char* last, T& value, int base)
{
return int_from_chars(first, last, value, base);
}
}
return detail::int_from_chars(first, last, value, base);
}
#endif
} // end namespace compat
} // end namespace celestia

View File

@ -2,14 +2,14 @@
#include <config.h>
#ifdef HAVE_CHARCONV
#ifdef HAVE_FLOAT_CHARCONV
#include <charconv>
namespace celestia
{
namespace compat
{
using std::from_chars_result;
using std::chars_format;
using std::from_chars_result;
using std::from_chars;
}
}

View File

@ -34,6 +34,19 @@ elseif(ENABLE_FFMPEG)
)
endif()
set(CELESTIA_CORE_LIBS $<TARGET_OBJECTS:cel3ds>
$<TARGET_OBJECTS:celengine>
$<TARGET_OBJECTS:celephem>
$<TARGET_OBJECTS:celimage>
$<TARGET_OBJECTS:celmath>
$<TARGET_OBJECTS:celmodel>
$<TARGET_OBJECTS:celttf>
$<TARGET_OBJECTS:celutil>
)
if(TARGET celcompat)
set(CELESTIA_CORE_LIBS ${CELESTIA_CORE_LIBS} $<TARGET_OBJECTS:celcompat>)
endif()
set(SCRIPT_LIBS $<TARGET_OBJECTS:celcommonscript> $<TARGET_OBJECTS:cellegacyscript>)
if(ENABLE_CELX)
set(SCRIPT_LIBS ${SCRIPT_LIBS} $<TARGET_OBJECTS:celluascript>)
@ -46,15 +59,7 @@ set(CELSO "celestia-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
add_library(celestia SHARED ${CELESTIA_SOURCES}
${SCRIPT_LIBS}
$<TARGET_OBJECTS:cel3ds>
$<TARGET_OBJECTS:celcompat>
$<TARGET_OBJECTS:celengine>
$<TARGET_OBJECTS:celephem>
$<TARGET_OBJECTS:celimage>
$<TARGET_OBJECTS:celmath>
$<TARGET_OBJECTS:celmodel>
$<TARGET_OBJECTS:celttf>
$<TARGET_OBJECTS:celutil>
${CELESTIA_CORE_LIBS}
)
set_target_properties(celestia PROPERTIES

View File

@ -1,3 +1,4 @@
test_case(charconv_compat)
test_case(hash)
if ((NOT HAVE_EXPERIMENTAL_FILESYSTEM) AND (NOT HAVE_FILESYSTEM))
test_case(fs)

View File

@ -0,0 +1,324 @@
#include <cmath>
#include <cstddef>
#include <iostream>
#include <limits>
#include <string>
#include <system_error>
#include <celcompat/charconv.h>
#include <catch.hpp>
namespace celcompat = celestia::compat;
template<typename T>
struct TestCase
{
const char* source;
std::size_t size;
T expected;
std::ptrdiff_t length;
};
TEMPLATE_TEST_CASE("Floating point general format: successful", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ "123", 3, TestType{ 123.0 }, 3 },
{ "1234", 3, TestType{ 123.0 }, 3 },
{ "123c", 4, TestType{ 123.0 }, 3},
{ ".5", 2, TestType{ 0.5 }, 2 },
{ "108.", 4, TestType{ 108.0 }, 4 },
{ "108.5", 4, TestType{ 108.0 }, 4},
{ "23.5", 4, TestType{ 23.5 }, 4 },
{ "132e", 4, TestType{ 132 }, 3 },
{ "14e2", 4, TestType{ 1400.0 }, 4 },
{ "92.e1", 5, TestType{ 920.0 }, 5 },
{ "1.4e2", 5, TestType{ 140.0 }, 5 },
{ "14E+2", 5, TestType{ 1400.0 }, 5 },
{ ".5e3", 4, TestType{ 500.0 }, 4 },
{ "92.e+1", 6, TestType{ 920.0 }, 6 },
{ "1.4E+2", 6, TestType{ 140.0 }, 6 },
{ "5e-1", 4, TestType{ 0.5 }, 4 },
{ "5.e-1", 5, TestType{ 0.5 }, 5 },
{ "2.5e-1", 6, TestType{ 0.25 }, 6 },
{ "-123", 4, TestType{ -123.0 }, 4 },
{ "-123", 3, TestType{ -12.0 }, 3 },
{ "-108.", 5, TestType{ -108.0 }, 5 },
{ "-23.5", 5, TestType{ -23.5 }, 5 },
{ "-14e2", 5, TestType{ -1400.0 }, 5 },
{ "-14e25", 5, TestType{ -1400.0 }, 5 },
{ "-92.e1", 6, TestType{ -920.0 }, 6 },
{ "-1.4E2", 6, TestType{ -140.0 }, 6 },
{ "-14e+2", 6, TestType{ -1400.0 }, 6 },
{ "-92.e+1", 7, TestType{ -920.0 }, 7 },
{ "-1.4E+2", 7, TestType{ -140.0 }, 7 },
{ "-5e-1", 5, TestType{ -0.5 }, 5 },
{ "-5.E-1", 6, TestType{ -0.5 }, 6 },
{ "-2.5e-1", 7, TestType{ -0.25 }, 7 },
{ "inf", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "Inf", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "INF", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "infi", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "Infi", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "INFI", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "infinity", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "Infinity", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "INFINITY", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "-inf", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-Inf", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-INF", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-infi", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-Infi", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-INFI", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-infinity", 9, -std::numeric_limits<TestType>::infinity(), 9 },
{ "-Infinity", 9, -std::numeric_limits<TestType>::infinity(), 9 },
{ "-INFINITY", 9, -std::numeric_limits<TestType>::infinity(), 9 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::general);
REQUIRE(result.ec == std::errc{});
REQUIRE(actual == example.expected);
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == example.length);
}
}
TEMPLATE_TEST_CASE("Floating point general format: negative zero", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ "-0", 2, TestType{ -0.0 }, 2 },
{ "-0.", 3, TestType{ -0.0 }, 3 },
{ "-0.0", 4, TestType{ -0.0 }, 4 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::general);
REQUIRE(result.ec == std::errc{});
REQUIRE(actual == -0.0f);
float signum = std::copysign(1.0f, actual);
REQUIRE(signum == -1.0f);
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == example.length);
}
}
TEMPLATE_TEST_CASE("Floating point general format: NaN", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ "nan", 3, std::numeric_limits<TestType>::quiet_NaN(), 3 },
{ "NaN", 3, std::numeric_limits<TestType>::quiet_NaN(), 3 },
{ "NAN", 3, std::numeric_limits<TestType>::quiet_NaN(), 3 },
{ "naN()", 5, std::numeric_limits<TestType>::quiet_NaN(), 5 },
{ "nAn(abc)", 8, std::numeric_limits<TestType>::quiet_NaN(), 8 },
{ "nan(abcd", 8, std::numeric_limits<TestType>::quiet_NaN(), 3 },
{ "NaN(a^c)", 8, std::numeric_limits<TestType>::quiet_NaN(), 3 },
{ "-naN()", 6, std::numeric_limits<TestType>::quiet_NaN(), 6 },
{ "-nAn(abc)", 9, std::numeric_limits<TestType>::quiet_NaN(), 9 },
{ "-nan(abcd", 9, std::numeric_limits<TestType>::quiet_NaN(), 4 },
{ "-NaN(a^c)", 9, std::numeric_limits<TestType>::quiet_NaN(), 4 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::general);
REQUIRE(result.ec == std::errc{});
REQUIRE(std::isnan(actual));
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == example.length);
}
}
TEMPLATE_TEST_CASE("Floating point fixed format: successful", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ "123", 3, TestType{ 123.0 }, 3 },
{ "1234", 3, TestType{ 123.0 }, 3 },
{ "123c", 4, TestType{ 123.0 }, 3},
{ ".5", 2, TestType{ 0.5 }, 2 },
{ "108.", 4, TestType{ 108.0 }, 4 },
{ "108.5", 4, TestType{ 108.0 }, 4},
{ "23.5", 4, TestType{ 23.5 }, 4 },
{ "14e2", 4, TestType{ 14.0 }, 2 },
{ ".5e3", 4, TestType{ 0.5 }, 2 },
{ "92.e1", 5, TestType{ 92.0 }, 3 },
{ "1.5E2", 5, TestType{ 1.5 }, 3 },
{ "14e+2", 5, TestType{ 14.0 }, 2 },
{ "92.e+1", 6, TestType{ 92.0 }, 3 },
{ "1.5E+2", 6, TestType{ 1.5 }, 3 },
{ "5e-1", 4, TestType{ 5.0 }, 1 },
{ "5.e-1", 5, TestType{ 5.0 }, 2 },
{ "2.5E-1", 6, TestType{ 2.5 }, 3 },
{ "-123", 4, TestType{ -123.0 }, 4 },
{ "-123", 3, TestType{ -12.0 }, 3 },
{ "-108.", 5, TestType{ -108.0 }, 5 },
{ "-23.5", 5, TestType{ -23.5 }, 5 },
{ "-14e2", 5, TestType{ -14.0 }, 3 },
{ "-92.e1", 6, TestType{ -92.0 }, 4 },
{ "-1.5E2", 6, TestType{ -1.5 }, 4 },
{ "-14e+2", 6, TestType{ -14.0 }, 3 },
{ "-92.e+1", 7, TestType{ -92.0 }, 4 },
{ "-1.5e+2", 7, TestType{ -1.5 }, 4 },
{ "-5e-1", 5, TestType{ -5.0 }, 2 },
{ "-5.e-1", 6, TestType{ -5.0 }, 3 },
{ "-2.5e-1", 7, TestType{ -2.5 }, 4 },
{ "inf", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "Inf", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "INF", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "infi", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "Infi", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "INFI", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "infinity", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "Infinity", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "INFINITY", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "-inf", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-Inf", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-INF", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-infi", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-Infi", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-INFI", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-infinity", 9, -std::numeric_limits<TestType>::infinity(), 9 },
{ "-Infinity", 9, -std::numeric_limits<TestType>::infinity(), 9 },
{ "-INFINITY", 9, -std::numeric_limits<TestType>::infinity(), 9 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::fixed);
REQUIRE(result.ec == std::errc{});
REQUIRE(actual == example.expected);
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == example.length);
}
}
TEMPLATE_TEST_CASE("Floating point scientific format: successful", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ "14e2", 4, TestType { 1400.0 }, 4 },
{ "92.E1", 5, TestType{ 920.0 }, 5 },
{ "1.4e2", 5, TestType{ 140.0 }, 5 },
{ "14e+2", 5, TestType{ 1400.0 }, 5 },
{ "92.e+1", 6, TestType{ 920.0 }, 6 },
{ "1.4e+2", 6, TestType{ 140.0 }, 6 },
{ "5e-1", 4, TestType{ 0.5 }, 4 },
{ "5.e-1", 5, TestType{ 0.5 }, 5 },
{ "2.5e-1", 6, TestType{ 0.25 }, 6 },
{ "-14e2", 5, TestType{ -1400.0 }, 5 },
{ ".5e3", 4, TestType{ 500.0 }, 4 },
{ "-14e25", 5, TestType{ -1400.0 }, 5 },
{ "-92.e1", 6, TestType{ -920.0 }, 6 },
{ "-1.4E2", 6, TestType{ -140.0 }, 6 },
{ "-14e+2", 6, TestType{ -1400.0 }, 6 },
{ "-92.e+1", 7, TestType{ -920.0 }, 7 },
{ "-1.4E+2", 7, TestType{ -140.0 }, 7 },
{ "-5e-1", 5, TestType{ -0.5 }, 5 },
{ "-5.E-1", 6, TestType{ -0.5 }, 6 },
{ "-2.5E-1", 7, TestType{ -0.25 }, 7 },
{ "inf", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "Inf", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "INF", 3, std::numeric_limits<TestType>::infinity(), 3 },
{ "infi", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "Infi", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "INFI", 4, std::numeric_limits<TestType>::infinity(), 3 },
{ "infinity", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "Infinity", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "INFINITY", 8, std::numeric_limits<TestType>::infinity(), 8 },
{ "-inf", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-Inf", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-INF", 4, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-infi", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-Infi", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-INFI", 5, -std::numeric_limits<TestType>::infinity(), 4 },
{ "-infinity", 9, -std::numeric_limits<TestType>::infinity(), 9 },
{ "-Infinity", 9, -std::numeric_limits<TestType>::infinity(), 9 },
{ "-INFINITY", 9, -std::numeric_limits<TestType>::infinity(), 9 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::scientific);
REQUIRE(result.ec == std::errc{});
REQUIRE(actual == example.expected);
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == example.length);
}
}
TEMPLATE_TEST_CASE("Floating point scientific format: missing exponential", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ "123", 3, TestType{ 123.0 }, 3 },
{ "1234", 3, TestType{ 123.0 }, 3 },
{ "123c", 4, TestType{ 123.0 }, 3},
{ ".5", 2, TestType{ 0.5 }, 2 },
{ "108.", 4, TestType{ 108.0 }, 4 },
{ "108.5", 4, TestType{ 108.0 }, 4},
{ "23.5", 4, TestType{ 23.5 }, 4 },
{ "-123", 4, TestType{ -123.0 }, 4 },
{ "-123", 3, TestType{ -12.0 }, 3 },
{ "-108.", 5, TestType{ -108.0 }, 5 },
{ "-23.5", 5, TestType{ -23.5 }, 5 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::scientific);
REQUIRE(result.ec == std::errc::invalid_argument);
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == 0);
}
}
TEMPLATE_TEST_CASE("Hexadecimal floating point", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ "1b", 2, TestType{ 27 }, 2 },
{ ".8", 2, TestType{ 0.5 }, 2 },
{ "1.c", 3, TestType{ 1.75 }, 3 },
{ "1.3eP10", 7, TestType{ 1272 }, 7 },
{ "1.3Ep+10", 8, TestType{ 1272 }, 8 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::hex);
REQUIRE(result.ec == std::errc{});
REQUIRE(actual == example.expected);
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == example.length);
}
}
TEMPLATE_TEST_CASE("Floating point format failures", "[charconv][floating-point]", float, double, long double)
{
TestCase<TestType> examples[] = {
{ " 0.5", 5, TestType{ }, 0 },
{ ".e3", 3, TestType{ }, 0 },
{ "+23", 3, TestType{ }, 0 },
{ "N", 1, TestType{ }, 0 },
{ "NA", 2, TestType{ }, 0 },
{ "N/A", 3, TestType{ }, 0 },
{ "in", 2, TestType{ }, 0 },
{ "-N/A", 4, TestType{ }, 0 },
{ "-in", 3, TestType{ }, 0 },
};
for (const auto& example : examples)
{
TestType actual;
auto result = celcompat::from_chars(example.source, example.source + example.size, actual, celcompat::chars_format::general);
REQUIRE(result.ec == std::errc::invalid_argument);
auto length = static_cast<std::size_t>(result.ptr - example.source);
REQUIRE(length == example.length);
}
}