[libc-commits] [libc] b555912 - [libc] Better IntegerToString API

Guillaume Chatelet via libc-commits libc-commits at lists.llvm.org
Wed Aug 9 00:33:50 PDT 2023


Author: Guillaume Chatelet
Date: 2023-08-09T07:33:29Z
New Revision: b555912e705924630de1f65b77d0139a20b5338b

URL: https://github.com/llvm/llvm-project/commit/b555912e705924630de1f65b77d0139a20b5338b
DIFF: https://github.com/llvm/llvm-project/commit/b555912e705924630de1f65b77d0139a20b5338b.diff

LOG: [libc] Better IntegerToString API

This patch is an alternative to D155902. It provides the following benefits:
 - No buffer manual allocation and error handling for the general case
 - More flexible API : width specifier, sign and prefix handling
 - Simpler code

The more flexible API removes the need for manually tweaking the buffer afterwards, and so prevents relying on implementation details of IntegerToString.

Reviewed By: michaelrj, jhuber6

Differential Revision: https://reviews.llvm.org/D156981

Added: 
    

Modified: 
    libc/src/__support/CMakeLists.txt
    libc/src/__support/CPP/string.h
    libc/src/__support/CPP/stringstream.h
    libc/src/__support/FPUtil/fpbits_str.h
    libc/src/__support/StringUtil/error_to_string.cpp
    libc/src/__support/StringUtil/signal_to_string.cpp
    libc/src/__support/integer_to_string.h
    libc/src/__support/libc_assert.h
    libc/src/__support/threads/linux/thread.cpp
    libc/src/stdio/printf_core/float_dec_converter.h
    libc/src/stdio/printf_core/int_converter.h
    libc/test/UnitTest/LibcTest.cpp
    libc/test/UnitTest/TestLogger.cpp
    libc/test/src/__support/integer_to_string_test.cpp
    utils/bazel/llvm-project-overlay/libc/BUILD.bazel

Removed: 
    


################################################################################
diff  --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index 02b444905cf1a1..74021093ae083f 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -90,10 +90,11 @@ add_header_library(
   HDRS
     integer_to_string.h
   DEPENDS
+    libc.src.__support.common
+    libc.src.__support.CPP.limits
     libc.src.__support.CPP.span
     libc.src.__support.CPP.string_view
     libc.src.__support.CPP.type_traits
-    libc.src.__support.common
 )
 
 

