Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

To chars #160

Merged
merged 6 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ set(SNITCH_WITH_MULTITHREADING ON CACHE BOOL "Make the testing fram
set(SNITCH_WITH_TIMINGS ON CACHE BOOL "Measure the time taken by each test case -- disable to speed up tests.")
set(SNITCH_WITH_SHORTHAND_MACROS ON CACHE BOOL "Use short names for test macros -- disable if this causes conflicts.")
set(SNITCH_CONSTEXPR_FLOAT_USE_BITCAST ON CACHE BOOL "Use std::bit_cast if available to implement exact constexpr float-to-string conversion.")
set(SNITCH_APPEND_TO_CHARS ON CACHE BOOL "Use std::to_chars for string conversions -- disable for greater compatability with a slight performance cost.")
set(SNITCH_DEFAULT_WITH_COLOR ON CACHE BOOL "Enable terminal colors by default -- can also be controlled by command line interface.")
set(SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS OFF CACHE BOOL "Enable expression decomposition even for successful assertions -- more expensive.")
set(SNITCH_WITH_ALL_REPORTERS ON CACHE BOOL "Allow all built-in reporters to be selected from the command line -- disable for faster compilation.")
Expand Down
49 changes: 28 additions & 21 deletions include/snitch/snitch_append.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,47 +45,53 @@ SNITCH_EXPORT [[nodiscard]] bool append_fast(small_string_span ss, double f) noe
return could_fit;
}

