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 supportedpull/1251/head
parent
63d4de9d81
commit
b94d084af3
|
@ -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)
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
test_case(charconv_compat)
|
||||
test_case(hash)
|
||||
if ((NOT HAVE_EXPERIMENTAL_FILESYSTEM) AND (NOT HAVE_FILESYSTEM))
|
||||
test_case(fs)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue