Доработана конвертация в числа. Обновлена версия и результаты бенчмарков.

This commit is contained in:
Aleksandr Orefkov 2025-11-26 22:15:50 +03:00
parent 573ffd247e
commit 13b31fdc4b
13 changed files with 4449 additions and 4279 deletions

View File

@ -5,8 +5,8 @@ include(FetchContent)
project(
simstr
VERSION 1.2.5
DESCRIPTION "Yet another string library"
VERSION 1.2.6
DESCRIPTION "Yet another modern C++ string library"
HOMEPAGE_URL "https://github.com/orefkov/simstr"
LANGUAGES CXX
)

View File

@ -176,9 +176,6 @@ void ToIntStr0(benchmark::State& state, const std::string& s, int c) {
}
void ToIntFromChars10(benchmark::State& state, const std::string_view& s, int c) {
//#ifdef __EMSCRIPTEN__
//state.SkipWithError("not implemented");
//#else
for (auto _: state) {
int res = 0;
std::from_chars(s.data(), s.data() + s.size(), res, 10);
@ -190,13 +187,9 @@ void ToIntFromChars10(benchmark::State& state, const std::string_view& s, int c)
#endif
benchmark::DoNotOptimize(res);
}
//#endif
}
void ToIntFromChars16(benchmark::State& state, const std::string_view& s, int c) {
//#ifdef __EMSCRIPTEN__
// state.SkipWithError("not implemented");
//#else
for (auto _: state) {
int res = 0;
std::from_chars(s.data(), s.data() + s.size(), res, 16);
@ -208,13 +201,12 @@ void ToIntFromChars16(benchmark::State& state, const std::string_view& s, int c)
#endif
benchmark::DoNotOptimize(res);
}
//#endif
}
template<typename T>
void ToIntSimStr10(benchmark::State& state, T t, int c) {
for (auto _: state) {
int res = std::get<0>(t. template to_int<int, true, 10, false, false>());
int res = t. template to_int<int, true, 10, false, false>().value;
#ifdef CHECK_RESULT
if (res != c) {
state.SkipWithError("not equal");
@ -229,7 +221,7 @@ void ToIntSimStr10(benchmark::State& state, T t, int c) {
template<typename T>
void ToIntSimStr16(benchmark::State& state, T t, int c) {
for (auto _: state) {
int res = std::get<0>(t. template to_int<int, true, 16, false, false>());
int res = t. template to_int<int, true, 16, false, false>().value;
#ifdef CHECK_RESULT
if (res != c) {
state.SkipWithError("not equal");
@ -244,7 +236,7 @@ void ToIntSimStr16(benchmark::State& state, T t, int c) {
template<typename T>
void ToIntSimStr0(benchmark::State& state, T t, int c) {
for (auto _: state) {
int res = std::get<0>(t. template to_int<int>());
int res = t. template to_int<int>().value;
#ifdef CHECK_RESULT
if (res != c) {
state.SkipWithError("not equal");
@ -258,7 +250,7 @@ void ToIntSimStr0(benchmark::State& state, T t, int c) {
void ToIntNoOverflow(benchmark::State& state, ssa t, int c) {
for (auto _: state) {
int res = std::get<0>(t.to_int<int, false>());
int res = t.to_int<int, false>().value;
#ifdef CHECK_RESULT
if (res != c) {
state.SkipWithError("not equal");
@ -287,6 +279,63 @@ BENCHMARK_CAPTURE(ToIntStr0, , std::string{" 123456789"}, 123456789)
BENCHMARK_CAPTURE(ToIntSimStr0, , stringa{" 123456789"}, 123456789) ->Name("stringa s = \" 123456789\"; int res = s.to_int<int>; // Check overflow");
BENCHMARK_CAPTURE(ToIntNoOverflow, , ssa{" 123456789"}, 123456789) ->Name("ssa s = \" 123456789\"; int res = s.to_int<int, false>; // No check overflow");
void ToDoubleStr(benchmark::State& state, const std::string& s, double c) {
for (auto _: state) {
char* ptr = nullptr;
double res = std::strtod(s.c_str(), &ptr);
if (ptr == s.c_str()) {
state.SkipWithError("not equal");
}
#ifdef CHECK_RESULT
if (res != c) {
state.SkipWithError("not equal");
break;
}
#endif
benchmark::DoNotOptimize(res);
}
}
void ToDoubleFromChars(benchmark::State& state, const std::string_view& s, double c) {
for (auto _: state) {
double res = 0;
if (std::from_chars(s.data(), s.data() + s.size(), res).ec != std::errc{}) {
state.SkipWithError("not equal");
}
#ifdef CHECK_RESULT
if (res != c) {
state.SkipWithError("not equal");
break;
}
#endif
benchmark::DoNotOptimize(res);
}
}
template<typename T>
void ToDoubleSimStr(benchmark::State& state, T t, double c) {
for (auto _: state) {
auto r = t.template to_double<false>();
if (!r) {
state.SkipWithError("not equal");
}
double res = *r;
#ifdef CHECK_RESULT
if (res != c) {
state.SkipWithError("not equal");
break;
}
#endif
benchmark::DoNotOptimize(res);
benchmark::DoNotOptimize(t);
}
}
BENCHMARK(__)->Name("----- Convert to double '1234.567e10' ---------")->Repetitions(1);
BENCHMARK_CAPTURE(ToDoubleStr, , std::string{"1234.567e10"}, 1234.567e10) ->Name("std::string s = \"1234.567e10\"; double res = std::strtod(s.c_str(), nullptr);");
BENCHMARK_CAPTURE(ToDoubleFromChars, , std::string_view{"1234.567e10"}, 1234.567e10) ->Name("std::string_view s = \"1234.567e10\"; std::from_chars(s.data(), s.data() + s.size(), res);");
BENCHMARK_CAPTURE(ToDoubleSimStr, , ssa{"1234.567e10"}, 1234.567e10) ->Name("ssa s = \"1234.567e10\"; double res = *s.to_double()");
void AppendStreamConstLiteral(benchmark::State& state) {
for (auto _: state) {
std::string result;

View File

@ -95,7 +95,7 @@ results_vector get_results_infos() {
// В начале имени файла может идти число и дефис, для сортировки, уберём их
// At the beginning of the file name there can be a number and a hyphen, for sorting, remove them
if (auto delimeter = fileName.find('-'); delimeter + 1 > 1) {
if (std::get<1>(fileName(0, delimeter).to_int<unsigned, false, 10, false, false>()) == IntConvertResult::Success) {
if (fileName(0, delimeter).to_int<unsigned, false, 10, false, false>().ec == IntConvertResult::Success) {
fileName.remove_prefix(delimeter + 1);
}
}
@ -187,7 +187,7 @@ ssa extract_source_for_benchmark(ssa benchName, ssa sourceText) {
static hashStrMapA<stringa> textes;
size_t delim = benchName.find_last('/');
if (delim != str::npos && std::get<1>(benchName(delim + 1).to_int<unsigned, false, 10, false, false>()) == IntConvertResult::Success) {
if (delim != str::npos && benchName(delim + 1).to_int<unsigned, false, 10, false, false>().ec == IntConvertResult::Success) {
benchName.len = delim;
}
auto [it, not_exist] = textes.try_emplace(benchName);
@ -326,6 +326,9 @@ void write_benchmarks(out_t& out, const results_vector& results, ssa sourceText,
throw std::runtime_error{"Not expected end of file"};
}
line = splitters[idx].next();
if (line.starts_with("std::unordered_map<std::string, size_t> emplace & find std::string_view;_cv")) {
int t = 1;
}
}
}
if (needFooter) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -293,6 +293,13 @@ constexpr unsigned digit_width() {
template<typename T, unsigned Base>
constexpr unsigned max_overflow_digits = (sizeof(T) * CHAR_BIT) / digit_width<Base>();
template<typename T>
struct convert_result {
T value;
IntConvertResult ec;
size_t read;
};
struct int_convert { // NOLINT
inline static const uint8_t NUMBERS[] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
@ -322,7 +329,7 @@ struct int_convert { // NOLINT
template<typename K, ToIntNumber T, unsigned Base, bool CheckOverflow>
requires(Base != 0)
static std::tuple<T, IntConvertResult, size_t> parse(const K* start, const K* current, const K* end, bool negate) {
static convert_result<T> parse(const K* start, const K* current, const K* end, bool negate) {
using u_type = std::make_unsigned_t<T>;
#ifndef HAS_BUILTIN_OVERFLOW
u_type maxMult = 0, maxAdd = 0;
@ -406,7 +413,7 @@ struct int_convert { // NOLINT
if (error == IntConvertResult::NotNumber && current > from) {
error = IntConvertResult::BadSymbolAtTail;
}
return {result, error, current - start};
return {result, error, size_t(current - start)};
}
public:
// Если Base = 0 - то пытается определить основание по префиксу 0[xX] как 16, 0 как 8, иначе 10
@ -415,7 +422,7 @@ public:
// If Base = -1 - then tries to determine the base by the prefix 0[xX] as 16, 0[bB] as 2, 0[oO] or 0 as 8, otherwise 10
template<typename K, ToIntNumber T, unsigned Base = 0, bool CheckOverflow = true, bool SkipWs = true, bool AllowSign = true>
requires(Base == -1 || (Base < 37 && Base != 1))
static std::tuple<T, IntConvertResult, size_t> to_integer(const K* start, size_t len) noexcept {
static convert_result<T> to_integer(const K* start, size_t len) noexcept {
const K *ptr = start, *end = ptr + len;
bool negate = false;
if constexpr (SkipWs) {
@ -467,13 +474,13 @@ public:
}
return parse<K, T, 8, CheckOverflow>(start, --ptr, end, negate);
}
return {0, IntConvertResult::Success, ptr - start};
return {0, IntConvertResult::Success, size_t(ptr - start)};
}
return parse<K, T, 10, CheckOverflow>(start, ptr, end, negate);
} else
return parse<K, T, Base, CheckOverflow>(start, ptr, end, negate);
}
return {0, IntConvertResult::NotNumber, ptr - start};
return {0, IntConvertResult::NotNumber, size_t(ptr - start)};
}
};
@ -1439,7 +1446,7 @@ public:
* - в остальных случаях 10.
* @tparam SkipWs - пропускать пробельные символы в начале строки. Пропускаются все символы с ASCII кодами <= 32.
* @tparam AllowSign - допустим ли знак '+' перед числом.
* @return std::tuple<T, IntConvertResult, size_t> - кортеж из полученного числа, успешности преобразования и количестве обработанных символов.
* @return convert_result<T> - кортеж из полученного числа, успешности преобразования и количестве обработанных символов.
* @en @brief Convert a string to a number of the given type.
* @tparam T - the desired number type.
* @tparam CheckOverflow - check for overflow.
@ -1452,10 +1459,10 @@ public:
* - in other cases 10.
* @tparam SkipWs - skip whitespace characters at the beginning of the line. All characters with ASCII codes <= 32 are skipped.
* @tparam AllowSign - whether the '+' sign is allowed before a number.
* @return std::tuple<T, IntConvertResult, size_t> - a tuple of the received number, the success of the conversion and the number of characters processed.
* @return convert_result<T> - a tuple of the received number, the success of the conversion and the number of characters processed.
*/
template<ToIntNumber T, bool CheckOverflow = true, unsigned Base = 0, bool SkipWs = true, bool AllowSign = true>
std::tuple<T, IntConvertResult, size_t> to_int() const noexcept {
convert_result<T> to_int() const noexcept {
return int_convert::to_integer<K, T, Base, CheckOverflow, SkipWs, AllowSign>(_str(), _len());
}
/*!
@ -1464,7 +1471,7 @@ public:
* @en @brief Convert string to double.
* @return std::optional<double>.
*/
template<bool SkipWS = true>
template<bool SkipWS = true, bool AllowPlus = true>
std::optional<double> to_double() const noexcept {
size_t len = _len();
const K* ptr = _str();
@ -1474,15 +1481,30 @@ public:
ptr++;
}
}
if (len) {
return impl_to_double(ptr, ptr + len);
if constexpr (AllowPlus) {
if (len && *ptr == K('+')) {
ptr++;
len--;
}
}
if (!len) {
return {};
}
#ifdef __linux__
if constexpr(sizeof(K) == 1) {
double d{};
if (std::from_chars(ptr, ptr + len, d).ec == std::errc{}) {
return d;
}
return {};
}
#endif
return impl_to_double(ptr, ptr + len);
}
/*!
* @ru @brief Преобразовать строку в double.
* @ru @brief Преобразовать строку в 16ричной записи в double. Пока работает только для char.
* @return std::optional<double>.
* @en @brief Convert string to double.
* @en @brief Convert string in hex form to double.
* @return std::optional<double>.
*/
template<bool SkipWS = true> requires (sizeof(K) == 1)

View File

@ -1,11 +1,11 @@
# simstr - String object and function library
[![CMake on multiple platforms](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml)
Version 1.2.5.
Version 1.2.6.
<span class="obfuscator"><a href="readme_ru.md">On Russian | По-русски</a></span>
This library contains the implementation of several types of string objects and various algorithms for working with strings.
This library contains the modern implementation of several types of string objects and various algorithms for working with strings.
The goal of the library is to make working with strings in C++ as simple and easy as in many other languages, especially
scripting languages, while maintaining optimality and performance at the level of C and C++, and even improving them.
@ -47,8 +47,7 @@ then the simstr approach will also be clear to you.
- Parsing integers with the possibility of "fine" tuning during compilation - you can set options for checking overflow,
skipping whitespace characters, a specific radix or auto-selection by prefixes `0x`, `0`, `0b`, `0o`,
admissibility of the `+` sign. Parsing is implemented for all types of strings and characters.
- Parsing double is currently implemented by calling the standard library and only works for `char`, `wchar_t` strings and types compatible with
`wchar_t` in size.
- Parsing doubles for all types of characters.
- Minimal Unicode support is included when converting `upper`, `lower` and case-insensitive string comparison.
It only works for characters in the first plane of Unicode (up to 0xFFFF), and when changing case, it does not take into account cases where one code point
can be converted into several, that is, the case conversion of characters corresponds to `std::towupper`, `std::towlower` for the unicode locale, only faster and can work with any type of characters.

View File

@ -1,11 +1,11 @@
# simstr - библиотека строковых объектов и функций
[![CMake on multiple platforms](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml)
Версия 1.2.5.
Версия 1.2.6.
<span class="obfuscator"><a href="readme.md">On English | По-английски</a></span>
В этой библиотеке содержится реализация нескольких видов строковых объектов и различных алгоритмов для работы со строками.
В этой библиотеке содержится современная реализация нескольких видов строковых объектов и различных алгоритмов для работы со строками.
Цель библиотеки - сделать работу со строками в С++ такой же простой и лёгкой, как во множестве других языков, особенно
скриптовых, но при этом сохранив оптимальность и производительность на уровне С и C++, и даже улучшив их.
@ -47,8 +47,7 @@
- Парсинг целых чисел с возможностью "тонкой" настройки при компиляции - можно задавать опции проверки переполнения,
пропуск пробельных символов, конкретное основание счисления либо автовыбор по префиксам `0x`, `0`, `0b`, `0o`,
допустимость знака `+`. Парсинг реализован для всех видов строк и символов.
- Парсинг double пока реализован вызовом стандартной библиотеки и работает только для строк `char`, `wchar_t` и совместимых с
`wchar_t` по размеру типов.
- Парсинг double для всех типов символов.
- Содержится минимальная поддержка Unicode при преобразовании `upper`, `lower` и регистро-независимом сравнении строк.
Работает только для символов первой плоскости Unicode (до 0xFFFF), а при смене регистра не учитываются случаи, когда один code point
может преобразовываться в несколько, то есть преобразование регистра символов соответствует `std::towupper`, `std::towlower` для unicode локали, только быстрее и может работать с любым видом символов.

View File

@ -544,6 +544,7 @@ SIMSTR_API size_t unicode_traits<u32s>::hashiu(const u32s* src, size_t l) {
}
return h;
}
namespace {
// Based on simdjson sources https://github.com/simdjson/simdjson/blob/667d0ed3c77f55cbda2082b034168d69898d1f88/include/simdjson/compile_time_json-inl.h#L189
@ -1385,18 +1386,22 @@ SIMSTR_API std::optional<double> impl_to_double(const K* start, const K* end) {
return *pointer;
};
const K* srcinit = start;
bool negative = false;
if (get_value(start) == '+') {
start++;
} else {
negative = (get_value(start) == '-');
bool negative = (get_value(start) == '-');
start += (uint8_t)negative;
if (end - start == 3) {
if (!negative && (start[0] | 0x20) == K('n')) {
if ((start[1] | 0x20) == K('a') && (start[2] | 0x20) == K('n')) {
return std::nan("0");
}
start += uint8_t(negative);
uint64_t i = 0;
} else if ((start[0] | 0x20) == K('i') && (start[1] | 0x20) == K('n') && (start[2] | 0x20) == K('f')) {
return negative ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
}
}
uint64_t digits = 0;
const K* p = start;
p += parse_digit(get_value(p), i);
bool leading_zero = (i == 0);
while (parse_digit(get_value(p), i)) {
p += parse_digit(get_value(p), digits);
bool leading_zero = (digits == 0);
while (parse_digit(get_value(p), digits)) {
p++;
}
if (p == start) {
@ -1411,11 +1416,11 @@ SIMSTR_API std::optional<double> impl_to_double(const K* start, const K* end) {
if (get_value(p) == '.') {
p++;
const K* start_decimal_digits = p;
if (!parse_digit(get_value(p), i)) {
if (!parse_digit(get_value(p), digits)) {
return {};
} // no decimal digits
p++;
while (parse_digit(get_value(p), i)) {
while (parse_digit(get_value(p), digits)) {
p++;
}
exponent = -(p - start_decimal_digits);
@ -1448,11 +1453,11 @@ SIMSTR_API std::optional<double> impl_to_double(const K* start, const K* end) {
exponent += exp_neg ? 0 - exp : exp;
}
if (exponent < smallest_power || exponent > largest_power) {
if (exponent < smallest_power || exponent > largest_power || p != end) {
return {};
}
double d;
if (!compute_float_64(exponent, i, negative, d)) {
if (!compute_float_64(exponent, digits, negative, d)) {
return {};
}
return d;