[[nodiscard]] constexpr std::size_t num_digits(large_uint_t x) noexcept {
return x >= 10u ? 1u + num_digits(x / 10u) : 1u;
template<large_uint_t Base = 10u, unsigned_integral T>
[[nodiscard]] constexpr std::size_t num_digits(T x) noexcept {
return x >= Base ? 1u + num_digits<Base>(x / Base) : 1u;
}

[[nodiscard]] constexpr std::size_t num_digits(large_int_t x) noexcept {
return x >= 10 ? 1u + num_digits(x / 10) : x <= -10 ? 1u + num_digits(x / 10) : x > 0 ? 1u : 2u;
template<large_int_t Base = 10, signed_integral T>
[[nodiscard]] constexpr std::size_t num_digits(T x) noexcept {
return (x >= Base || x <= -Base) ? 1u + num_digits<Base>(x / Base) : x > 0 ? 1u : 2u;
}

constexpr std::array<char, 10> digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
constexpr std::array<char, 16> digits = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

constexpr std::size_t max_uint_length = num_digits(std::numeric_limits<large_uint_t>::max());
constexpr std::size_t max_int_length = max_uint_length + 1;

[[nodiscard]] constexpr bool append_constexpr(small_string_span ss, large_uint_t i) noexcept {
template<large_uint_t Base = 10u, unsigned_integral T>
[[nodiscard]] constexpr bool append_constexpr(small_string_span ss, T i) noexcept {
if (i != 0u) {
small_string<max_uint_length> tmp;
tmp.resize(num_digits(i));
tmp.resize(num_digits<Base>(i));
std::size_t k = 1;
for (large_uint_t j = i; j != 0u; j /= 10u, ++k) {
tmp[tmp.size() - k] = digits[j % 10u];
for (large_uint_t j = i; j != 0u; j /= Base, ++k) {
tmp[tmp.size() - k] = digits[j % Base];
}
return append_constexpr(ss, tmp);
} else {
return append_constexpr(ss, "0");
}
}

[[nodiscard]] constexpr bool append_constexpr(small_string_span ss, large_int_t i) noexcept {
template<large_int_t Base = 10, signed_integral T>
[[nodiscard]] constexpr bool append_constexpr(small_string_span ss, T i) noexcept {
if (i > 0) {
small_string<max_int_length> tmp;
tmp.resize(num_digits(i));
tmp.resize(num_digits<Base>(i));
std::size_t k = 1;
for (large_int_t j = i; j != 0; j /= 10, ++k) {
tmp[tmp.size() - k] = digits[j % 10];
for (large_int_t j = i; j != 0; j /= Base, ++k) {
tmp[tmp.size() - k] = digits[j % Base];
}
return append_constexpr(ss, tmp);
} else if (i < 0) {
small_string<max_int_length> tmp;
tmp.resize(num_digits(i));
tmp.resize(num_digits<Base>(i));
std::size_t k = 1;
for (large_int_t j = i; j != 0; j /= 10, ++k) {
tmp[tmp.size() - k] = digits[-(j % 10)];
for (large_int_t j = i; j != 0; j /= Base, ++k) {
tmp[tmp.size() - k] = digits[-(j % Base)];
}
tmp[0] = '-';
return append_constexpr(ss, tmp);
Expand All @@ -98,15 +104,15 @@ constexpr std::size_t max_int_length = max_uint_length + 1;
constexpr std::size_t min_exp_digits = 2u;

[[nodiscard]] constexpr std::size_t num_exp_digits(fixed_exp_t x) noexcept {
const std::size_t exp_digits = num_digits(static_cast<large_uint_t>(x > 0 ? x : -x));
const std::size_t exp_digits = num_digits<10>(static_cast<large_uint_t>(x > 0 ? x : -x));
return exp_digits < min_exp_digits ? min_exp_digits : exp_digits;
}

[[nodiscard]] constexpr std::size_t num_digits(const signed_fixed_data& x) noexcept {
// +1 for fractional separator '.'
// +1 for exponent separator 'e'
// +1 for exponent sign
return num_digits(static_cast<large_uint_t>(x.digits)) + num_exp_digits(x.exponent) +
return num_digits<10>(static_cast<large_uint_t>(x.digits)) + num_exp_digits(x.exponent) +
(x.sign ? 1u : 0u) + 3u;
}

Expand Down Expand Up @@ -135,7 +141,7 @@ set_precision(signed_fixed_data fd, std::size_t p) noexcept {
// and round-half-to-even is the default rounding mode for IEEE 754 floats. We don't follow
// the current rounding mode, but we can at least follow the default.

std::size_t base_digits = num_digits(static_cast<large_uint_t>(fd.digits));
std::size_t base_digits = num_digits<10>(static_cast<large_uint_t>(fd.digits));

bool only_zero = true;
while (base_digits > p) {
Expand All @@ -144,12 +150,13 @@ set_precision(signed_fixed_data fd, std::size_t p) noexcept {
only_zero = false;
}
fd.digits = fd.digits / 10u;
base_digits -= 1u;
} else {
fd.digits = round_half_to_even(fd.digits, only_zero);
fd.digits = round_half_to_even(fd.digits, only_zero);
base_digits = num_digits<10>(static_cast<large_uint_t>(fd.digits));
}

fd.exponent += 1;
base_digits -= 1u;
}

return fd;
Expand Down
11 changes: 9 additions & 2 deletions include/snitch/snitch_concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@
#include <type_traits>

namespace snitch {

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T>
concept signed_integral = std::is_signed_v<T>;
concept signed_integral = integral<T> && std::is_signed_v<T>;

template<typename T>
concept unsigned_integral = std::is_unsigned_v<T>;
concept unsigned_integral = integral<T> && std::is_unsigned_v<T>;

template<typename T>
concept floating_point = std::is_floating_point_v<T>;

template<typename T, typename U>
concept convertible_to = std::is_convertible_v<T, U>;

template<typename T, typename U>
concept same_as = std::is_same_v<T, U>;

template<typename T>
concept enumeration = std::is_enum_v<T>;

Expand Down
10 changes: 10 additions & 0 deletions include/snitch/snitch_config.hpp.config
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
#if !defined(SNITCH_CONSTEXPR_FLOAT_USE_BITCAST)
#cmakedefine01 SNITCH_CONSTEXPR_FLOAT_USE_BITCAST
#endif
#if !defined(SNITCH_APPEND_TO_CHARS)
#cmakedefine01 SNITCH_APPEND_TO_CHARS
#endif
#if !defined(SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS)
#cmakedefine01 SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS
#endif
Expand Down Expand Up @@ -111,6 +114,13 @@
# define SNITCH_CONSTEXPR_FLOAT_USE_BITCAST 0
#endif

#if (!defined(__cpp_lib_to_chars)) || (defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE <= 11) || \
(defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 14000) || \
(defined(_MSC_VER) && _MSC_VER <= 1924)
# undef SNITCH_APPEND_TO_CHARS
# define SNITCH_APPEND_TO_CHARS 0
#endif

#if SNITCH_SHARED_LIBRARY
# if defined(_MSC_VER)
# if defined(SNITCH_EXPORTS)
Expand Down
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ option('with_multithreading' ,type: 'boolean', value: true, descript
option('with_timings' ,type: 'boolean' ,value: true, description: 'Measure the time taken by each test case -- disable to speed up tests.')
option('with_shorthand_macros' ,type: 'boolean' ,value: true, description: 'Use short names for test macros -- disable if this causes conflicts.')
option('constexpr_float_use_bitcast' ,type: 'boolean' ,value: true, description: 'Use std::bit_cast if available to implement exact constexpr float-to-string conversion.')
option('snitch_append_to_chars' ,type: 'boolean' ,value: true, description: 'Use std::to_chars for string conversions -- disable for greater compatability with a slight performance cost.')
option('default_with_color' ,type: 'boolean' ,value: true, description: 'Enable terminal colors by default -- can also be controlled by command line interface.')
option('decompose_successful_assertions' ,type: 'boolean' ,value: true, description: 'Enable expression decomposition even for successful assertions -- more expensive.')
option('with_all_reporters' ,type: 'boolean' ,value: true, description: 'Allow all built-in reporters to be selected from the command line -- disable for faster compilation.')
Expand Down
1 change: 1 addition & 0 deletions snitch/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ conf_data = configuration_data({
'SNITCH_WITH_TIMINGS' : get_option('with_timings').to_int(),
'SNITCH_WITH_SHORTHAND_MACROS' : get_option('with_shorthand_macros').to_int(),
'SNITCH_CONSTEXPR_FLOAT_USE_BITCAST' : get_option('constexpr_float_use_bitcast').to_int(),
'SNITCH_APPEND_TO_CHARS' : get_option('snitch_append_to_chars').to_int(),
'SNITCH_DEFAULT_WITH_COLOR' : get_option('default_with_color').to_int(),
'SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS' : get_option('decompose_successful_assertions').to_int(),
'SNITCH_WITH_ALL_REPORTERS' : get_option('with_all_reporters').to_int(),
Expand Down
137 changes: 82 additions & 55 deletions src/snitch_append.cpp
Original file line number Diff line number Diff line change
@@ -1,65 +1,74 @@
#include "snitch/snitch_append.hpp"

#include <cinttypes> // for format strings
#include <cstdio> // for std::snprintf
#include "snitch/snitch_concepts.hpp"
#include "snitch/snitch_string.hpp"

#include <cstdint> // for std::uintptr_t
#include <cstring> // for std::memmove
#if SNITCH_APPEND_TO_CHARS
# include <charconv> // for std::to_chars
# include <system_error> // for std::errc
#endif

tocic marked this conversation as resolved.
Show resolved Hide resolved
namespace snitch::impl {
namespace {
using snitch::small_string_span;

template<typename T>
constexpr const char* get_format_code() noexcept {
if constexpr (std::is_same_v<T, const void*>) {
#if defined(_MSC_VER)
return "0x%p";
#else
return "%p";
#endif
} else if constexpr (std::is_same_v<T, std::uintmax_t>) {
return "%" PRIuMAX;
} else if constexpr (std::is_same_v<T, std::intmax_t>) {
return "%" PRIdMAX;
} else if constexpr (std::is_same_v<T, float>) {
return "%.6e";
} else if constexpr (std::is_same_v<T, double>) {
return "%.15e";
} else {
static_assert(!std::is_same_v<T, T>, "unsupported type");
using namespace std::literals;

#if SNITCH_APPEND_TO_CHARS
template<floating_point T>
bool append_to(small_string_span ss, T value) noexcept {
constexpr auto fmt = std::chars_format::scientific;
constexpr auto precision = same_as<float, std::remove_cvref_t<T>> ? 6 : 15;
auto [end, err] = std::to_chars(ss.end(), ss.begin() + ss.capacity(), value, fmt, precision);
if (err != std::errc{}) {
// Not enough space, try into a temporary string that *should* be big enough,
// and copy whatever we can. 32 characters is enough for all integers and floating
// point values encoded on 64 bit or less.
small_string<32> fallback;
auto [end2, err2] = std::to_chars(
fallback.end(), fallback.begin() + fallback.capacity(), value, fmt, precision);
if (err2 != std::errc{}) {
return false;
}
fallback.grow(end2 - fallback.begin());
return append(ss, fallback);
}
}

template<typename T>
bool append_fmt(small_string_span ss, T value) noexcept {
if (ss.available() <= 1) {
// snprintf will always print a null-terminating character,
// so abort early if only space for one or zero character, as
// this would clobber the original string.
return false;
}
ss.grow(end - ss.end());
return true;
}

// Calculate required length.
const int return_code = std::snprintf(nullptr, 0, get_format_code<T>(), value);
if (return_code < 0) {
return false;
template<large_int_t Base = 10, integral T>
bool append_to(small_string_span ss, T value) noexcept {
auto [end, err] = std::to_chars(ss.end(), ss.begin() + ss.capacity(), value, Base);
if (err != std::errc{}) {
// Not enough space, try into a temporary string that *should* be big enough,
// and copy whatever we can. 32 characters is enough for all integers and floating
// point values encoded on 64 bit or less.
small_string<32> fallback;
auto [end2, err2] =
std::to_chars(fallback.end(), fallback.begin() + fallback.capacity(), value, Base);
if (err2 != std::errc{}) {
return false;
}
fallback.grow(end2 - fallback.begin());
return append(ss, fallback);
}
ss.grow(end - ss.end());
return true;
}
#else
template<floating_point T>
bool append_to(small_string_span ss, T value) noexcept {
return append_constexpr(ss, value);
}

// 'return_code' holds the number of characters that are required,
// excluding the null-terminating character, which always gets appended,
// so we need to +1.
const std::size_t length = static_cast<std::size_t>(return_code) + 1;
const bool could_fit = length <= ss.available();

const std::size_t offset = ss.size();
const std::size_t prev_space = ss.available();
ss.resize(std::min(ss.size() + length, ss.capacity()));
std::snprintf(ss.begin() + offset, prev_space, get_format_code<T>(), value);

// Pop the null-terminating character, always printed unfortunately.
ss.pop_back();

return could_fit;
template<large_int_t Base = 10, integral T>
bool append_to(small_string_span ss, T value) noexcept {
return append_constexpr<Base>(ss, value);
}
#endif
} // namespace

bool append_fast(small_string_span ss, std::string_view str) noexcept {
Expand All @@ -80,24 +89,42 @@
bool append_fast(small_string_span ss, const void* ptr) noexcept {
if (ptr == nullptr) {
return append(ss, nullptr);
} else {
return append_fmt(ss, ptr);
}

if (!append_fast(ss, "0x"sv)) {
return false;

Check warning on line 95 in src/snitch_append.cpp

View check run for this annotation

Codecov / codecov/patch

src/snitch_append.cpp#L95

Added line #L95 was not covered by tests
}

const auto int_ptr = reinterpret_cast<std::uintptr_t>(ptr);

// Pad with zeros.
constexpr std::size_t max_digits = 2 * sizeof(void*);
std::size_t padding = max_digits - num_digits<16>(int_ptr);
while (padding > 0) {
constexpr std::string_view zeroes = "0000000000000000";
const std::size_t batch = std::min(zeroes.size(), padding);
if (!append_fast(ss, zeroes.substr(0, batch))) {
return false;
}

padding -= batch;
}
return append_to<16>(ss, int_ptr);
}

bool append_fast(small_string_span ss, large_uint_t i) noexcept {
return append_fmt(ss, i);
return append_to(ss, i);
}

bool append_fast(small_string_span ss, large_int_t i) noexcept {
return append_fmt(ss, i);
return append_to(ss, i);
}

bool append_fast(small_string_span ss, float f) noexcept {
return append_fmt(ss, f);
return append_to(ss, f);
}

bool append_fast(small_string_span ss, double d) noexcept {
return append_fmt(ss, d);
return append_to(ss, d);
}
} // namespace snitch::impl
Loading
Loading