Skip to content

Commit

Permalink
Add support for built-in __int128 when available
Browse files Browse the repository at this point in the history
  • Loading branch information
denizevrenci committed Sep 2, 2019
1 parent 534a10a commit cc5456c
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 11 deletions.
2 changes: 1 addition & 1 deletion include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ struct chrono_formatter {
void write(Rep value, int width) {
write_sign();
if (isnan(value)) return write_nan();
uint32_or_64_t<int> n = to_unsigned(
uint32_or_64_or_128_t<int> n = to_unsigned(
to_nonnegative_int(value, (std::numeric_limits<int>::max)()));
int num_digits = internal::count_digits(n);
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
Expand Down
36 changes: 36 additions & 0 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@ enum type {
uint_type,
long_long_type,
ulong_long_type,
int128_type,
uint128_type,
bool_type,
char_type,
last_integer_type = char_type,
Expand All @@ -669,6 +671,13 @@ FMT_TYPE_CONSTANT(int, int_type);
FMT_TYPE_CONSTANT(unsigned, uint_type);
FMT_TYPE_CONSTANT(long long, long_long_type);
FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type);
#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
FMT_TYPE_CONSTANT(__int128, int128_type);
FMT_TYPE_CONSTANT(unsigned __int128, uint128_type);
#pragma GCC diagnostic pop
#endif
FMT_TYPE_CONSTANT(bool, bool_type);
FMT_TYPE_CONSTANT(Char, char_type);
FMT_TYPE_CONSTANT(double, double_type);
Expand Down Expand Up @@ -708,6 +717,13 @@ template <typename Context> class value {
unsigned uint_value;
long long long_long_value;
unsigned long long ulong_long_value;
#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
__int128 int128_value;
unsigned __int128 uint128_value;
#pragma GCC diagnostic pop
#endif
bool bool_value;
char_type char_value;
double double_value;
Expand All @@ -722,6 +738,13 @@ template <typename Context> class value {
FMT_CONSTEXPR value(unsigned val) : uint_value(val) {}
value(long long val) : long_long_value(val) {}
value(unsigned long long val) : ulong_long_value(val) {}
#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
value(__int128 val) : int128_value(val) {}
value(unsigned __int128 val) : uint128_value(val) {}
#pragma GCC diagnostic pop
#endif
value(double val) : double_value(val) {}
value(long double val) : long_double_value(val) {}
value(bool val) : bool_value(val) {}
Expand Down Expand Up @@ -781,6 +804,13 @@ template <typename Context> struct arg_mapper {
FMT_CONSTEXPR ulong_type map(unsigned long val) { return val; }
FMT_CONSTEXPR long long map(long long val) { return val; }
FMT_CONSTEXPR unsigned long long map(unsigned long long val) { return val; }
#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
FMT_CONSTEXPR __int128 map(__int128 val) { return val; }
FMT_CONSTEXPR unsigned __int128 map(unsigned __int128 val) { return val; }
#pragma GCC diagnostic pop
#endif
FMT_CONSTEXPR bool map(bool val) { return val; }

template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
Expand Down Expand Up @@ -941,6 +971,12 @@ FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
return vis(arg.value_.long_long_value);
case internal::ulong_long_type:
return vis(arg.value_.ulong_long_value);
#if FMT_USE_INT128
case internal::int128_type:
return vis(arg.value_.int128_value);
case internal::uint128_type:
return vis(arg.value_.uint128_value);
#endif
case internal::bool_type:
return vis(arg.value_.bool_value);
case internal::char_type:
Expand Down
2 changes: 1 addition & 1 deletion include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ FMT_FUNC void format_error_code(internal::buffer<char>& out, int error_code,
static const char ERROR_STR[] = "error ";
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
auto abs_value = static_cast<uint32_or_64_t<int>>(error_code);
auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);
if (internal::is_negative(error_code)) {
abs_value = 0 - abs_value;
++error_code_size;
Expand Down
92 changes: 86 additions & 6 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -636,11 +636,23 @@ FMT_CONSTEXPR bool is_negative(T) {
return false;
}

#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
// Smallest of uint32_t, uint64_t, unsigned __int128 that is large enough to represent all
// values of T.
template <typename T>
using uint32_or_64_or_128_t =
conditional_t<std::numeric_limits<T>::digits <= 32, uint32_t,
conditional_t<std::numeric_limits<T>::digits <= 64, uint64_t, unsigned __int128>>;
#pragma GCC diagnostic pop
#else
// Smallest of uint32_t and uint64_t that is large enough to represent all
// values of T.
template <typename T>
using uint32_or_64_t =
using uint32_or_64_or_128_t =
conditional_t<std::numeric_limits<T>::digits <= 32, uint32_t, uint64_t>;
#endif

// Static data is placed in this class template for the header-only config.
template <typename T = void> struct FMT_EXTERN_TEMPLATE_API basic_data {
Expand Down Expand Up @@ -689,6 +701,26 @@ inline int count_digits(uint64_t n) {
}
#endif

#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
inline int count_digits(unsigned __int128 n) {
int count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
if (n < 10) return count;
if (n < 100) return count + 1;
if (n < 1000) return count + 2;
if (n < 10000) return count + 3;
n /= 10000u;
count += 4;
}
}
#pragma GCC diagnostic pop
#endif

// Counts the number of digits in n. BITS = log2(radix).
template <unsigned BITS, typename UInt> inline int count_digits(UInt n) {
int num_digits = 0;
Expand Down Expand Up @@ -818,12 +850,32 @@ inline Char* format_decimal(Char* buffer, UInt value, int num_digits,
return end;
}

template <typename Int>
constexpr int digits10() noexcept {
return std::numeric_limits<Int>::digits10;
}

#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
template <>
constexpr int digits10<__int128>() noexcept {
return 38;
}

template <>
constexpr int digits10<unsigned __int128>() noexcept {
return 38;
}
#pragma GCC diagnostic pop
#endif

template <typename Char, typename UInt, typename Iterator, typename F>
inline Iterator format_decimal(Iterator out, UInt value, int num_digits,
F add_thousands_sep) {
FMT_ASSERT(num_digits >= 0, "invalid digit count");
// Buffer should be large enough to hold all digits (<= digits10 + 1).
enum { max_size = std::numeric_limits<UInt>::digits10 + 1 };
enum { max_size = digits10<UInt>() + 1 };
Char buffer[max_size + max_size / 3];
auto end = format_decimal(buffer, value, num_digits, add_thousands_sep);
return internal::copy_str<Char>(buffer, end, out);
Expand Down Expand Up @@ -1324,7 +1376,7 @@ template <typename Range> class basic_writer {

// Writes a decimal integer.
template <typename Int> void write_decimal(Int value) {
auto abs_value = static_cast<uint32_or_64_t<Int>>(value);
auto abs_value = static_cast<uint32_or_64_or_128_t<Int>>(value);
bool is_negative = internal::is_negative(value);
if (is_negative) abs_value = 0 - abs_value;
int num_digits = internal::count_digits(abs_value);
Expand All @@ -1336,7 +1388,7 @@ template <typename Range> class basic_writer {

// The handle_int_type_spec handler that writes an integer.
template <typename Int, typename Specs> struct int_writer {
using unsigned_type = uint32_or_64_t<Int>;
using unsigned_type = uint32_or_64_or_128_t<Int>;

basic_writer<Range>& writer;
const Specs& specs;
Expand Down Expand Up @@ -1608,10 +1660,22 @@ template <typename Range> class basic_writer {
void write(int value) { write_decimal(value); }
void write(long value) { write_decimal(value); }
void write(long long value) { write_decimal(value); }
#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
void write(__int128 value) { write_decimal(value); }
#pragma GCC diagnostic pop
#endif

void write(unsigned value) { write_decimal(value); }
void write(unsigned long value) { write_decimal(value); }
void write(unsigned long long value) { write_decimal(value); }
#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
void write(unsigned __int128 value) { write_decimal(value); }
#pragma GCC diagnostic pop
#endif

// Writes a formatted integer.
template <typename T, typename Spec>
Expand Down Expand Up @@ -1694,6 +1758,20 @@ template <typename Range> class basic_writer {

using writer = basic_writer<buffer_range<char>>;

template <typename T>
struct is_integral_s : std::is_integral<T> {};

#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
template <>
struct is_integral_s<__int128> : std::true_type {};

template <>
struct is_integral_s<unsigned __int128> : std::true_type {};
#pragma GCC diagnostic pop
#endif

template <typename Range, typename ErrorHandler = internal::error_handler>
class arg_formatter_base {
public:
Expand Down Expand Up @@ -1756,7 +1834,7 @@ class arg_formatter_base {
return out();
}

template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
template <typename T, FMT_ENABLE_IF(is_integral_s<T>::value)>
iterator operator()(T value) {
if (specs_)
writer_.write_int(value, *specs_);
Expand Down Expand Up @@ -1888,7 +1966,7 @@ template <typename Context> class custom_formatter {

template <typename T>
using is_integer =
bool_constant<std::is_integral<T>::value && !std::is_same<T, bool>::value &&
bool_constant<is_integral_s<T>::value && !std::is_same<T, bool>::value &&
!std::is_same<T, char>::value &&
!std::is_same<T, wchar_t>::value>;

Expand Down Expand Up @@ -2947,6 +3025,8 @@ struct formatter<T, Char,
case internal::uint_type:
case internal::long_long_type:
case internal::ulong_long_type:
case internal::int128_type:
case internal::uint128_type:
case internal::bool_type:
handle_int_type_spec(specs_.type,
internal::int_type_checker<decltype(eh)>(eh));
Expand Down
2 changes: 1 addition & 1 deletion include/fmt/printf.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ template <typename Char> class printf_width_handler {

template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) {
auto width = static_cast<uint32_or_64_t<T>>(value);
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (internal::is_negative(value)) {
specs_.align = align::left;
width = 0 - width;
Expand Down
11 changes: 11 additions & 0 deletions test/format-impl-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ template <typename T> struct value_extractor {
template <typename U> FMT_NORETURN T operator()(U) {
throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name()));
}

#ifdef __apple_build_version__
// Apple Clang does not define typeid for __int128.
FMT_NORETURN T operator()(__int128) {
throw std::runtime_error(fmt::format("invalid type {}", "__int128"));
}

FMT_NORETURN T operator()(unsigned __int128) {
throw std::runtime_error(fmt::format("invalid type {}", "unsigned __int128"));
}
#endif
};

TEST(FormatTest, ArgConverter) {
Expand Down
11 changes: 9 additions & 2 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1923,7 +1923,14 @@ using buffer_range = fmt::buffer_range<char>;
class mock_arg_formatter
: public fmt::internal::arg_formatter_base<buffer_range> {
private:
#if FMT_USE_INT128
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
MOCK_METHOD1(call, void(__int128 value));
#pragma GCC diagnostic pop
#else
MOCK_METHOD1(call, void(long long value));
#endif

public:
typedef fmt::internal::arg_formatter_base<buffer_range> base;
Expand All @@ -1936,14 +1943,14 @@ class mock_arg_formatter
}

template <typename T>
typename std::enable_if<std::is_integral<T>::value, iterator>::type
typename std::enable_if<fmt::internal::is_integral_s<T>::value, iterator>::type
operator()(T value) {
call(value);
return base::operator()(value);
}

template <typename T>
typename std::enable_if<!std::is_integral<T>::value, iterator>::type
typename std::enable_if<!fmt::internal::is_integral_s<T>::value, iterator>::type
operator()(T value) {
return base::operator()(value);
}
Expand Down

0 comments on commit cc5456c

Please sign in to comment.