duckhandy/include/duckhandy/implem/int_conv.hpp

372 lines
14 KiB
C++

/* Copyright 2016-2021 Michele Santullo
* This file is part of "duckhandy".
*
* "duckhandy" is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* "duckhandy" is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with "duckhandy". If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef id4754A95F12BE4ADEA65642A056A51907
#define id4754A95F12BE4ADEA65642A056A51907
#include "reversed_sized_array_bt.hpp"
#include "../has_method.hpp"
#include <sprout/math/log10.hpp>
#include <sprout/math/abs.hpp>
#include <sprout/math/ceil.hpp>
#include <cstddef>
#include <cstdint>
#include <array>
#include <limits>
#include <emmintrin.h>
#if defined(__SSE4_1__)
# include <smmintrin.h>
#endif
#if !defined(INT_CONV_WITHOUT_HELPERS)
# include <string_view>
# include <string>
#endif
#include <type_traits>
namespace dhandy {
namespace implem {
namespace {
[[gnu::always_inline]]
inline __m128i muly(const __m128i &a, const __m128i &b) {
#if defined(__SSE4_1__) // modern CPU - use SSE 4.1
return _mm_mullo_epi32(a, b);
#else // old CPU - use SSE 2
__m128i tmp1 = _mm_mul_epu32(a,b); /* mul 2,0*/
__m128i tmp2 = _mm_mul_epu32( _mm_srli_si128(a,4), _mm_srli_si128(b,4)); /* mul 3,1 */
return _mm_unpacklo_epi32(_mm_shuffle_epi32(tmp1, _MM_SHUFFLE (0,0,2,0)), _mm_shuffle_epi32(tmp2, _MM_SHUFFLE (0,0,2,0))); /* shuffle results to [63..0] and pack */
#endif
}
} //unnamed namespace
//Used for checking ASCIITranslator::AltLetter
define_has_enum(AltLetter, AltLetter);
template <typename Tr, bool=HasAltLetterEnum<Tr>::value != 0>
inline auto g_AltLetterOrZero = Tr::AltLetter;
template <typename Tr>
inline auto g_AltLetterOrZero<Tr, false> = Tr::NullChar;
template <typename T, typename C, unsigned int Base, typename Tr>
T to_integer_sse (const C* s, std::size_t l);
template <typename I, std::size_t Base>
constexpr std::size_t max_digit_count = static_cast<std::size_t>(
sprout::ceil(
sprout::log10(sprout::abs(static_cast<long double>(std::numeric_limits<I>::max()))) /
sprout::log10(static_cast<long double>(Base))
)
);
template <typename I, unsigned int Base>
struct int_info {
static_assert(Base > 1, "Invalid base");
static const constexpr bool always_unsigned = (Base == 16 or Base == 2 or Base == 36);
static const constexpr bool is_signed = std::numeric_limits<I>::is_signed and not always_unsigned;
static const constexpr std::size_t max_len = max_digit_count<typename std::conditional<always_unsigned, std::make_unsigned_t<I>, I>::type, Base> + is_signed;
};
template <typename I, unsigned int Base, typename Tr>
using RevArray = ReversedSizedArray<typename Tr::char_type, int_info<I, Base>::max_len + 1>;
template <typename I, unsigned int Base, bool Signed=int_info<I, Base>::is_signed>
struct IsNegative { static constexpr bool check (I) { return false; } };
template <typename I, unsigned int Base>
struct IsNegative<I, Base, true> { static constexpr bool check (I in) { return in < I(0); } };
template <typename I, unsigned int Base>
constexpr bool is_negative (I in) {
return IsNegative<I, Base>::check(in);
}
template <
typename I,
std::size_t Base,
bool IsSigned=std::numeric_limits<I>::is_signed,
bool ForceUnsigned=int_info<I, Base>::always_unsigned
> struct NumberAdaptation {
static const constexpr bool BecomesUnsigned = IsSigned and ForceUnsigned;
using UnsignedType = typename std::make_unsigned<I>::type;
using CastedType = typename std::conditional<BecomesUnsigned, UnsignedType, I>::type;
template <typename L>
[[gnu::pure,gnu::always_inline]]
static constexpr L abs(L v);
[[gnu::pure,gnu::always_inline]]
static constexpr CastedType cast (I in) { return static_cast<CastedType>(in); }
};
template <typename I, std::size_t Base, bool IsSigned, bool ForceUnsigned>
template <typename L>
[[gnu::const,gnu::always_inline]]
inline constexpr L NumberAdaptation<I, Base, IsSigned, ForceUnsigned>::abs(L v) {
if constexpr (not BecomesUnsigned and std::numeric_limits<L>::is_signed) {
//https://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs
const L mask = v >> (sizeof(L) * CHAR_BIT - 1);
const UnsignedType r = (v + mask) ^ mask;
return static_cast<L>(r);
}
else {
return v;
}
}
template <typename I, unsigned int Base, typename Tr, typename=void>
struct IntConversion {
static constexpr RevArray<I, Base, Tr> to_ary (I in) {
using Num = implem::NumberAdaptation<I, Base>;
const bool was_negative = implem::is_negative<I, Base>(in);
RevArray<I, Base, Tr> arr;
arr.push_front(Tr::NullChar);
do {
arr.push_front(Tr::to_digit(static_cast<int>(Num::abs(Num::cast(in)) % Base)));
in = static_cast<I>(Num::cast(in) / static_cast<I>(Base));
} while (in);
if (was_negative)
arr.push_front(Tr::Minus);
return arr;
}
};
template <unsigned int Base, typename Tr>
struct IntConversion<bool, Base, Tr, void> {
static constexpr ReversedSizedArray<typename Tr::char_type, 2> to_ary (bool in) {
ReversedSizedArray<typename Tr::char_type, 2> arr;
arr.push_front(Tr::NullChar);
arr.push_front(in ? Tr::to_digit(1) : Tr::to_digit(0));
return arr;
}
};
template <typename I, typename Tr>
struct IntConversion<I, 10, Tr, typename std::enable_if<std::is_integral<I>::value and not std::is_same<I, bool>::value>::type> {
static constexpr RevArray<I, 10, Tr> to_ary (I in) {
using RetType = RevArray<I, 10, Tr>;
using Num = implem::NumberAdaptation<I, 10>;
const bool was_negative = implem::is_negative<I, 10>(in);
RetType arr;
if (not std::is_constant_evaluated())
__builtin_prefetch(arr.base_ptr(), 1, 3);
arr.push_front(Tr::NullChar);
in = Num::abs(in);
while (in >= static_cast<I>(100)) {
const auto last_two = in % 100;
const auto digit_low = in % 10;
in = static_cast<I>(in / static_cast<I>(100));
const auto digit_high = last_two / 10;
arr.push_front(Tr::to_digit(static_cast<int>(digit_low)));
arr.push_front(Tr::to_digit(static_cast<int>(digit_high)));
};
if (in < static_cast<I>(10)) {
arr.push_front(Tr::to_digit(static_cast<int>(in)));
}
else {
const auto digit_low = in % 10;
const auto digit_high = (in / 10) % 10;
arr.push_front(Tr::to_digit(static_cast<int>(digit_low)));
arr.push_front(Tr::to_digit(static_cast<int>(digit_high)));
}
if (was_negative)
arr.push_front(Tr::Minus);
return arr;
}
};
template <typename I, I In, unsigned int Base, typename Tr>
inline constexpr const auto g_int_to_str = IntConversion<std::remove_cvref_t<I>, Base, Tr>::to_ary(In);
template <typename T>
[[gnu::always_inline,gnu::pure]]
constexpr inline T negated_ifn (T n, bool negate) {
//return static_cast<int32_t>(((static_cast<unsigned int>(n) - (mask bitand 1)) xor mask) bitor ((mask bitand 1) << 31));
return (negate ? -n : n);
}
template <typename I, unsigned int Base, typename Tr, bool Constexpr, typename=void>
struct AryConversion {
constexpr static const bool is_sse = false;
template <typename C>
constexpr static I from_ary (const C* beg, const C* end) {
I retval = 0;
I factor = 1;
std::size_t i = end - beg;
const bool was_negative = (i and *beg == Tr::Minus);
if (i and (*beg == Tr::Minus or *beg == Tr::Plus)) {
i--;
beg++;
}
while (i--) {
retval += Tr::from_digit(beg[i]) * factor;
factor *= Base;
}
return negated_ifn(retval, was_negative);
}
};
template <typename I, unsigned int Base, typename Tr>
struct AryConversion<I, Base, Tr, false, typename std::enable_if<std::is_integral<I>::value and not std::is_same<I, bool>::value and sizeof(I) <= sizeof(std::uint32_t)>::type> {
constexpr static const bool is_sse = true;
template <typename C> static I from_ary (C* beg, C* end) { return to_integer_sse<I, C, Base, Tr>(beg, end - beg); }
};
template <unsigned int Base, typename Tr, bool Constexpr>
struct AryConversion<bool, Base, Tr, Constexpr> {
constexpr static const bool is_sse = false;
template <typename C> constexpr static bool from_ary (C* beg, C* end) {
if (end == beg)
return false;
return (Tr::from_digit(*beg) ? true : false);
}
};
template <typename T, typename C, unsigned int Base, typename Tr>
[[gnu::pure]]
T to_integer_sse (const C* s, std::size_t l) {
static const constexpr int base1 = static_cast<int>(Base);
static const constexpr int base2 = base1 * base1;
static const constexpr int base3 = base1 * base1 * base1;
static const constexpr int base4 = base2 * base2;
__builtin_prefetch(s, 0);
const bool was_negative = (l and *s == Tr::Minus);
if (l and (*s == Tr::Minus or *s == Tr::Plus)) {
l--;
s++;
}
switch (l) {
case 0:
return 0;
case 1:
return negated_ifn(Tr::from_digit(*s), was_negative);
case 2:
return negated_ifn(Tr::from_digit(s[0]) * base1 + Tr::from_digit(s[1]), was_negative);
case 3:
return negated_ifn(Tr::from_digit(s[0]) * base2 + Tr::from_digit(s[1]) * base1 + Tr::from_digit(s[2]), was_negative);
default:
{
__m128i factor = _mm_set_epi32(base3, base2, base1, 1);
__m128i res = _mm_set1_epi32(0);
const __m128i char_0 = _mm_set1_epi32(Tr::FirstDigit);
const __m128i char_befo_a = _mm_set1_epi32(Tr::FirstLetter - 1);
const __m128i char_past_f = _mm_set1_epi32(Tr::FirstLetter + Base - Tr::DigitCount);
std::size_t idx = 0;
const std::size_t cap = l bitand -4;
do {
const __m128i digits = _mm_set_epi32(s[cap - idx - 3 - 1], s[cap - idx - 2 - 1], s[cap - idx - 1 - 1], s[cap - idx - 0 - 1]);
__m128i mask = _mm_and_si128(_mm_cmpgt_epi32(digits, char_befo_a), _mm_cmplt_epi32(digits, char_past_f));
__m128i offs = _mm_and_si128(mask, _mm_set1_epi32(Tr::FirstLetter - Tr::DigitCount));
if constexpr (HasAltLetterEnum<Tr>::value) {
const __m128i char_befo_A = _mm_set1_epi32(g_AltLetterOrZero<Tr> - 1);
const __m128i char_past_F = _mm_set1_epi32(g_AltLetterOrZero<Tr> + Base - Tr::DigitCount);
const __m128i alt_mask = _mm_and_si128(_mm_cmpgt_epi32(digits, char_befo_A), _mm_cmplt_epi32(digits, char_past_F));
const __m128i alt_offs = _mm_and_si128(alt_mask, _mm_set1_epi32(g_AltLetterOrZero<Tr> - Tr::DigitCount));
offs = _mm_add_epi32(alt_offs, offs);
mask = _mm_or_si128(mask, alt_mask);
}
const __m128i addend = _mm_add_epi32(offs, _mm_andnot_si128(mask, char_0));
res = _mm_add_epi32(res, muly(_mm_sub_epi32(digits, addend), factor));
factor = muly(factor, _mm_set1_epi32(base4));
idx += 4;
} while (l - idx > 3);
{
res = _mm_add_epi32(res, _mm_srli_si128(res, 8));
res = _mm_add_epi32(res, _mm_srli_si128(res, 4));
constexpr const std::array<int, 4> scale {1, base1, base2, base3};
return negated_ifn(to_integer_sse<T, C, Base, Tr>(s + idx, l - idx) + _mm_cvtsi128_si32(res) * scale[l - idx], was_negative);
}
}
}
}
} //namespace implem
template <typename C, C FDigit='0', C FLetter='a', C CPlus='+', C CMinus='-', C CNull='\0', unsigned int DCount=10>
struct ASCIITranslator {
typedef C char_type;
static const constexpr C FirstDigit = FDigit;
static const constexpr C FirstLetter = FLetter;
//static const constexpr C AltLetter = FAltLetter;
static const constexpr C Plus = CPlus;
static const constexpr C Minus = CMinus;
static const constexpr C NullChar = CNull;
static const constexpr unsigned int DigitCount = DCount;
static constexpr C to_digit (unsigned int num) {
constexpr const C ret[2] = {FirstDigit, FirstLetter - DigitCount};
#if 0
//https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching
const bool f = num >= DigitCount;
constexpr const unsigned int m = 1;
const unsigned int w = (-f & m);
#else
const auto w = (DigitCount - 1 - num) >> (sizeof(unsigned int) * CHAR_BIT - 1);
#endif
return num + ret[w];
}
static constexpr int from_digit (C dig) {
//return (dig < FirstLetter ? dig - FirstDigit : dig - FirstLetter + DigitCount);
typedef typename std::make_unsigned<C>::type UC;
constexpr const C ret[2] = {FirstLetter - DigitCount, FirstDigit};
const auto w = static_cast<UC>(dig - FirstLetter) >> (sizeof(UC) * CHAR_BIT - 1);
return dig - ret[w];
}
};
template <typename C>
using ASCIITranslatorUpcase = ASCIITranslator<C, '0', 'A'>;
template <typename I, I In, unsigned int Base=10, typename Tr=ASCIITranslator<char>>
constexpr inline const auto& buildtime_int_to_ary() {
return implem::g_int_to_str<I, In, Base, Tr>;
}
template <typename I, unsigned int Base=10, typename Tr=ASCIITranslator<char>>
constexpr inline auto int_to_ary (I in) {
return implem::IntConversion<std::remove_cvref_t<I>, Base, Tr>::to_ary(in);
}
template <typename R, typename C, unsigned int Base=10, typename Tr=ASCIITranslator<C>>
constexpr inline R ary_to_int (const C* beg, const C* end) {
if (std::is_constant_evaluated())
return implem::AryConversion<R, Base, Tr, true>::from_ary(beg, end);
else
return implem::AryConversion<R, Base, Tr, false>::from_ary(beg, end);
}
#if !defined(INT_CONV_WITHOUT_HELPERS)
template <typename I>
std::string to_string (I num) {
return std::string(int_to_ary(num).to_string_view());
}
template <typename C, std::size_t S>
inline
std::basic_string<C> operator+ (std::basic_string<C> a, const ReversedSizedArray<C, S>& b) {
a.append(b.data(), b.size() - 1);
return a;
}
#endif
} //namespace dhandy
#endif