372 lines
14 KiB
C++
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
|