diff  --git a/libc/src/__support/CPP/string.h b/libc/src/__support/CPP/string.h
index 239fa18eac36d3..be7ed41ffcdce1 100644
--- a/libc/src/__support/CPP/string.h
+++ b/libc/src/__support/CPP/string.h
@@ -195,10 +195,8 @@ LIBC_INLINE string operator+(const char *lhs, const string &rhs) {
 
 namespace internal {
 template <typename T> string to_dec_string(T value) {
-  char dec_buf[IntegerToString::dec_bufsize<T>()];
-  auto maybe_string_view = IntegerToString::dec(value, dec_buf);
-  const auto &string_view = *maybe_string_view;
-  return string(string_view.data(), string_view.size());
+  const IntegerToString<T> buffer(value);
+  return buffer.view();
 }
 } // namespace internal
 

diff  --git a/libc/src/__support/CPP/stringstream.h b/libc/src/__support/CPP/stringstream.h
index 5cae89d41e7d5f..089483147e6a57 100644
--- a/libc/src/__support/CPP/stringstream.h
+++ b/libc/src/__support/CPP/stringstream.h
@@ -9,8 +9,8 @@
 #ifndef LLVM_LIBC_SRC_SUPPORT_CPP_STRINGSTREAM_H
 #define LLVM_LIBC_SRC_SUPPORT_CPP_STRINGSTREAM_H
 
-#include "string_view.h"
 #include "span.h"
+#include "string_view.h"
 #include "type_traits.h"
 
 #include "src/__support/integer_to_string.h"
@@ -58,11 +58,8 @@ class StringStream {
   // Write the |val| as string.
   template <typename T, enable_if_t<is_integral_v<T>, int> = 0>
   StringStream &operator<<(T val) {
-    char buffer[IntegerToString::dec_bufsize<T>()];
-    auto int_to_str = IntegerToString::dec(val, buffer);
-    if (int_to_str)
-      return operator<<(*int_to_str);
-    return *this;
+    const IntegerToString<T> buffer(val);
+    return *this << buffer.view();
   }
 
   template <typename T, enable_if_t<is_floating_point_v<T>, int> = 0>

diff  --git a/libc/src/__support/FPUtil/fpbits_str.h b/libc/src/__support/FPUtil/fpbits_str.h
index 475b582d050821..0a1041e15221d7 100644
--- a/libc/src/__support/FPUtil/fpbits_str.h
+++ b/libc/src/__support/FPUtil/fpbits_str.h
@@ -18,6 +18,15 @@
 
 namespace __llvm_libc {
 
+namespace details {
+
+// Format T as uppercase hexadecimal number with leading zeros.
+template <typename T>
+using ZeroPaddedHexFmt = IntegerToString<
+    T, typename radix::Hex::WithWidth<(sizeof(T) * 2)>::WithPrefix::Uppercase>;
+
+} // namespace details
+
 // Converts the bits to a string in the following format:
 //    "0x<NNN...N> = S: N, E: 0xNNNN, M:0xNNN...N"
 // 1. N is a hexadecimal digit.
@@ -33,36 +42,31 @@ template <typename T> LIBC_INLINE cpp::string str(fputil::FPBits<T> x) {
   if (x.is_inf())
     return x.get_sign() ? "(-Infinity)" : "(+Infinity)";
 
-  auto zerofill = [](char *arr, size_t n) {
-    for (size_t i = 0; i < n; ++i)
-      arr[i] = '0';
-  };
+  const auto sign_char = [](bool sign) -> char { return sign ? '1' : '0'; };
+
+  cpp::string s;
 
-  cpp::string s("0x");
-  char bitsbuf[IntegerToString::hex_bufsize<UIntType>()];
-  zerofill(bitsbuf, sizeof(bitsbuf));
-  IntegerToString::hex(x.bits, bitsbuf, false);
-  s += cpp::string(bitsbuf, sizeof(bitsbuf));
+  const details::ZeroPaddedHexFmt<UIntType> bits(x.bits);
+  s += bits.view();
 
-  s += " = (";
-  s += cpp::string("S: ") + (x.get_sign() ? "1" : "0");
+  s += " = (S: ";
+  s += sign_char(x.get_sign());
 
-  char expbuf[IntegerToString::hex_bufsize<uint16_t>()];
-  zerofill(expbuf, sizeof(expbuf));
-  IntegerToString::hex(x.get_unbiased_exponent(), expbuf, false);
-  s += cpp::string(", E: 0x") + cpp::string(expbuf, sizeof(expbuf));
+  s += ", E: ";
+  const details::ZeroPaddedHexFmt<uint16_t> exponent(x.get_unbiased_exponent());
+  s += exponent.view();
 
   if constexpr (cpp::is_same_v<T, long double> &&
                 fputil::FloatProperties<long double>::MANTISSA_WIDTH == 63) {
-    s += cpp::string(", I: ") + (x.get_implicit_bit() ? "1" : "0");
+    s += ", I: ";
+    s += sign_char(x.get_implicit_bit());
   }
 
-  char mantbuf[IntegerToString::hex_bufsize<UIntType>()] = {'0'};
-  zerofill(mantbuf, sizeof(mantbuf));
-  IntegerToString::hex(x.get_mantissa(), mantbuf, false);
-  s += cpp::string(", M: 0x") + cpp::string(mantbuf, sizeof(mantbuf));
+  s += ", M: ";
+  const details::ZeroPaddedHexFmt<UIntType> mantissa(x.get_mantissa());
+  s += mantissa.view();
 
-  s += ")";
+  s += ')';
   return s;
 }
 

diff  --git a/libc/src/__support/StringUtil/error_to_string.cpp b/libc/src/__support/StringUtil/error_to_string.cpp
index 96624920d8f0ca..692f8845558270 100644
--- a/libc/src/__support/StringUtil/error_to_string.cpp
+++ b/libc/src/__support/StringUtil/error_to_string.cpp
@@ -23,10 +23,9 @@ namespace internal {
 
 constexpr size_t max_buff_size() {
   constexpr size_t unknown_str_len = sizeof("Unknown error");
-  constexpr size_t max_num_len =
-      __llvm_libc::IntegerToString::dec_bufsize<int>();
   // the buffer should be able to hold "Unknown error" + ' ' + num_str
-  return (unknown_str_len + 1 + max_num_len) * sizeof(char);
+  return (unknown_str_len + 1 + IntegerToString<int>::buffer_size()) *
+         sizeof(char);
 }
 
 // This is to hold error strings that have to be custom built. It may be
@@ -51,7 +50,7 @@ cpp::string_view build_error_string(int err_num, cpp::span<char> buffer) {
   // if the buffer can't hold "Unknown error" + ' ' + num_str, then just
   // return "Unknown error".
   if (buffer.size() <
-      (sizeof("Unknown error") + 1 + IntegerToString::dec_bufsize<int>()))
+      (sizeof("Unknown error") + 1 + IntegerToString<int>::buffer_size()))
     return const_cast<char *>("Unknown error");
 
   cpp::StringStream buffer_stream(

diff  --git a/libc/src/__support/StringUtil/signal_to_string.cpp b/libc/src/__support/StringUtil/signal_to_string.cpp
index c3610deb592d24..78b4d4dd853682 100644
--- a/libc/src/__support/StringUtil/signal_to_string.cpp
+++ b/libc/src/__support/StringUtil/signal_to_string.cpp
@@ -24,10 +24,9 @@ namespace internal {
 
 constexpr size_t max_buff_size() {
   constexpr size_t base_str_len = sizeof("Real-time signal");
-  constexpr size_t max_num_len =
-      __llvm_libc::IntegerToString::dec_bufsize<int>();
   // the buffer should be able to hold "Real-time signal" + ' ' + num_str
-  return (base_str_len + 1 + max_num_len) * sizeof(char);
+  return (base_str_len + 1 + IntegerToString<int>::buffer_size()) *
+         sizeof(char);
 }
 
 // This is to hold signal strings that have to be custom built. It may be
@@ -54,7 +53,7 @@ cpp::string_view build_signal_string(int sig_num, cpp::span<char> buffer) {
   // if the buffer can't hold "Unknown signal" + ' ' + num_str, then just
   // return "Unknown signal".
   if (buffer.size() <
-      (base_str.size() + 1 + IntegerToString::dec_bufsize<int>()))
+      (base_str.size() + 1 + IntegerToString<int>::buffer_size()))
     return base_str;
 
   cpp::StringStream buffer_stream(

diff  --git a/libc/src/__support/integer_to_string.h b/libc/src/__support/integer_to_string.h
index 4140da27a39905..998ee6d6363ce8 100644
--- a/libc/src/__support/integer_to_string.h
+++ b/libc/src/__support/integer_to_string.h
@@ -5,12 +5,63 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+//
+// Converts an integer to a string.
+//
+// By default, the string is written as decimal to an internal buffer and
+// accessed via the 'view' method.
+//
+//   IntegerToString<int> buffer(42);
+//   cpp::string_view view = buffer.view();
+//
+// The buffer is allocated on the stack and its size is so that the conversion
+// always succeeds.
+//
+// It is also possible to write the data to a preallocated buffer, but this may
+// fail.
+//
+//   char buffer[8];
+//   if (auto maybe_view = IntegerToString<int>::write_to_span(buffer, 42)) {
+//     cpp::string_view view = *maybe_view;
+//   }
+//
+// The first template parameter is the type of the integer.
+// The second template parameter defines how the integer is formatted.
+// Available default are 'radix::Bin', 'radix::Oct', 'radix::Dec' and
+// 'radix::Hex'.
+//
+// For 'radix::Bin', 'radix::Oct' and 'radix::Hex' the value is always
+// interpreted as a positive type but 'radix::Dec' will honor negative values.
+// e.g.,
+//
+//   IntegerToString<int8_t>(-1)             // "-1"
+//   IntegerToString<int8_t, radix::Dec>(-1) // "-1"
+//   IntegerToString<int8_t, radix::Bin>(-1) // "11111111"
+//   IntegerToString<int8_t, radix::Oct>(-1) // "377"
+//   IntegerToString<int8_t, radix::Hex>(-1) // "ff"
+//
+// Additionnally, the format can be changed by navigating the subtypes:
+//  - WithPrefix    : Adds "0b", "0", "0x" for binary, octal and hexadecimal
+//  - WithWidth<XX> : Pad string to XX characters filling leading digits with 0
+//  - Uppercase     : Use uppercase letters (only for HexString)
+//  - WithSign      : Prepend '+' for positive values (only for DecString)
+//
+// Examples
+// --------
+//   IntegerToString<int8_t, radix::Dec::WithWidth<2>::WithSign>(0)     : "+00"
+//   IntegerToString<int8_t, radix::Dec::WithWidth<2>::WithSign>(-1)    : "-01"
+//   IntegerToString<uint8_t, radix::Hex::WithPrefix::Uppercase>(255)   : "0xFF"
+//   IntegerToString<uint8_t, radix::Hex::WithWidth<4>::Uppercase>(255) : "00FF"
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC_SUPPORT_INTEGER_TO_STRING_H
 #define LLVM_LIBC_SRC_SUPPORT_INTEGER_TO_STRING_H
 
 #include <stdint.h>
 
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/CPP/limits.h"
 #include "src/__support/CPP/optional.h"
 #include "src/__support/CPP/span.h"
 #include "src/__support/CPP/string_view.h"
@@ -19,186 +70,251 @@
 
 namespace __llvm_libc {
 
-// Convert integer values to their string representation.
-//
-// Example usage:
-//   int a = 1234567;
-//
-//   // Convert to hexadecimal string:
-//   char hexbuf[IntegerToString::hex_bufsize<int>()];
-//   auto str = IntegerToString::hex(
-//       a, hexbuf, false /* generate upper case characters */);
-//
-//   // Convert to decimal string:
-//   char decbuf[IntegerToString::dec_bufsize<int>()];
-//   auto str = IntegerToString::dec(a, decbuf);
-//
-//   // Convert to octal string:
-//   char octbuf[IntegerToString::oct_bufsize<int>(a)];
-//   auto str = IntegerToString::dec(a, octbuf);
-//
-//   // Convert to binary string:
-//   char binbuf[IntegerToString::bin_bufsize<int>(a)];
-//   auto str = IntegerToString::bin(a, binbuf);
-//
-//   // Convert to base 30 string:
-//   char b30buf[IntegerToString::bufsize<30, int>(a)];
-//   auto str = IntegerToString::convert<30>(a, b30buf);
-class IntegerToString {
-  LIBC_INLINE static cpp::string_view convert_uintmax(uintmax_t uval,
-                                                      cpp::span<char> &buffer,
-                                                      bool lowercase,
-                                                      const uint8_t conv_base) {
-    const char a = lowercase ? 'a' : 'A';
-
-    size_t len = 0;
-
-    size_t buffptr = buffer.size();
-    if (uval == 0) {
-      buffer[buffptr - 1] = '0';
-      --buffptr;
-    } else {
-      for (; uval > 0; --buffptr, uval /= conv_base) {
-        uintmax_t digit = (uval % conv_base);
-        buffer[buffptr - 1] = static_cast<char>(digit < 10 ? digit + '0' : digit + a - 10);
-      }
-    }
-    len = buffer.size() - buffptr;
+namespace details {
 
-    return cpp::string_view(buffer.data() + buffer.size() - len, len);
-  }
+template <uint8_t base, bool prefix = false, bool force_sign = false,
+          bool is_uppercase = false, size_t min_digits = 1>
+struct Fmt {
+  static constexpr uint8_t BASE = base;
+  static constexpr size_t MIN_DIGITS = min_digits;
+  static constexpr bool IS_UPPERCASE = is_uppercase;
+  static constexpr bool PREFIX = prefix;
+  static constexpr char FORCE_SIGN = force_sign;
 
-  LIBC_INLINE static cpp::string_view convert_intmax(intmax_t val,
-                                                     cpp::span<char> &buffer,
-                                                     bool lowercase,
-                                                     const uint8_t conv_base) {
-    if (val >= 0)
-      return convert_uintmax(uintmax_t(val), buffer, lowercase, conv_base);
-    uintmax_t uval = uintmax_t(-val);
-    auto str_view = convert_uintmax(uval, buffer, lowercase, conv_base);
-    size_t len = str_view.size();
-    ++len;
-    buffer[buffer.size() - len] = '-';
-    return cpp::string_view(buffer.data() + buffer.size() - len, len);
-  }
+  using WithPrefix = Fmt<BASE, true, FORCE_SIGN, IS_UPPERCASE, MIN_DIGITS>;
+  using WithSign = Fmt<BASE, PREFIX, true, IS_UPPERCASE, MIN_DIGITS>;
+  using Uppercase = Fmt<BASE, PREFIX, FORCE_SIGN, true, MIN_DIGITS>;
+  template <size_t value>
+  using WithWidth = Fmt<BASE, PREFIX, FORCE_SIGN, IS_UPPERCASE, value>;
 
-  LIBC_INLINE static constexpr size_t floor_log_2(size_t num) {
-    size_t i = 0;
-    for (; num > 1; num /= 2) {
-      ++i;
-    }
-    return i;
+  // Invariants
+  static constexpr uint8_t NUMERICAL_DIGITS = 10;
+  static constexpr uint8_t ALPHA_DIGITS = 26;
+  static constexpr uint8_t MAX_DIGIT = NUMERICAL_DIGITS + ALPHA_DIGITS;
+  static_assert(BASE > 1 && BASE <= MAX_DIGIT);
+  static_assert(!IS_UPPERCASE || BASE > 10, "Uppercase is only for radix > 10");
+  static_assert(!FORCE_SIGN || BASE == 10, "WithSign is only for radix == 10");
+  static_assert(!PREFIX || (BASE == 2 || BASE == 8 || BASE == 16),
+                "WithPrefix is only for radix == 2, 8 or 16");
+};
+
+// Move this to a separate header since it might be useful elsewhere.
+template <bool forward> class StringBufferWriterImpl {
+  cpp::span<char> buffer;
+  size_t index = 0;
+  bool out_of_range = false;
+
+  LIBC_INLINE size_t location() const {
+    return forward ? index : buffer.size() - 1 - index;
   }
 
 public:
-  // We size the string buffer for base 10 using an approximation algorithm:
-  //
-  //   size = ceil(sizeof(T) * 5 / 2)
-  //
-  // If sizeof(T) is 1, then size is 3 (actually need 3)
-  // If sizeof(T) is 2, then size is 5 (actually need 5)
-  // If sizeof(T) is 4, then size is 10 (actually need 10)
-  // If sizeof(T) is 8, then size is 20 (actually need 20)
-  // If sizeof(T) is 16, then size is 40 (actually need 39)
-  //
-  // NOTE: The ceil operation is actually implemented as
-  //     floor(((sizeof(T) * 5) + 1)/2)
-  // where floor operation is just integer division.
-  //
-  // This estimation grows slightly faster than the actual value, but the
-  // overhead is small enough to tolerate. In the actual formula below, we
-  // add an additional byte to accommodate the '-' sign in case of signed
-  // integers.
-  // For other bases, we approximate by rounding down to the nearest power of
-  // two base, since the space needed is easy to calculate and it won't
-  // overestimate by too much.
-  template <uint8_t BASE, typename T>
-  LIBC_INLINE static constexpr size_t bufsize() {
-    constexpr size_t BITS_PER_DIGIT = floor_log_2(BASE);
-    constexpr size_t BUFSIZE_COMMON =
-        ((sizeof(T) * 8 + (BITS_PER_DIGIT - 1)) / BITS_PER_DIGIT);
-    constexpr size_t BUFSIZE_BASE10 = (sizeof(T) * 5 + 1) / 2;
-    return (cpp::is_signed<T>() ? 1 : 0) +
-           (BASE == 10 ? BUFSIZE_BASE10 : BUFSIZE_COMMON);
-  }
+  StringBufferWriterImpl(const StringBufferWriterImpl &) = delete;
+  StringBufferWriterImpl(cpp::span<char> buffer) : buffer(buffer) {}
 
-  template <typename T> LIBC_INLINE static constexpr size_t dec_bufsize() {
-    return bufsize<10, T>();
-  }
+  LIBC_INLINE size_t size() const { return index; }
+  LIBC_INLINE size_t remainder_size() const { return buffer.size() - size(); }
+  LIBC_INLINE bool empty() const { return size() == 0; }
+  LIBC_INLINE bool full() const { return size() == buffer.size(); }
+  LIBC_INLINE bool ok() const { return !out_of_range; }
 
-  template <typename T> LIBC_INLINE static constexpr size_t hex_bufsize() {
-    return bufsize<16, T>();
+  LIBC_INLINE StringBufferWriterImpl &push(char c) {
+    if (ok()) {
+      if (!full()) {
+        buffer[location()] = c;
+        ++index;
+      } else {
+        out_of_range = true;
+      }
+    }
+    return *this;
   }
 
-  template <typename T> LIBC_INLINE static constexpr size_t oct_bufsize() {
-    return bufsize<8, T>();
+  LIBC_INLINE cpp::span<char> remainder_span() const {
+    return forward ? buffer.last(remainder_size())
+                   : buffer.first(remainder_size());
   }
 
-  template <typename T> LIBC_INLINE static constexpr size_t bin_bufsize() {
-    return bufsize<2, T>();
+  LIBC_INLINE cpp::span<char> buffer_span() const {
+    return forward ? buffer.first(size()) : buffer.last(size());
   }
 
-  template <uint8_t BASE, typename T,
-            cpp::enable_if_t<2 <= BASE && BASE <= 36 && cpp::is_integral_v<T>,
-                             int> = 0>
-  LIBC_INLINE static cpp::optional<cpp::string_view>
-  convert(T val, cpp::span<char> buffer, bool lowercase = true) {
-    if (buffer.size() < bufsize<BASE, T>())
-      return cpp::optional<cpp::string_view>();
-    if (cpp::is_signed_v<T>)
-      return convert_intmax(intmax_t(val), buffer, lowercase, BASE);
-    else
-      return convert_uintmax(uintmax_t(val), buffer, lowercase, BASE);
+  LIBC_INLINE cpp::string_view buffer_view() const {
+    const auto s = buffer_span();
+    return {s.data(), s.size()};
   }
+};
 
-  template <typename T, cpp::enable_if_t<cpp::is_integral_v<T>, int> = 0>
-  LIBC_INLINE static cpp::optional<cpp::string_view>
-  dec(T val, cpp::span<char> buffer) {
-    return convert<10>(val, buffer);
-  }
+using StringBufferWriter = StringBufferWriterImpl<true>;
+using BackwardStringBufferWriter = StringBufferWriterImpl<false>;
+
+} // namespace details
+
+namespace radix {
+
+using Bin = details::Fmt<2>;
+using Oct = details::Fmt<8>;
+using Dec = details::Fmt<10>;
+using Hex = details::Fmt<16>;
+template <size_t radix> using Custom = details::Fmt<radix>;
 
-  template <typename T, cpp::enable_if_t<cpp::is_integral_v<T> &&
-                                             (sizeof(T) <= sizeof(uintmax_t)),
-                                         int> = 0>
-  LIBC_INLINE static cpp::optional<cpp::string_view>
-  hex(T val, cpp::span<char> buffer, bool lowercase = true) {
-    return convert<16>(val, buffer, lowercase);
+} // namespace radix
+
+// See file header for documentation.
+template <typename T, typename Fmt = radix::Dec> class IntegerToString {
+  static_assert(cpp::is_integral_v<T>);
+
+  LIBC_INLINE static constexpr size_t compute_buffer_size() {
+    constexpr auto max_digits = []() -> size_t {
+      // We size the string buffer for base 10 using an approximation algorithm:
+      //
+      //   size = ceil(sizeof(T) * 5 / 2)
+      //
+      // If sizeof(T) is 1, then size is 3 (actually need 3)
+      // If sizeof(T) is 2, then size is 5 (actually need 5)
+      // If sizeof(T) is 4, then size is 10 (actually need 10)
+      // If sizeof(T) is 8, then size is 20 (actually need 20)
+      // If sizeof(T) is 16, then size is 40 (actually need 39)
+      //
+      // NOTE: The ceil operation is actually implemented as
+      //     floor(((sizeof(T) * 5) + 1) / 2)
+      // where floor operation is just integer division.
+      //
+      // This estimation grows slightly faster than the actual value, but the
+      // overhead is small enough to tolerate.
+      if constexpr (Fmt::BASE == 10)
+        return ((sizeof(T) * 5) + 1) / 2;
+      // For other bases, we approximate by rounding down to the nearest power
+      // of two base, since the space needed is easy to calculate and it won't
+      // overestimate by too much.
+      constexpr auto floor_log_2 = [](size_t num) -> size_t {
+        size_t i = 0;
+        for (; num > 1; num /= 2)
+          ++i;
+        return i;
+      };
+      constexpr size_t BITS_PER_DIGIT = floor_log_2(Fmt::BASE);
+      return ((sizeof(T) * 8 + (BITS_PER_DIGIT - 1)) / BITS_PER_DIGIT);
+    };
+    constexpr auto max = [](size_t a, size_t b) -> size_t {
+      return a > b ? a : b;
+    };
+    constexpr size_t digit_size = max(max_digits(), Fmt::MIN_DIGITS);
+    constexpr size_t sign_size = Fmt::BASE == 10 ? 1 : 0;
+    constexpr size_t prefix_size = Fmt::PREFIX ? 2 : 0;
+    return digit_size + sign_size + prefix_size;
   }
 
-  template <typename T, cpp::enable_if_t<cpp::is_integral_v<T> &&
-                                             (sizeof(T) > sizeof(uintmax_t)) &&
-                                             sizeof(T) % sizeof(uintmax_t) == 0,
-                                         int> = 0>
-  LIBC_INLINE static cpp::optional<cpp::string_view>
-  hex(T val, cpp::span<char> buffer, bool lowercase = true) {
-    // We will assume the buffer is exactly sized, which will be the case if
-    // it was sized using the bufsize method.
-    constexpr size_t BLOCKS = sizeof(T) / sizeof(uintmax_t);
-    constexpr size_t UINTMAX_BUFSIZE = bufsize<16, uintmax_t>();
-    // We will zero out the buffer. This specialization is not used to
-    // implement a public function so zeroing out byte-by-byte does not
-    // have any affect on runtime or user expectations.
-    for (size_t i = 0; i < buffer.size(); ++i)
-      buffer[i] = '0';
-    for (size_t i = 0; i < BLOCKS; ++i, val >>= (sizeof(uintmax_t) * 8)) {
-      uintmax_t block_val = static_cast<uintmax_t>(val);
-      hex(block_val,
-          buffer.subspan((BLOCKS - i - 1) * UINTMAX_BUFSIZE, UINTMAX_BUFSIZE),
-          lowercase);
+  static constexpr size_t BUFFER_SIZE = compute_buffer_size();
+  static_assert(BUFFER_SIZE > 0);
+
+  // An internal stateless structure that handles the number formatting logic.
+  struct IntegerWriter {
+    static_assert(cpp::is_integral_v<T>);
+    using UNSIGNED_T = cpp::make_unsigned_t<T>;
+
+    LIBC_INLINE static char digit_char(uint8_t digit) {
+      if (digit < 10)
+        return '0' + digit;
+      return (Fmt::IS_UPPERCASE ? 'A' : 'a') + (digit - 10);
+    }
+
+    LIBC_INLINE static void
+    write_unsigned_number(UNSIGNED_T value,
+                          details::BackwardStringBufferWriter &sink) {
+      for (; sink.ok() && value != 0; value /= Fmt::BASE) {
+        const uint8_t digit(value % Fmt::BASE);
+        sink.push(digit_char(digit));
+      }
+    }
+
+    // Returns the absolute value of 'value' as 'UNSIGNED_T'.
+    LIBC_INLINE static UNSIGNED_T abs(T value) {
+      if (cpp::is_unsigned_v<T> || value >= 0)
+        return value; // already of the right sign.
+
+      // Signed integers are asymmetric (e.g., int8_t ∈ [-128, 127]).
+      // Thus negating the type's minimum value would overflow.
+      // From C++20 on, signed types are guaranteed to be represented as 2's
+      // complement. We take advantage of this representation and negate the
+      // value by using the exact same bit representation, e.g.,
+      // binary : 0b1000'0000
+      // int8_t : -128
+      // uint8_t:  128
+
+      // Note: the compiler can completely optimize out the two branches and
+      // replace them by a simple negate instruction.
+      // https://godbolt.org/z/hE7zahT9W
+      if (value == cpp::numeric_limits<T>::min()) {
+        return cpp::bit_cast<UNSIGNED_T>(value);
+      } else {
+        return -value; // legal and representable both as T and UNSIGNED_T.`
+      }
+    }
+
+    LIBC_INLINE static void write(T value,
+                                  details::BackwardStringBufferWriter &sink) {
+      if constexpr (Fmt::BASE == 10) {
+        write_unsigned_number(abs(value), sink);
+      } else {
+        write_unsigned_number(cpp::bit_cast<UNSIGNED_T>(value), sink);
+      }
+      // width
+      while (sink.ok() && sink.size() < Fmt::MIN_DIGITS)
+        sink.push('0');
+      // sign
+      if constexpr (Fmt::BASE == 10) {
+        if (value < 0)
+          sink.push('-');
+        else if (Fmt::FORCE_SIGN)
+          sink.push('+');
+      }
+      // prefix
+      if constexpr (Fmt::PREFIX) {
+        if constexpr (Fmt::BASE == 2) {
+          sink.push('b');
+          sink.push('0');
+        }
+        if constexpr (Fmt::BASE == 16) {
+          sink.push('x');
+          sink.push('0');
+        }
+        if constexpr (Fmt::BASE == 8) {
+          const cpp::string_view written = sink.buffer_view();
+          if (written.empty() || written.front() != '0')
+            sink.push('0');
+        }
+      }
     }
-    return cpp::string_view(buffer.data(), buffer.size());
+  };
+
+  cpp::array<char, BUFFER_SIZE> array;
+  size_t written = 0;
+
+public:
+  IntegerToString(const IntegerToString &) = delete;
+  IntegerToString(T value) {
+    details::BackwardStringBufferWriter writer(array);
+    IntegerWriter::write(value, writer);
+    written = writer.size();
   }
 
-  template <typename T, cpp::enable_if_t<cpp::is_integral_v<T>, int> = 0>
-  LIBC_INLINE static cpp::optional<cpp::string_view>
-  oct(T val, cpp::span<char> buffer) {
-    return convert<8>(val, buffer);
+  [[nodiscard]] LIBC_INLINE static cpp::optional<cpp::string_view>
+  format_to(cpp::span<char> buffer, T value) {
+    details::BackwardStringBufferWriter writer(buffer);
+    IntegerWriter::write(value, writer);
+    if (writer.ok())
+      return cpp::string_view(buffer.data() + buffer.size() - writer.size(),
+                              writer.size());
+    return cpp::nullopt;
   }
 
-  template <typename T, cpp::enable_if_t<cpp::is_integral_v<T>, int> = 0>
-  LIBC_INLINE static cpp::optional<cpp::string_view>
-  bin(T val, cpp::span<char> buffer) {
-    return convert<2>(val, buffer);
+  LIBC_INLINE static constexpr size_t buffer_size() { return BUFFER_SIZE; }
+
+  LIBC_INLINE size_t size() const { return written; }
+  LIBC_INLINE cpp::string_view view() && = delete;
+  LIBC_INLINE cpp::string_view view() const & {
+    return cpp::string_view(array.data() + array.size() - size(), size());
   }
 };
 

diff  --git a/libc/src/__support/libc_assert.h b/libc/src/__support/libc_assert.h
index eabcce8071f340..8c1f630fb4a403 100644
--- a/libc/src/__support/libc_assert.h
+++ b/libc/src/__support/libc_assert.h
@@ -32,17 +32,15 @@ namespace __llvm_libc {
 LIBC_INLINE void report_assertion_failure(const char *assertion,
                                           const char *filename, unsigned line,
                                           const char *funcname) {
-  char line_str[IntegerToString::dec_bufsize<unsigned>()];
-  // dec returns an optional, will always be valid for this size buffer
-  auto line_number = IntegerToString::dec(line, line_str);
-  __llvm_libc::write_to_stderr(filename);
-  __llvm_libc::write_to_stderr(":");
-  __llvm_libc::write_to_stderr(*line_number);
-  __llvm_libc::write_to_stderr(": Assertion failed: '");
-  __llvm_libc::write_to_stderr(assertion);
-  __llvm_libc::write_to_stderr("' in function: '");
-  __llvm_libc::write_to_stderr(funcname);
-  __llvm_libc::write_to_stderr("'\n");
+  const IntegerToString<unsigned> line_buffer(line);
+  write_to_stderr(filename);
+  write_to_stderr(":");
+  write_to_stderr(line_buffer.view());
+  write_to_stderr(": Assertion failed: '");
+  write_to_stderr(assertion);
+  write_to_stderr("' in function: '");
+  write_to_stderr(funcname);
+  write_to_stderr("'\n");
 }
 
 } // namespace __llvm_libc

diff  --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp
index 668f0f11e970ba..88efaece182a37 100644
--- a/libc/src/__support/threads/linux/thread.cpp
+++ b/libc/src/__support/threads/linux/thread.cpp
@@ -391,7 +391,7 @@ bool Thread::operator==(const Thread &thread) const {
 static constexpr cpp::string_view THREAD_NAME_PATH_PREFIX("/proc/self/task/");
 static constexpr size_t THREAD_NAME_PATH_SIZE =
     THREAD_NAME_PATH_PREFIX.size() +
-    IntegerToString::dec_bufsize<int>() + // Size of tid
+    IntegerToString<int>::buffer_size() + // Size of tid
     1 +                                   // For '/' character
     5; // For the file name "comm" and the nullterminator.
 

diff  --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index a0c891fb866c5a..7927220da3fff8 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -32,6 +32,9 @@ namespace __llvm_libc {
 namespace printf_core {
 
 using MantissaInt = fputil::FPBits<long double>::UIntType;
+using DecimalString = IntegerToString<intmax_t>;
+using ExponentString =
+    IntegerToString<intmax_t, radix::Dec::WithWidth<2>::WithSign>;
 
 // Returns true if value is divisible by 2^p.
 template <typename T>
@@ -193,39 +196,11 @@ class FloatWriter {
     return 0;
   }
 
-  cpp::string_view exp_str(int exponent, cpp::span<char> exp_buffer) {
-
-    // -exponent will never overflow because all long double types we support
-    // have at most 15 bits of mantissa and the C standard defines an int as
-    // being at least 16 bits.
-    static_assert(fputil::FloatProperties<long double>::EXPONENT_WIDTH <
-                  (sizeof(int) * 8));
-
-    int positive_exponent = exponent < 0 ? -exponent : exponent;
-    char exp_sign = exponent < 0 ? '-' : '+';
-    auto const int_to_str =
-        *IntegerToString::dec(positive_exponent, exp_buffer);
-
-    // IntegerToString writes the digits from right to left so there will be
-    // space to the left of int_to_str.
-    size_t digits_in_exp = int_to_str.size();
-    size_t index = exp_buffer.size() - digits_in_exp - 1;
-
-    // Ensure that at least two digits were written. IntegerToString always
-    // writes at least 1 digit (it writes "0" when the input number is 0).
-    if (digits_in_exp < 2) {
-      exp_buffer[index] = '0';
-      --index;
-    }
-
-    // Since the exp_buffer has to be sized to handle an intmax_t, it has space
-    // for a sign. In this case we're handling the sign on our own since we also
-    // want plus signs for positive numbers.
-    exp_buffer[index] = exp_sign;
-
-    return cpp::string_view(exp_buffer.data() + index,
-                            exp_buffer.size() - index);
-  }
+  // -exponent will never overflow because all long double types we support
+  // have at most 15 bits of mantissa and the C standard defines an int as
+  // being at least 16 bits.
+  static_assert(fputil::FloatProperties<long double>::EXPONENT_WIDTH <
+                (sizeof(int) * 8));
 
 public:
   FloatWriter(Writer *init_writer, bool init_has_decimal_point,
@@ -239,8 +214,8 @@ class FloatWriter {
   }
 
   void write_first_block(BlockInt block, bool exp_format = false) {
-    char buf[IntegerToString::dec_bufsize<intmax_t>()];
-    auto const int_to_str = *IntegerToString::dec(block, buf);
+    const DecimalString buf(block);
+    const cpp::string_view int_to_str = buf.view();
     size_t digits_buffered = int_to_str.size();
     // Block Buffer is guaranteed to not overflow since block cannot have more
     // than BLOCK_SIZE digits.
@@ -268,9 +243,8 @@ class FloatWriter {
       // Now buffer the current block. We add 1 + MAX_BLOCK to force the
       // leading zeroes, and drop the leading one. This is probably inefficient,
       // but it works. See https://xkcd.com/2021/
-      char buf[IntegerToString::dec_bufsize<intmax_t>()];
-      auto const int_to_str =
-          *IntegerToString::dec(block + (MAX_BLOCK + 1), buf);
+      const DecimalString buf(block + (MAX_BLOCK + 1));
+      const cpp::string_view int_to_str = buf.view();
       // TODO: Replace with memcpy
       for (size_t count = 0; count < BLOCK_SIZE; ++count) {
         block_buffer[count] = int_to_str[count + 1];
@@ -285,8 +259,8 @@ class FloatWriter {
                            RoundDirection round) {
     char end_buff[BLOCK_SIZE];
 
-    char buf[IntegerToString::dec_bufsize<intmax_t>()];
-    auto const int_to_str = *IntegerToString::dec(block + (MAX_BLOCK + 1), buf);
+    const DecimalString buf(block + (MAX_BLOCK + 1));
+    const cpp::string_view int_to_str = buf.view();
 
     // copy the last block_digits characters into the start of end_buff.
     // TODO: Replace with memcpy
@@ -372,9 +346,8 @@ class FloatWriter {
     char end_buff[BLOCK_SIZE];
 
     {
-      char buf[IntegerToString::dec_bufsize<intmax_t>()];
-      auto const int_to_str =
-          *IntegerToString::dec(block + (MAX_BLOCK + 1), buf);
+      const DecimalString buf(block + (MAX_BLOCK + 1));
+      const cpp::string_view int_to_str = buf.view();
 
       // copy the last block_digits characters into the start of end_buff.
       // TODO: Replace with memcpy
@@ -424,8 +397,8 @@ class FloatWriter {
         // but we do increment the exponent.
         ++exponent;
 
-        char buf[IntegerToString::dec_bufsize<intmax_t>()];
-        auto const int_to_str = exp_str(exponent, buf);
+        const ExponentString buf(exponent);
+        const cpp::string_view int_to_str = buf.view();
 
         // TODO: also change this to calculate the width of the number more
         // efficiently.
@@ -479,11 +452,9 @@ class FloatWriter {
     buffered_digits = block_digits;
     RET_IF_RESULT_NEGATIVE(flush_buffer());
 
-    char buf[IntegerToString::dec_bufsize<intmax_t>()];
-    auto const int_to_str = exp_str(exponent, buf);
-
     RET_IF_RESULT_NEGATIVE(writer->write(exp_char));
-    RET_IF_RESULT_NEGATIVE(writer->write(int_to_str));
+    const ExponentString buf(exponent);
+    RET_IF_RESULT_NEGATIVE(writer->write(buf.view()));
 
     total_digits_written = total_digits;
 
@@ -707,17 +678,12 @@ LIBC_INLINE int convert_float_dec_exp_typed(Writer *writer,
     cur_block = 0;
   }
 
-  // TODO: Find a better way to calculate the number of digits in the
-  // initial block and exponent.
-  char buf[IntegerToString::dec_bufsize<intmax_t>()];
-  auto int_to_str = *IntegerToString::dec(digits, buf);
-  size_t block_width = int_to_str.size();
+  const size_t block_width = IntegerToString<intmax_t>(digits).size();
 
   final_exponent = (cur_block * BLOCK_SIZE) + static_cast<int>(block_width - 1);
   int positive_exponent = final_exponent < 0 ? -final_exponent : final_exponent;
 
-  int_to_str = *IntegerToString::dec(positive_exponent, buf);
-  size_t exponent_width = int_to_str.size();
+  size_t exponent_width = IntegerToString<intmax_t>(positive_exponent).size();
 
   // Calculate the total number of digits in the number.
   // 1 - the digit before the decimal point
@@ -752,10 +718,7 @@ LIBC_INLINE int convert_float_dec_exp_typed(Writer *writer,
 
   // if the last block is also the first block, then ignore leading zeroes.
   if (digits_written == 0) {
-    // TODO: Find a better way to calculate the number of digits in a block.
-    char buf[IntegerToString::dec_bufsize<intmax_t>()];
-    auto int_to_str = *IntegerToString::dec(digits, buf);
-    last_block_size = int_to_str.size();
+    last_block_size = IntegerToString<intmax_t>(digits).size();
   }
 
   // This is the last block.
@@ -881,14 +844,7 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer *writer,
     return convert_float_decimal_typed<T>(writer, new_conv, float_bits);
   }
 
-  size_t block_width = 0;
-  {
-    // TODO: Find a better way to calculate the number of digits in the
-    // initial block and exponent.
-    char buf[IntegerToString::dec_bufsize<intmax_t>()];
-    auto int_to_str = *IntegerToString::dec(digits, buf);
-    block_width = int_to_str.size();
-  }
+  const size_t block_width = IntegerToString<intmax_t>(digits).size();
 
   size_t digits_checked = 0;
   // TODO: look into unifying trailing_zeroes and trailing_nines. The number can
@@ -900,8 +856,8 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer *writer,
 
   // If the first block is not also the last block
   if (block_width <= exp_precision + 1) {
-    char buf[IntegerToString::dec_bufsize<intmax_t>()];
-    auto int_to_str = *IntegerToString::dec(digits, buf);
+    const DecimalString buf(digits);
+    const cpp::string_view int_to_str = buf.view();
 
     for (size_t i = 0; i < block_width; ++i) {
       if (int_to_str[i] == '9') {
@@ -962,8 +918,8 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer *writer,
 
   size_t last_block_size = BLOCK_SIZE;
 
-  char buf[IntegerToString::dec_bufsize<intmax_t>()];
-  auto int_to_str = *IntegerToString::dec(digits, buf);
+  const DecimalString buf(digits);
+  const cpp::string_view int_to_str = buf.view();
 
   size_t implicit_leading_zeroes = BLOCK_SIZE - int_to_str.size();
 

diff  --git a/libc/src/stdio/printf_core/int_converter.h b/libc/src/stdio/printf_core/int_converter.h
index aa91e34486ba48..044721b5afde00 100644
--- a/libc/src/stdio/printf_core/int_converter.h
+++ b/libc/src/stdio/printf_core/int_converter.h
@@ -28,17 +28,38 @@ namespace printf_core {
 LIBC_INLINE constexpr char to_lower(char a) { return a | 32; }
 LIBC_INLINE constexpr bool is_lower(char a) { return (a & 32) > 0; }
 
+namespace details {
+
+using HexFmt = IntegerToString<uintmax_t, radix::Hex>;
+using HexFmtUppercase = IntegerToString<uintmax_t, radix::Hex::Uppercase>;
+using OctFmt = IntegerToString<uintmax_t, radix::Oct>;
+using DecFmt = IntegerToString<uintmax_t>;
+
+LIBC_INLINE constexpr size_t num_buf_size() {
+  constexpr auto max = [](size_t a, size_t b) -> size_t {
+    return (a < b) ? b : a;
+  };
+  return max(HexFmt::buffer_size(),
+             max(HexFmtUppercase::buffer_size(),
+                 max(OctFmt::buffer_size(), DecFmt::buffer_size())));
+}
+
 LIBC_INLINE cpp::optional<cpp::string_view>
 num_to_strview(uintmax_t num, cpp::span<char> bufref, char conv_name) {
   if (to_lower(conv_name) == 'x') {
-    return IntegerToString::hex(num, bufref, is_lower(conv_name));
+    if (is_lower(conv_name))
+      return HexFmt::format_to(bufref, num);
+    else
+      return HexFmtUppercase::format_to(bufref, num);
   } else if (conv_name == 'o') {
-    return IntegerToString::oct(num, bufref);
+    return OctFmt::format_to(bufref, num);
   } else {
-    return IntegerToString::dec(num, bufref);
+    return DecFmt::format_to(bufref, num);
   }
 }
 
+} // namespace details
+
 LIBC_INLINE int convert_int(Writer *writer, const FormatSection &to_conv) {
   static constexpr size_t BITS_IN_BYTE = 8;
   static constexpr size_t BITS_IN_NUM = sizeof(uintmax_t) * BITS_IN_BYTE;
@@ -66,8 +87,8 @@ LIBC_INLINE int convert_int(Writer *writer, const FormatSection &to_conv) {
 
   num = apply_length_modifier(num, to_conv.length_modifier);
 
-  char buf[IntegerToString::oct_bufsize<intmax_t>()];
-  auto str = num_to_strview(num, buf, to_conv.conv_name);
+  cpp::array<char, details::num_buf_size()> buf;
+  auto str = details::num_to_strview(num, buf, to_conv.conv_name);
   if (!str)
     return INT_CONVERSION_ERROR;
 

diff  --git a/libc/test/UnitTest/LibcTest.cpp b/libc/test/UnitTest/LibcTest.cpp
index 3ffd0c3d18abd4..d9a9e50ca9a65b 100644
--- a/libc/test/UnitTest/LibcTest.cpp
+++ b/libc/test/UnitTest/LibcTest.cpp
@@ -47,9 +47,8 @@ cpp::enable_if_t<cpp::is_integral_v<T> && (sizeof(T) > sizeof(uint64_t)),
                  cpp::string>
 describeValue(T Value) {
   static_assert(sizeof(T) % 8 == 0, "Unsupported size of UInt");
-  char buf[IntegerToString::hex_bufsize<T>()];
-  IntegerToString::hex(Value, buf, false);
-  return "0x" + cpp::string(buf, sizeof(buf));
+  const IntegerToString<T, radix::Hex::WithPrefix> buffer(Value);
+  return buffer.view();
 }
 
 // When the value is of a standard integral type, just display it as normal.

diff  --git a/libc/test/UnitTest/TestLogger.cpp b/libc/test/UnitTest/TestLogger.cpp
index 0fa2153c6726b1..02be4bdf5de811 100644
--- a/libc/test/UnitTest/TestLogger.cpp
+++ b/libc/test/UnitTest/TestLogger.cpp
@@ -50,9 +50,8 @@ template <typename T> TestLogger &TestLogger::operator<<(T t) {
   if constexpr (cpp::is_integral_v<T> && cpp::is_unsigned_v<T> &&
                 sizeof(T) > sizeof(uint64_t)) {
     static_assert(sizeof(T) % 8 == 0, "Unsupported size of UInt");
-    char buf[IntegerToString::hex_bufsize<T>()];
-    IntegerToString::hex(t, buf, false);
-    return *this << "0x" << cpp::string_view(buf, sizeof(buf));
+    const IntegerToString<T, radix::Hex::WithPrefix> buffer(t);
+    return *this << buffer.view();
   } else {
     return *this << cpp::to_string(t);
   }

diff  --git a/libc/test/src/__support/integer_to_string_test.cpp b/libc/test/src/__support/integer_to_string_test.cpp
index 783a4d80826908..991582e896d2fe 100644
--- a/libc/test/src/__support/integer_to_string_test.cpp
+++ b/libc/test/src/__support/integer_to_string_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "src/__support/CPP/span.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/UInt.h"
 #include "src/__support/UInt128.h"
@@ -16,275 +17,289 @@
 #include "limits.h"
 
 using __llvm_libc::IntegerToString;
+using __llvm_libc::cpp::span;
 using __llvm_libc::cpp::string_view;
+using __llvm_libc::radix::Bin;
+using __llvm_libc::radix::Custom;
+using __llvm_libc::radix::Dec;
+using __llvm_libc::radix::Hex;
+using __llvm_libc::radix::Oct;
+
+#define EXPECT(type, value, string_value)                                      \
+  {                                                                            \
+    const type buffer(value);                                                  \
+    EXPECT_EQ(buffer.view(), string_view(string_value));                       \
+  }
 
 TEST(LlvmLibcIntegerToStringTest, UINT8) {
-  char buf[IntegerToString::dec_bufsize<uint8_t>()];
-  EXPECT_EQ(*IntegerToString::dec(uint8_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(uint8_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(uint8_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(uint8_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(uint8_t(UINT8_MAX), buf), string_view("255"));
-  EXPECT_EQ(*IntegerToString::dec(uint8_t(-1), buf), string_view("255"));
+  using type = IntegerToString<uint8_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, UINT8_MAX, "255");
+  EXPECT(type, -1, "255");
 }
 
 TEST(LlvmLibcIntegerToStringTest, INT8) {
-  char buf[IntegerToString::dec_bufsize<int8_t>()];
-  EXPECT_EQ(*IntegerToString::dec(int8_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(int8_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(int8_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(int8_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(int8_t(-12), buf), string_view("-12"));
-  EXPECT_EQ(*IntegerToString::dec(int8_t(-123), buf), string_view("-123"));
-  EXPECT_EQ(*IntegerToString::dec(int8_t(INT8_MAX), buf), string_view("127"));
-  EXPECT_EQ(*IntegerToString::dec(int8_t(INT8_MIN), buf), string_view("-128"));
+  using type = IntegerToString<int8_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, -12, "-12");
+  EXPECT(type, -123, "-123");
+  EXPECT(type, INT8_MAX, "127");
+  EXPECT(type, INT8_MIN, "-128");
 }
 
 TEST(LlvmLibcIntegerToStringTest, UINT16) {
-  char buf[IntegerToString::dec_bufsize<uint16_t>()];
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(1234), buf), string_view("1234"));
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(12345), buf), string_view("12345"));
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(UINT16_MAX), buf),
-            string_view("65535"));
-  EXPECT_EQ(*IntegerToString::dec(uint16_t(-1), buf), string_view("65535"));
+  using type = IntegerToString<uint16_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, 1234, "1234");
+  EXPECT(type, 12345, "12345");
+  EXPECT(type, UINT16_MAX, "65535");
+  EXPECT(type, -1, "65535");
 }
 
 TEST(LlvmLibcIntegerToStringTest, INT16) {
-  char buf[IntegerToString::dec_bufsize<int16_t>()];
-  EXPECT_EQ(*IntegerToString::dec(int16_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(1234), buf), string_view("1234"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(12345), buf), string_view("12345"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(-1), buf), string_view("-1"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(-12), buf), string_view("-12"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(-123), buf), string_view("-123"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(-1234), buf), string_view("-1234"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(-12345), buf), string_view("-12345"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(INT16_MAX), buf),
-            string_view("32767"));
-  EXPECT_EQ(*IntegerToString::dec(int16_t(INT16_MIN), buf),
-            string_view("-32768"));
+  using type = IntegerToString<int16_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, 1234, "1234");
+  EXPECT(type, 12345, "12345");
+  EXPECT(type, -1, "-1");
+  EXPECT(type, -12, "-12");
+  EXPECT(type, -123, "-123");
+  EXPECT(type, -1234, "-1234");
+  EXPECT(type, -12345, "-12345");
+  EXPECT(type, INT16_MAX, "32767");
+  EXPECT(type, INT16_MIN, "-32768");
 }
 
 TEST(LlvmLibcIntegerToStringTest, UINT32) {
-  char buf[IntegerToString::dec_bufsize<uint32_t>()];
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(1234), buf), string_view("1234"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(12345), buf), string_view("12345"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(123456), buf), string_view("123456"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(1234567), buf),
-            string_view("1234567"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(12345678), buf),
-            string_view("12345678"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(123456789), buf),
-            string_view("123456789"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(1234567890), buf),
-            string_view("1234567890"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(UINT32_MAX), buf),
-            string_view("4294967295"));
-  EXPECT_EQ(*IntegerToString::dec(uint32_t(-1), buf), string_view("4294967295"));
+  using type = IntegerToString<uint32_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, 1234, "1234");
+  EXPECT(type, 12345, "12345");
+  EXPECT(type, 123456, "123456");
+  EXPECT(type, 1234567, "1234567");
+  EXPECT(type, 12345678, "12345678");
+  EXPECT(type, 123456789, "123456789");
+  EXPECT(type, 1234567890, "1234567890");
+  EXPECT(type, UINT32_MAX, "4294967295");
+  EXPECT(type, -1, "4294967295");
 }
 
 TEST(LlvmLibcIntegerToStringTest, INT32) {
-  char buf[IntegerToString::dec_bufsize<int32_t>()];
-  EXPECT_EQ(*IntegerToString::dec(int32_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(1234), buf), string_view("1234"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(12345), buf), string_view("12345"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(123456), buf), string_view("123456"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(1234567), buf),
-            string_view("1234567"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(12345678), buf),
-            string_view("12345678"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(123456789), buf),
-            string_view("123456789"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(1234567890), buf),
-            string_view("1234567890"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-1), buf), string_view("-1"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-12), buf), string_view("-12"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-123), buf), string_view("-123"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-1234), buf), string_view("-1234"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-12345), buf), string_view("-12345"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-123456), buf),
-            string_view("-123456"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-1234567), buf),
-            string_view("-1234567"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-12345678), buf),
-            string_view("-12345678"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-123456789), buf),
-            string_view("-123456789"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(-1234567890), buf),
-            string_view("-1234567890"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(INT32_MAX), buf),
-            string_view("2147483647"));
-  EXPECT_EQ(*IntegerToString::dec(int32_t(INT32_MIN), buf),
-            string_view("-2147483648"));
+  using type = IntegerToString<int32_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, 1234, "1234");
+  EXPECT(type, 12345, "12345");
+  EXPECT(type, 123456, "123456");
+  EXPECT(type, 1234567, "1234567");
+  EXPECT(type, 12345678, "12345678");
+  EXPECT(type, 123456789, "123456789");
+  EXPECT(type, 1234567890, "1234567890");
+  EXPECT(type, -1, "-1");
+  EXPECT(type, -12, "-12");
+  EXPECT(type, -123, "-123");
+  EXPECT(type, -1234, "-1234");
+  EXPECT(type, -12345, "-12345");
+  EXPECT(type, -123456, "-123456");
+  EXPECT(type, -1234567, "-1234567");
+  EXPECT(type, -12345678, "-12345678");
+  EXPECT(type, -123456789, "-123456789");
+  EXPECT(type, -1234567890, "-1234567890");
+  EXPECT(type, INT32_MAX, "2147483647");
+  EXPECT(type, INT32_MIN, "-2147483648");
 }
 
 TEST(LlvmLibcIntegerToStringTest, UINT64) {
-  char buf[IntegerToString::dec_bufsize<uint64_t>()];
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(1234), buf), string_view("1234"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(12345), buf), string_view("12345"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(123456), buf), string_view("123456"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(1234567), buf),
-            string_view("1234567"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(12345678), buf),
-            string_view("12345678"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(123456789), buf),
-            string_view("123456789"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(1234567890), buf),
-            string_view("1234567890"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(1234567890123456789), buf),
-            string_view("1234567890123456789"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(UINT64_MAX), buf),
-            string_view("18446744073709551615"));
-  EXPECT_EQ(*IntegerToString::dec(uint64_t(-1), buf),
-            string_view("18446744073709551615"));
+  using type = IntegerToString<uint64_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, 1234, "1234");
+  EXPECT(type, 12345, "12345");
+  EXPECT(type, 123456, "123456");
+  EXPECT(type, 1234567, "1234567");
+  EXPECT(type, 12345678, "12345678");
+  EXPECT(type, 123456789, "123456789");
+  EXPECT(type, 1234567890, "1234567890");
+  EXPECT(type, 1234567890123456789, "1234567890123456789");
+  EXPECT(type, UINT64_MAX, "18446744073709551615");
+  EXPECT(type, -1, "18446744073709551615");
 }
 
 TEST(LlvmLibcIntegerToStringTest, INT64) {
-  char buf[IntegerToString::dec_bufsize<int64_t>()];
-  EXPECT_EQ(*IntegerToString::dec(int64_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(1), buf), string_view("1"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(12), buf), string_view("12"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(123), buf), string_view("123"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(1234), buf), string_view("1234"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(12345), buf), string_view("12345"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(123456), buf), string_view("123456"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(1234567), buf),
-            string_view("1234567"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(12345678), buf),
-            string_view("12345678"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(123456789), buf),
-            string_view("123456789"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(1234567890), buf),
-            string_view("1234567890"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(1234567890123456789), buf),
-            string_view("1234567890123456789"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-1), buf), string_view("-1"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-12), buf), string_view("-12"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-123), buf), string_view("-123"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-1234), buf), string_view("-1234"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-12345), buf), string_view("-12345"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-123456), buf),
-            string_view("-123456"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-1234567), buf),
-            string_view("-1234567"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-12345678), buf),
-            string_view("-12345678"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-123456789), buf),
-            string_view("-123456789"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-1234567890), buf),
-            string_view("-1234567890"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(-1234567890123456789), buf),
-            string_view("-1234567890123456789"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(INT64_MAX), buf),
-            string_view("9223372036854775807"));
-  EXPECT_EQ(*IntegerToString::dec(int64_t(INT64_MIN), buf),
-            string_view("-9223372036854775808"));
+  using type = IntegerToString<int64_t>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 1, "1");
+  EXPECT(type, 12, "12");
+  EXPECT(type, 123, "123");
+  EXPECT(type, 1234, "1234");
+  EXPECT(type, 12345, "12345");
+  EXPECT(type, 123456, "123456");
+  EXPECT(type, 1234567, "1234567");
+  EXPECT(type, 12345678, "12345678");
+  EXPECT(type, 123456789, "123456789");
+  EXPECT(type, 1234567890, "1234567890");
+  EXPECT(type, 1234567890123456789, "1234567890123456789");
+  EXPECT(type, -1, "-1");
+  EXPECT(type, -12, "-12");
+  EXPECT(type, -123, "-123");
+  EXPECT(type, -1234, "-1234");
+  EXPECT(type, -12345, "-12345");
+  EXPECT(type, -123456, "-123456");
+  EXPECT(type, -1234567, "-1234567");
+  EXPECT(type, -12345678, "-12345678");
+  EXPECT(type, -123456789, "-123456789");
+  EXPECT(type, -1234567890, "-1234567890");
+  EXPECT(type, -1234567890123456789, "-1234567890123456789");
+  EXPECT(type, INT64_MAX, "9223372036854775807");
+  EXPECT(type, INT64_MIN, "-9223372036854775808");
 }
 
 TEST(LlvmLibcIntegerToStringTest, UINT64_Base_8) {
-  char buf[IntegerToString::oct_bufsize<uint64_t>()];
-  EXPECT_EQ((*IntegerToString::oct(uint64_t(0), buf)), string_view("0"));
-  EXPECT_EQ((*IntegerToString::oct(uint64_t(012345), buf)),
-            string_view("12345"));
-  EXPECT_EQ((*IntegerToString::oct(uint64_t(0123456701234567012345), buf)),
-            string_view("123456701234567012345"));
-  EXPECT_EQ((*IntegerToString::oct(uint64_t(01777777777777777777777), buf)),
-            string_view("1777777777777777777777"));
+  using type = IntegerToString<int64_t, Oct>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 012345, "12345");
+  EXPECT(type, 0123456701234567012345, "123456701234567012345");
+  EXPECT(type, 01777777777777777777777, "1777777777777777777777");
 }
 
 TEST(LlvmLibcIntegerToStringTest, UINT64_Base_16) {
-  char buf[IntegerToString::hex_bufsize<uint64_t>()];
-  EXPECT_EQ(*IntegerToString::hex(uint64_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::hex(uint64_t(0x12345), buf), string_view("12345"));
-  EXPECT_EQ((*IntegerToString::hex(uint64_t(0x123456789abcdef), buf)),
-            string_view("123456789abcdef"));
-  EXPECT_EQ(*IntegerToString::hex(uint64_t(0x123456789abcdef), buf, false),
-            string_view("123456789ABCDEF"));
-  EXPECT_EQ(*IntegerToString::hex(uint64_t(0xffffffffffffffff), buf),
-            string_view("ffffffffffffffff"));
+  using type = IntegerToString<uint64_t, Hex>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 0x12345, "12345");
+  EXPECT(type, 0x123456789abcdef, "123456789abcdef");
+  EXPECT(type, 0xffffffffffffffff, "ffffffffffffffff");
+  using TYPE = IntegerToString<uint64_t, Hex::Uppercase>;
+  EXPECT(TYPE, 0x123456789abcdef, "123456789ABCDEF");
 }
 
 TEST(LlvmLibcIntegerToStringTest, UINT64_Base_2) {
-  char buf[IntegerToString::bin_bufsize<uint64_t>()];
-  EXPECT_EQ(*IntegerToString::bin(uint64_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::bin(uint64_t(0xf0c), buf),
-            string_view("111100001100"));
-  EXPECT_EQ(*IntegerToString::bin(uint64_t(0x123abc), buf),
-            string_view("100100011101010111100"));
-  EXPECT_EQ(
-      *IntegerToString::bin(uint64_t(0xffffffffffffffff), buf),
-      string_view(
-          "1111111111111111111111111111111111111111111111111111111111111111"));
+  using type = IntegerToString<uint64_t, Bin>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 0b111100001100, "111100001100");
+  EXPECT(type, 0b100100011101010111100, "100100011101010111100");
+  EXPECT(type, 0xffffffffffffffff,
+         "1111111111111111111111111111111111111111111111111111111111111111");
 }
 
-TEST(LlvmLibcIntegerToStringTest, UINT64_Base_36) {
-  char buf[IntegerToString::bufsize<36, uint64_t>()];
-  EXPECT_EQ(*IntegerToString::convert<36>(uint64_t(0), buf), string_view("0"));
-  EXPECT_EQ(*IntegerToString::convert<36>(uint64_t(12345), buf),
-            string_view("9ix"));
-  EXPECT_EQ(*IntegerToString::convert<36>(uint64_t(1047601316295595), buf),
-            string_view("abcdefghij"));
-  EXPECT_EQ(*IntegerToString::convert<36>(uint64_t(2092218013456445), buf),
-            string_view("klmnopqrst"));
-  EXPECT_EQ(*IntegerToString::convert<36>(uint64_t(1867590395), buf, false),
-            string_view("UVWXYZ"));
-  EXPECT_EQ(*IntegerToString::convert<36>(uint64_t(0xffffffffffffffff), buf),
-            string_view("3w5e11264sgsf"));
+TEST(LlvmLibcIntegerToStringTest, UINT128_Base_16) {
+  using type = IntegerToString<UInt128, Hex::WithWidth<32>>;
+  EXPECT(type, 0, "00000000000000000000000000000000");
+  EXPECT(type, 0x12345, "00000000000000000000000000012345");
+  EXPECT(type, static_cast<UInt128>(0x1234) << 112,
+         "12340000000000000000000000000000");
+  EXPECT(type, static_cast<UInt128>(0x1234) << 48,
+         "00000000000000001234000000000000");
+  EXPECT(type, static_cast<UInt128>(0x1234) << 52,
+         "00000000000000012340000000000000");
 }
 
-TEST(LlvmLibcIntegerToStringTest, UINT128_Base_16) {
-  char buf[IntegerToString::hex_bufsize<UInt128>()];
-  EXPECT_EQ(*IntegerToString::hex(static_cast<UInt128>(0), buf),
-            string_view("00000000000000000000000000000000"));
-  EXPECT_EQ(*IntegerToString::hex(static_cast<UInt128>(0x12345), buf),
-            string_view("00000000000000000000000000012345"));
-  EXPECT_EQ((*IntegerToString::hex(static_cast<UInt128>(0x1234) << 112, buf)),
-            string_view("12340000000000000000000000000000"));
-  EXPECT_EQ((*IntegerToString::hex(static_cast<UInt128>(0x1234) << 48, buf)),
-            string_view("00000000000000001234000000000000"));
-  EXPECT_EQ((*IntegerToString::hex(static_cast<UInt128>(0x1234) << 52, buf)),
-            string_view("00000000000000012340000000000000"));
+TEST(LlvmLibcIntegerToStringTest, UINT64_Base_36) {
+  using type = IntegerToString<uint64_t, Custom<36>>;
+  EXPECT(type, 0, "0");
+  EXPECT(type, 12345, "9ix");
+  EXPECT(type, 1047601316295595, "abcdefghij");
+  EXPECT(type, 2092218013456445, "klmnopqrst");
+  EXPECT(type, 0xffffffffffffffff, "3w5e11264sgsf");
+
+  using TYPE = IntegerToString<uint64_t, Custom<36>::Uppercase>;
+  EXPECT(TYPE, 1867590395, "UVWXYZ");
 }
 
 TEST(LlvmLibcIntegerToStringTest, UINT256_Base_16) {
   using UInt256 = __llvm_libc::cpp::UInt<256>;
-  char buf[IntegerToString::hex_bufsize<UInt256>()];
-  EXPECT_EQ(
-      *IntegerToString::hex(static_cast<UInt256>(0), buf),
-      string_view(
-          "0000000000000000000000000000000000000000000000000000000000000000"));
-  EXPECT_EQ(
-      *IntegerToString::hex(static_cast<UInt256>(0x12345), buf),
-      string_view(
-          "0000000000000000000000000000000000000000000000000000000000012345"));
-  EXPECT_EQ(
-      (*IntegerToString::hex(static_cast<UInt256>(0x1234) << 112, buf)),
-      string_view(
-          "0000000000000000000000000000000012340000000000000000000000000000"));
-  EXPECT_EQ(
-      (*IntegerToString::hex(static_cast<UInt256>(0x1234) << 116, buf)),
-      string_view(
-          "0000000000000000000000000000000123400000000000000000000000000000"));
-  EXPECT_EQ(
-      (*IntegerToString::hex(static_cast<UInt256>(0x1234) << 240, buf)),
-      string_view(
-          "1234000000000000000000000000000000000000000000000000000000000000"));
+  using type = IntegerToString<UInt256, Hex::WithWidth<64>>;
+  EXPECT(type, static_cast<UInt256>(0),
+         "0000000000000000000000000000000000000000000000000000000000000000");
+  EXPECT(type, static_cast<UInt256>(0x12345),
+         "0000000000000000000000000000000000000000000000000000000000012345");
+  EXPECT(type, static_cast<UInt256>(0x1234) << 112,
+         "0000000000000000000000000000000012340000000000000000000000000000");
+  EXPECT(type, static_cast<UInt256>(0x1234) << 116,
+         "0000000000000000000000000000000123400000000000000000000000000000");
+  EXPECT(type, static_cast<UInt256>(0x1234) << 240,
+         "1234000000000000000000000000000000000000000000000000000000000000");
+}
+
+TEST(LlvmLibcIntegerToStringTest, NegativeInterpretedAsPositive) {
+  using BIN = IntegerToString<int8_t, Bin>;
+  using OCT = IntegerToString<int8_t, Oct>;
+  using DEC = IntegerToString<int8_t, Dec>;
+  using HEX = IntegerToString<int8_t, Hex>;
+  EXPECT(BIN, -1, "11111111");
+  EXPECT(OCT, -1, "377");
+  EXPECT(DEC, -1, "-1"); // Only DEC format negative values
+  EXPECT(HEX, -1, "ff");
+}
+
+TEST(LlvmLibcIntegerToStringTest, Width) {
+  using BIN = IntegerToString<uint8_t, Bin::WithWidth<4>>;
+  using OCT = IntegerToString<uint8_t, Oct::WithWidth<4>>;
+  using DEC = IntegerToString<uint8_t, Dec::WithWidth<4>>;
+  using HEX = IntegerToString<uint8_t, Hex::WithWidth<4>>;
+  EXPECT(BIN, 1, "0001");
+  EXPECT(HEX, 1, "0001");
+  EXPECT(OCT, 1, "0001");
+  EXPECT(DEC, 1, "0001");
+}
+
+TEST(LlvmLibcIntegerToStringTest, Prefix) {
+  // WithPrefix is not supported for Decimal
+  using BIN = IntegerToString<uint8_t, Bin::WithPrefix>;
+  using OCT = IntegerToString<uint8_t, Oct::WithPrefix>;
+  using HEX = IntegerToString<uint8_t, Hex::WithPrefix>;
+  EXPECT(BIN, 1, "0b1");
+  EXPECT(HEX, 1, "0x1");
+  EXPECT(OCT, 1, "01");
+  EXPECT(OCT, 0, "0"); // Zero is not prefixed for octal
+}
+
+TEST(LlvmLibcIntegerToStringTest, Uppercase) {
+  using HEX = IntegerToString<uint64_t, Hex::Uppercase>;
+  EXPECT(HEX, 0xDEADC0DE, "DEADC0DE");
+}
+
+TEST(LlvmLibcIntegerToStringTest, Sign) {
+  // WithSign only compiles with DEC
+  using DEC = IntegerToString<int8_t, Dec::WithSign>;
+  EXPECT(DEC, -1, "-1");
+  EXPECT(DEC, 0, "+0");
+  EXPECT(DEC, 1, "+1");
+}
+
+TEST(LlvmLibcIntegerToStringTest, BufferOverrun) {
+  { // Writing '0' in an empty buffer requiring zero digits : works
+    const auto view =
+        IntegerToString<int, Dec::WithWidth<0>>::format_to(span<char>(), 0);
+    ASSERT_TRUE(view.has_value());
+    ASSERT_EQ(*view, string_view(""));
+  }
+  char buffer[1];
+  { // Writing '1' in a buffer of one char : works
+    const auto view = IntegerToString<int>::format_to(buffer, 1);
+    ASSERT_TRUE(view.has_value());
+    ASSERT_EQ(*view, string_view("1"));
+  }
+  { // Writing '11' in a buffer of one char : fails
+    const auto view = IntegerToString<int>::format_to(buffer, 11);
+    ASSERT_FALSE(view.has_value());
+  }
 }

diff  --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 8284e157a5b090..80d5deae9fc841 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -413,6 +413,8 @@ libc_support_library(
     hdrs = ["src/__support/integer_to_string.h"],
     deps = [
         ":__support_common",
+        ":__support_cpp_bit",
+        ":__support_cpp_limits",
         ":__support_cpp_optional",
         ":__support_cpp_span",
         ":__support_cpp_string_view",


        


More information about the libc-commits mailing list