ClickHouse/dbms/src/IO/WriteIntText.h
2018-12-26 03:31:51 +03:00

223 lines
5.4 KiB
C++

#pragma once
#include <limits>
#include <type_traits>
#include <common/likely.h>
#include <Core/Types.h>
#include <IO/WriteBuffer.h>
/// 20 digits or 19 digits and a sign
#define WRITE_HELPERS_MAX_INT_WIDTH 20U
namespace DB
{
namespace detail
{
/** See:
* https://github.com/localvoid/cxx-benchmark-itoa
* http://www.slideshare.net/andreialexandrescu1/three-optimization-tips-for-c-15708507
* http://vimeo.com/55639112
*/
/// The usual way, if there is not enough space in the buffer for any number to fit there.
template <typename T>
void writeUIntTextFallback(T x, WriteBuffer & buf)
{
if (x == 0)
{
buf.nextIfAtEnd();
*buf.position() = '0';
++buf.position();
return;
}
char tmp[WRITE_HELPERS_MAX_INT_WIDTH];
char * pos;
for (pos = tmp + WRITE_HELPERS_MAX_INT_WIDTH - 1; x != 0; --pos)
{
*pos = '0' + x % 10;
x /= 10;
}
++pos;
buf.write(pos, tmp + WRITE_HELPERS_MAX_INT_WIDTH - pos);
}
/** Count the number of decimal digits in the number.
* Works well for nonuniform distribution of numbers, which usually happens.
* If most of the numbers are long, then a "branchless" code with a `bsr` instruction and a smart conversion would work better.
*/
template <typename T>
UInt32 digits10(T x)
{
if (x < 10ULL)
return 1;
if (x < 100ULL)
return 2;
if (x < 1000ULL)
return 3;
if (x < 1000000000000ULL)
{
if (x < 100000000ULL)
{
if (x < 1000000ULL)
{
if (x < 10000ULL)
return 4;
else
return 5 + (x >= 100000ULL);
}
return 7 + (x >= 10000000ULL);
}
if (x < 10000000000ULL)
return 9 + (x >= 1000000000ULL);
return 11 + (x >= 100000000000ULL);
}
return 12 + digits10(x / 1000000000000ULL);
}
/** Converts two digits per iteration.
* Works well for the nonuniform distribution of numbers, which usually happens.
* If most of the numbers are long, and if you do care about the cache, then the variant
* with a large table and four digits per iteration.
*/
template <typename T>
UInt32 writeUIntText(T x, char * dst)
{
static const char digits[201] =
"00010203040506070809"
"10111213141516171819"
"20212223242526272829"
"30313233343536373839"
"40414243444546474849"
"50515253545556575859"
"60616263646566676869"
"70717273747576777879"
"80818283848586878889"
"90919293949596979899";
const UInt32 length = digits10(x);
UInt32 next = length - 1;
while (x >= 100)
{
const auto i = (x % 100) * 2;
x /= 100;
dst[next] = digits[i + 1];
dst[next - 1] = digits[i];
next -= 2;
}
if (x < 10)
{
dst[next] = '0' + x;
}
else
{
const auto i = x * 2;
dst[next] = digits[i + 1];
dst[next - 1] = digits[i];
}
return length;
}
/** If there is enough space in the buffer - calls an optimized version, otherwise - the normal version.
*/
template <typename T>
void writeUIntText(T x, WriteBuffer & buf)
{
if (likely(buf.position() + WRITE_HELPERS_MAX_INT_WIDTH < buf.buffer().end()))
buf.position() += writeUIntText(x, buf.position());
else
writeUIntTextFallback(x, buf);
}
inline void writeLeadingMinus(WriteBuffer & buf)
{
buf.nextIfAtEnd();
*buf.position() = '-';
++buf.position();
}
/** Wrapper for signed numbers.
*/
template <typename T>
void writeSIntText(T x, WriteBuffer & buf)
{
/// A special case for the smallest negative number
if (unlikely(x == std::numeric_limits<T>::min()))
{
if (sizeof(x) == 1)
buf.write("-128", 4);
else if (sizeof(x) == 2)
buf.write("-32768", 6);
else if (sizeof(x) == 4)
buf.write("-2147483648", 11);
else
buf.write("-9223372036854775808", 20);
return;
}
if (x < 0)
{
x = -x;
writeLeadingMinus(buf);
}
writeUIntText(static_cast<std::make_unsigned_t<T>>(x), buf);
}
inline void writeSIntText(__int128 x, WriteBuffer & buf)
{
static const __int128 min_int128 = __int128(0x8000000000000000ll) << 64;
if (unlikely(x == min_int128))
{
buf.write("-170141183460469231731687303715884105728", 40);
return;
}
if (x < 0)
{
x = -x;
writeLeadingMinus(buf);
}
writeUIntText(static_cast<unsigned __int128>(x), buf);
}
}
template <typename T>
std::enable_if_t<std::is_signed_v<T> || std::is_same_v<T, Int128>, void> writeIntText(T x, WriteBuffer & buf)
{
detail::writeSIntText(x, buf);
}
template <typename T>
std::enable_if_t<std::is_unsigned_v<T>, void> writeIntText(T x, WriteBuffer & buf)
{
detail::writeUIntText(x, buf);
}
}