numeric/str: Add constexpr int decode
This makes it possible to convert strings to numbers in a constexpr
context.
Change-Id: I051ce8600b643533cc25a8de06e15c917db9fbd7
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/numeric/str.hpp b/include/stdplus/numeric/str.hpp
index fa5ba86..680ceb7 100644
--- a/include/stdplus/numeric/str.hpp
+++ b/include/stdplus/numeric/str.hpp
@@ -7,6 +7,7 @@
#include <concepts>
#include <cstdint>
#include <limits>
+#include <stdexcept>
#include <string>
#include <type_traits>
@@ -130,6 +131,104 @@
return uintToStr<base>(buf, std::make_unsigned_t<T>(v), min_width);
}
+inline constexpr auto charTable = []() {
+ std::array<int8_t, 256> ret;
+ std::fill(ret.begin(), ret.end(), -1);
+ for (int8_t i = 0; i < 10; ++i)
+ {
+ ret[i + '0'] = i;
+ }
+ for (int8_t i = 0; i < 26; ++i)
+ {
+ ret[i + 'A'] = i + 10;
+ ret[i + 'a'] = i + 10;
+ }
+ return ret;
+}();
+
+template <uint8_t base, typename T, typename CharT>
+constexpr T strToUInt(std::basic_string_view<CharT> str)
+{
+ static_assert(std::is_unsigned_v<T>);
+ if (str.empty())
+ {
+ throw std::invalid_argument("Empty Str");
+ }
+ constexpr auto max = std::numeric_limits<T>::max();
+ T ret = 0;
+ for (auto c : str)
+ {
+ constexpr auto cmax = 1 << (sizeof(CharT) << 3);
+ if constexpr (detail::charTable.size() < cmax)
+ {
+ if (detail::charTable.size() <= c)
+ {
+ throw std::invalid_argument("Invalid numeral");
+ }
+ }
+ auto v = detail::charTable[c];
+ if (v < 0 || v >= base)
+ {
+ throw std::invalid_argument("Invalid numeral");
+ }
+ if constexpr (std::popcount(base) == 1)
+ {
+ constexpr auto shift = std::countr_zero(base);
+ constexpr auto maxshift = max >> shift;
+ if (ret > maxshift)
+ {
+ throw std::overflow_error("Integer Decode Overflow");
+ }
+ ret = (ret << shift) | v;
+ }
+ else
+ {
+ constexpr auto maxbase = max / base;
+ if (ret > maxbase)
+ {
+ throw std::overflow_error("Integer Decode Overflow");
+ }
+ ret *= base;
+ if (max - v < ret)
+ {
+ throw std::overflow_error("Integer Decode Overflow");
+ }
+ ret += v;
+ }
+ }
+ return ret;
+}
+
+template <uint8_t base, typename T, typename CharT>
+constexpr T strToUInt0(std::basic_string_view<CharT> str)
+{
+ if constexpr (base > 0)
+ {
+ return strToUInt<base, T>(str);
+ }
+ if (str.starts_with("0x"))
+ {
+ return strToUInt<16, T>(str.substr(2));
+ }
+ return strToUInt<10, T>(str);
+}
+
+template <uint8_t base, std::integral T, typename CharT>
+constexpr T strToInt(std::basic_string_view<CharT> str)
+{
+ if constexpr (std::is_unsigned_v<T>)
+ {
+ return strToUInt0<base, T>(str);
+ }
+ bool is_neg = str.starts_with('-');
+ auto ret = strToUInt0<base, std::make_unsigned_t<T>>(str.substr(is_neg));
+ if (ret > std::numeric_limits<T>::max())
+ {
+ throw std::overflow_error("Integer Decode Overflow");
+ }
+ return is_neg ? -static_cast<T>(ret) : ret;
+}
+
} // namespace detail
template <uint8_t base, std::integral T>
@@ -167,4 +266,31 @@
using type = T;
};
+template <uint8_t base, std::integral T>
+struct StrToInt
+{
+ static_assert(base <= detail::maxBase);
+
+ template <typename CharT>
+ constexpr T operator()(std::basic_string_view<CharT> str) const
+ {
+ using ptr_t =
+ std::conditional_t<std::is_signed_v<T>, intptr_t, uintptr_t>;
+ auto ret = detail::strToInt<
+ base, std::conditional_t<sizeof(T) <= sizeof(ptr_t), ptr_t, T>>(
+ str);
+ if (ret > std::numeric_limits<T>::max())
+ {
+ throw std::overflow_error("Integer Decode");
+ }
+ return ret;
+ }
+};
+
+template <std::integral T>
+struct FromStr<T> : StrToInt<0, T>
+{
+ using type = T;
+};
+
} // namespace stdplus
diff --git a/src/numeric/str.cpp b/src/numeric/str.cpp
index 7c3ac13..81e936d 100644
--- a/src/numeric/str.cpp
+++ b/src/numeric/str.cpp
@@ -4,4 +4,6 @@
{
template char* uintToStr<16>(char*, uintptr_t, uint8_t) noexcept;
template char* uintToStr<10>(char*, uintptr_t, uint8_t) noexcept;
+template uintptr_t strToUInt<16>(std::string_view);
+template uintptr_t strToUInt<10>(std::string_view);
} // namespace stdplus::detail
diff --git a/test/numeric/str.cpp b/test/numeric/str.cpp
index 008cbe5..cedc87c 100644
--- a/test/numeric/str.cpp
+++ b/test/numeric/str.cpp
@@ -4,6 +4,8 @@
#include <gtest/gtest.h>
+using std::literals::string_view_literals::operator""sv;
+
namespace stdplus
{
@@ -100,4 +102,95 @@
EXPECT_TRUE(false);
}
+TEST(StrToInt, Uint8_10)
+{
+ StrToInt<10, uint8_t> dec;
+ EXPECT_EQ(42, dec("42"sv));
+ EXPECT_EQ(255, dec("255"sv));
+ EXPECT_THROW(dec(""sv), std::invalid_argument);
+ EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+ EXPECT_THROW(dec("a0"sv), std::invalid_argument);
+ EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+ EXPECT_THROW(dec("257"sv), std::overflow_error);
+ EXPECT_THROW(dec("300"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Uint8_11)
+{
+ StrToInt<11, uint8_t> dec;
+ EXPECT_EQ(112, dec("a2"sv));
+ EXPECT_EQ(255, dec("212"sv));
+ EXPECT_THROW(dec(""sv), std::invalid_argument);
+ EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+ EXPECT_THROW(dec("b0"sv), std::invalid_argument);
+ EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+ EXPECT_THROW(dec("213"sv), std::overflow_error);
+ EXPECT_THROW(dec("300"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Uint16_16)
+{
+ StrToInt<16, uint16_t> dec;
+ EXPECT_EQ(0x42, dec("42"sv));
+ EXPECT_EQ(0xfacf, dec("facf"sv));
+ EXPECT_THROW(dec(""sv), std::invalid_argument);
+ EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+ EXPECT_THROW(dec("g0"sv), std::invalid_argument);
+ EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+ EXPECT_THROW(dec("10000"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Uint16_8)
+{
+ StrToInt<8, uint16_t> dec;
+ EXPECT_EQ(042, dec("42"sv));
+ EXPECT_EQ(0177777, dec("177777"sv));
+ EXPECT_THROW(dec(""sv), std::invalid_argument);
+ EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+ EXPECT_THROW(dec("g0"sv), std::invalid_argument);
+ EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+ EXPECT_THROW(dec("277777"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Int8_16)
+{
+ StrToInt<16, int8_t> dec;
+ EXPECT_EQ(-1, dec("-1"sv));
+ EXPECT_EQ(0x42, dec("42"sv));
+ EXPECT_EQ(-0x7f, dec("-7f"sv));
+ EXPECT_THROW(dec(""sv), std::invalid_argument);
+ EXPECT_THROW(dec("--1"sv), std::invalid_argument);
+ EXPECT_THROW(dec("g0"sv), std::invalid_argument);
+ EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+ EXPECT_THROW(dec("ff"sv), std::overflow_error);
+ EXPECT_THROW(dec("10000"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Int8_0)
+{
+ StrToInt<0, int8_t> dec;
+ EXPECT_EQ(-1, dec("-1"sv));
+ EXPECT_EQ(42, dec("42"sv));
+ EXPECT_EQ(0x42, dec("0x42"sv));
+ EXPECT_EQ(-42, dec("-42"sv));
+ EXPECT_EQ(-0x42, dec("-0x42"sv));
+ EXPECT_THROW(dec(""sv), std::invalid_argument);
+ EXPECT_THROW(dec("--1"sv), std::invalid_argument);
+ EXPECT_THROW(dec("ac"sv), std::invalid_argument);
+ EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+ EXPECT_THROW(dec("0xff"sv), std::overflow_error);
+ EXPECT_THROW(dec("10000"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Perf)
+{
+ GTEST_SKIP();
+ StrToInt<16, size_t> dec;
+ for (size_t i = 0; i < 100000000; ++i)
+ {
+ dec("53036893"sv);
+ }
+ EXPECT_TRUE(false);
+}
+
} // namespace stdplus