|  | @@ -12,14 +12,13 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #include "absl/base/port.h"
 | 
	
		
			
				|  |  |  #include "absl/strings/internal/str_format/float_conversion.h"
 | 
	
		
			
				|  |  | +#include "absl/strings/numbers.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  namespace absl {
 | 
	
		
			
				|  |  |  ABSL_NAMESPACE_BEGIN
 | 
	
		
			
				|  |  |  namespace str_format_internal {
 | 
	
		
			
				|  |  |  namespace {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const char kDigit[2][32] = { "0123456789abcdef", "0123456789ABCDEF" };
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  // Reduce *capacity by s.size(), clipped to a 0 minimum.
 | 
	
		
			
				|  |  |  void ReducePadding(string_view s, size_t *capacity) {
 | 
	
		
			
				|  |  |    *capacity = Excess(s.size(), *capacity);
 | 
	
	
		
			
				|  | @@ -48,94 +47,144 @@ struct IsSigned<absl::int128> : std::true_type {};
 | 
	
		
			
				|  |  |  template <>
 | 
	
		
			
				|  |  |  struct IsSigned<absl::uint128> : std::false_type {};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -class ConvertedIntInfo {
 | 
	
		
			
				|  |  | +// Integral digit printer.
 | 
	
		
			
				|  |  | +// Call one of the PrintAs* routines after construction once.
 | 
	
		
			
				|  |  | +// Use with_neg_and_zero/without_neg_or_zero/is_negative to access the results.
 | 
	
		
			
				|  |  | +class IntDigits {
 | 
	
		
			
				|  |  |   public:
 | 
	
		
			
				|  |  | +  // Print the unsigned integer as octal.
 | 
	
		
			
				|  |  | +  // Supports unsigned integral types and uint128.
 | 
	
		
			
				|  |  | +  template <typename T>
 | 
	
		
			
				|  |  | +  void PrintAsOct(T v) {
 | 
	
		
			
				|  |  | +    static_assert(!IsSigned<T>::value, "");
 | 
	
		
			
				|  |  | +    char *p = storage_ + sizeof(storage_);
 | 
	
		
			
				|  |  | +    do {
 | 
	
		
			
				|  |  | +      *--p = static_cast<char>('0' + (static_cast<size_t>(v) & 7));
 | 
	
		
			
				|  |  | +      v >>= 3;
 | 
	
		
			
				|  |  | +    } while (v);
 | 
	
		
			
				|  |  | +    start_ = p;
 | 
	
		
			
				|  |  | +    size_ = storage_ + sizeof(storage_) - p;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Print the signed or unsigned integer as decimal.
 | 
	
		
			
				|  |  | +  // Supports all integral types.
 | 
	
		
			
				|  |  |    template <typename T>
 | 
	
		
			
				|  |  | -  ConvertedIntInfo(T v, ConversionChar conv) {
 | 
	
		
			
				|  |  | -    using Unsigned = typename MakeUnsigned<T>::type;
 | 
	
		
			
				|  |  | -    auto u = static_cast<Unsigned>(v);
 | 
	
		
			
				|  |  | -    if (IsNeg(v)) {
 | 
	
		
			
				|  |  | -      is_neg_ = true;
 | 
	
		
			
				|  |  | -      u = Unsigned{} - u;
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      is_neg_ = false;
 | 
	
		
			
				|  |  | +  void PrintAsDec(T v) {
 | 
	
		
			
				|  |  | +    static_assert(std::is_integral<T>::value, "");
 | 
	
		
			
				|  |  | +    start_ = storage_;
 | 
	
		
			
				|  |  | +    size_ = numbers_internal::FastIntToBuffer(v, storage_) - storage_;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void PrintAsDec(int128 v) {
 | 
	
		
			
				|  |  | +    auto u = static_cast<uint128>(v);
 | 
	
		
			
				|  |  | +    bool add_neg = false;
 | 
	
		
			
				|  |  | +    if (v < 0) {
 | 
	
		
			
				|  |  | +      add_neg = true;
 | 
	
		
			
				|  |  | +      u = uint128{} - u;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    UnsignedToStringRight(u, conv);
 | 
	
		
			
				|  |  | +    PrintAsDec(u, add_neg);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  string_view digits() const {
 | 
	
		
			
				|  |  | -    return {end() - size_, static_cast<size_t>(size_)};
 | 
	
		
			
				|  |  | +  void PrintAsDec(uint128 v, bool add_neg = false) {
 | 
	
		
			
				|  |  | +    // This function can be sped up if needed. We can call FastIntToBuffer
 | 
	
		
			
				|  |  | +    // twice, or fix FastIntToBuffer to support uint128.
 | 
	
		
			
				|  |  | +    char *p = storage_ + sizeof(storage_);
 | 
	
		
			
				|  |  | +    do {
 | 
	
		
			
				|  |  | +      p -= 2;
 | 
	
		
			
				|  |  | +      numbers_internal::PutTwoDigits(static_cast<size_t>(v % 100), p);
 | 
	
		
			
				|  |  | +      v /= 100;
 | 
	
		
			
				|  |  | +    } while (v);
 | 
	
		
			
				|  |  | +    if (p[0] == '0') {
 | 
	
		
			
				|  |  | +      // We printed one too many hexits.
 | 
	
		
			
				|  |  | +      ++p;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if (add_neg) {
 | 
	
		
			
				|  |  | +      *--p = '-';
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    size_ = storage_ + sizeof(storage_) - p;
 | 
	
		
			
				|  |  | +    start_ = p;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  bool is_neg() const { return is_neg_; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | - private:
 | 
	
		
			
				|  |  | -  template <typename T, bool IsSigned>
 | 
	
		
			
				|  |  | -  struct IsNegImpl {
 | 
	
		
			
				|  |  | -    static bool Eval(T v) { return v < 0; }
 | 
	
		
			
				|  |  | -  };
 | 
	
		
			
				|  |  | +  // Print the unsigned integer as hex using lowercase.
 | 
	
		
			
				|  |  | +  // Supports unsigned integral types and uint128.
 | 
	
		
			
				|  |  |    template <typename T>
 | 
	
		
			
				|  |  | -  struct IsNegImpl<T, false> {
 | 
	
		
			
				|  |  | -    static bool Eval(T) {
 | 
	
		
			
				|  |  | -      return false;
 | 
	
		
			
				|  |  | +  void PrintAsHexLower(T v) {
 | 
	
		
			
				|  |  | +    static_assert(!IsSigned<T>::value, "");
 | 
	
		
			
				|  |  | +    char *p = storage_ + sizeof(storage_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    do {
 | 
	
		
			
				|  |  | +      p -= 2;
 | 
	
		
			
				|  |  | +      constexpr const char* table = numbers_internal::kHexTable;
 | 
	
		
			
				|  |  | +      std::memcpy(p, table + 2 * (static_cast<size_t>(v) & 0xFF), 2);
 | 
	
		
			
				|  |  | +      if (sizeof(T) == 1) break;
 | 
	
		
			
				|  |  | +      v >>= 8;
 | 
	
		
			
				|  |  | +    } while (v);
 | 
	
		
			
				|  |  | +    if (p[0] == '0') {
 | 
	
		
			
				|  |  | +      // We printed one too many digits.
 | 
	
		
			
				|  |  | +      ++p;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -  };
 | 
	
		
			
				|  |  | +    start_ = p;
 | 
	
		
			
				|  |  | +    size_ = storage_ + sizeof(storage_) - p;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  // Print the unsigned integer as hex using uppercase.
 | 
	
		
			
				|  |  | +  // Supports unsigned integral types and uint128.
 | 
	
		
			
				|  |  |    template <typename T>
 | 
	
		
			
				|  |  | -  bool IsNeg(T v) {
 | 
	
		
			
				|  |  | -    return IsNegImpl<T, IsSigned<T>::value>::Eval(v);
 | 
	
		
			
				|  |  | +  void PrintAsHexUpper(T v) {
 | 
	
		
			
				|  |  | +    static_assert(!IsSigned<T>::value, "");
 | 
	
		
			
				|  |  | +    char *p = storage_ + sizeof(storage_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // kHexTable is only lowercase, so do it manually for uppercase.
 | 
	
		
			
				|  |  | +    do {
 | 
	
		
			
				|  |  | +      *--p = "0123456789ABCDEF"[static_cast<size_t>(v) & 15];
 | 
	
		
			
				|  |  | +      v >>= 4;
 | 
	
		
			
				|  |  | +    } while (v);
 | 
	
		
			
				|  |  | +    start_ = p;
 | 
	
		
			
				|  |  | +    size_ = storage_ + sizeof(storage_) - p;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  template <typename T>
 | 
	
		
			
				|  |  | -  void UnsignedToStringRight(T u, ConversionChar conv) {
 | 
	
		
			
				|  |  | -    char *p = end();
 | 
	
		
			
				|  |  | -    switch (FormatConversionCharRadix(conv)) {
 | 
	
		
			
				|  |  | -      default:
 | 
	
		
			
				|  |  | -      case 10:
 | 
	
		
			
				|  |  | -        for (; u; u /= 10)
 | 
	
		
			
				|  |  | -          *--p = static_cast<char>('0' + static_cast<size_t>(u % 10));
 | 
	
		
			
				|  |  | -        break;
 | 
	
		
			
				|  |  | -      case 8:
 | 
	
		
			
				|  |  | -        for (; u; u /= 8)
 | 
	
		
			
				|  |  | -          *--p = static_cast<char>('0' + static_cast<size_t>(u % 8));
 | 
	
		
			
				|  |  | -        break;
 | 
	
		
			
				|  |  | -      case 16: {
 | 
	
		
			
				|  |  | -        const char *digits = kDigit[FormatConversionCharIsUpper(conv) ? 1 : 0];
 | 
	
		
			
				|  |  | -        for (; u; u /= 16) *--p = digits[static_cast<size_t>(u % 16)];
 | 
	
		
			
				|  |  | -        break;
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    size_ = static_cast<int>(end() - p);
 | 
	
		
			
				|  |  | +  // The printed value including the '-' sign if available.
 | 
	
		
			
				|  |  | +  // For inputs of value `0`, this will return "0"
 | 
	
		
			
				|  |  | +  string_view with_neg_and_zero() const { return {start_, size_}; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // The printed value not including the '-' sign.
 | 
	
		
			
				|  |  | +  // For inputs of value `0`, this will return "".
 | 
	
		
			
				|  |  | +  string_view without_neg_or_zero() const {
 | 
	
		
			
				|  |  | +    static_assert('-' < '0', "The check below verifies both.");
 | 
	
		
			
				|  |  | +    size_t advance = start_[0] <= '0' ? 1 : 0;
 | 
	
		
			
				|  |  | +    return {start_ + advance, size_ - advance};
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  const char *end() const { return storage_ + sizeof(storage_); }
 | 
	
		
			
				|  |  | -  char *end() { return storage_ + sizeof(storage_); }
 | 
	
		
			
				|  |  | +  bool is_negative() const { return start_[0] == '-'; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  bool is_neg_;
 | 
	
		
			
				|  |  | -  int size_;
 | 
	
		
			
				|  |  | -  // Max size: 128 bit value as octal -> 43 digits
 | 
	
		
			
				|  |  | -  char storage_[128 / 3 + 1];
 | 
	
		
			
				|  |  | + private:
 | 
	
		
			
				|  |  | +  const char *start_;
 | 
	
		
			
				|  |  | +  size_t size_;
 | 
	
		
			
				|  |  | +  // Max size: 128 bit value as octal -> 43 digits, plus sign char
 | 
	
		
			
				|  |  | +  char storage_[128 / 3 + 1 + 1];
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // Note: 'o' conversions do not have a base indicator, it's just that
 | 
	
		
			
				|  |  |  // the '#' flag is specified to modify the precision for 'o' conversions.
 | 
	
		
			
				|  |  | -string_view BaseIndicator(const ConvertedIntInfo &info,
 | 
	
		
			
				|  |  | +string_view BaseIndicator(const IntDigits &as_digits,
 | 
	
		
			
				|  |  |                            const ConversionSpec conv) {
 | 
	
		
			
				|  |  | -  bool alt = conv.has_alt_flag();
 | 
	
		
			
				|  |  | -  int radix = FormatConversionCharRadix(conv.conversion_char());
 | 
	
		
			
				|  |  | -  if (conv.conversion_char() == ConversionChar::p)
 | 
	
		
			
				|  |  | -    alt = true;  // always show 0x for %p.
 | 
	
		
			
				|  |  | +  // always show 0x for %p.
 | 
	
		
			
				|  |  | +  bool alt = conv.has_alt_flag() || conv.conversion_char() == ConversionChar::p;
 | 
	
		
			
				|  |  | +  bool hex = (conv.conversion_char() == FormatConversionChar::x ||
 | 
	
		
			
				|  |  | +              conv.conversion_char() == FormatConversionChar::X ||
 | 
	
		
			
				|  |  | +              conv.conversion_char() == FormatConversionChar::p);
 | 
	
		
			
				|  |  |    // From the POSIX description of '#' flag:
 | 
	
		
			
				|  |  |    //   "For x or X conversion specifiers, a non-zero result shall have
 | 
	
		
			
				|  |  |    //   0x (or 0X) prefixed to it."
 | 
	
		
			
				|  |  | -  if (alt && radix == 16 && !info.digits().empty()) {
 | 
	
		
			
				|  |  | -    if (FormatConversionCharIsUpper(conv.conversion_char())) return "0X";
 | 
	
		
			
				|  |  | -    return "0x";
 | 
	
		
			
				|  |  | +  if (alt && hex && !as_digits.without_neg_or_zero().empty()) {
 | 
	
		
			
				|  |  | +    return conv.conversion_char() == FormatConversionChar::X ? "0X" : "0x";
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    return {};
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  string_view SignColumn(bool neg, const ConversionSpec conv) {
 | 
	
		
			
				|  |  | -  if (FormatConversionCharIsSigned(conv.conversion_char())) {
 | 
	
		
			
				|  |  | +  if (conv.conversion_char() == FormatConversionChar::d ||
 | 
	
		
			
				|  |  | +      conv.conversion_char() == FormatConversionChar::i) {
 | 
	
		
			
				|  |  |      if (neg) return "-";
 | 
	
		
			
				|  |  |      if (conv.has_show_pos_flag()) return "+";
 | 
	
		
			
				|  |  |      if (conv.has_sign_col_flag()) return " ";
 | 
	
	
		
			
				|  | @@ -154,20 +203,20 @@ bool ConvertCharImpl(unsigned char v, const ConversionSpec conv,
 | 
	
		
			
				|  |  |    return true;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -bool ConvertIntImplInner(const ConvertedIntInfo &info,
 | 
	
		
			
				|  |  | -                         const ConversionSpec conv, FormatSinkImpl *sink) {
 | 
	
		
			
				|  |  | +bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
 | 
	
		
			
				|  |  | +                             const ConversionSpec conv, FormatSinkImpl *sink) {
 | 
	
		
			
				|  |  |    // Print as a sequence of Substrings:
 | 
	
		
			
				|  |  |    //   [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
 | 
	
		
			
				|  |  |    size_t fill = 0;
 | 
	
		
			
				|  |  |    if (conv.width() >= 0) fill = conv.width();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  string_view formatted = info.digits();
 | 
	
		
			
				|  |  | +  string_view formatted = as_digits.without_neg_or_zero();
 | 
	
		
			
				|  |  |    ReducePadding(formatted, &fill);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  string_view sign = SignColumn(info.is_neg(), conv);
 | 
	
		
			
				|  |  | +  string_view sign = SignColumn(as_digits.is_negative(), conv);
 | 
	
		
			
				|  |  |    ReducePadding(sign, &fill);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  string_view base_indicator = BaseIndicator(info, conv);
 | 
	
		
			
				|  |  | +  string_view base_indicator = BaseIndicator(as_digits, conv);
 | 
	
		
			
				|  |  |    ReducePadding(base_indicator, &fill);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    int precision = conv.precision();
 | 
	
	
		
			
				|  | @@ -208,35 +257,54 @@ bool ConvertIntImplInner(const ConvertedIntInfo &info,
 | 
	
		
			
				|  |  |    return true;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -template <typename T>
 | 
	
		
			
				|  |  | -bool ConvertIntImplInner(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
 | 
	
		
			
				|  |  | -  ConvertedIntInfo info(v, conv.conversion_char());
 | 
	
		
			
				|  |  | -  if (conv.is_basic() && (conv.conversion_char() != ConversionChar::p)) {
 | 
	
		
			
				|  |  | -    if (info.is_neg()) sink->Append(1, '-');
 | 
	
		
			
				|  |  | -    if (info.digits().empty()) {
 | 
	
		
			
				|  |  | -      sink->Append(1, '0');
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      sink->Append(info.digits());
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    return true;
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -  return ConvertIntImplInner(info, conv, sink);
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  template <typename T>
 | 
	
		
			
				|  |  |  bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
 | 
	
		
			
				|  |  | -  if (FormatConversionCharIsFloat(conv.conversion_char())) {
 | 
	
		
			
				|  |  | -    return FormatConvertImpl(static_cast<double>(v), conv, sink).value;
 | 
	
		
			
				|  |  | +  using U = typename MakeUnsigned<T>::type;
 | 
	
		
			
				|  |  | +  IntDigits as_digits;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  switch (conv.conversion_char()) {
 | 
	
		
			
				|  |  | +    case FormatConversionChar::c:
 | 
	
		
			
				|  |  | +      return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    case FormatConversionChar::o:
 | 
	
		
			
				|  |  | +      as_digits.PrintAsOct(static_cast<U>(v));
 | 
	
		
			
				|  |  | +      break;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    case FormatConversionChar::x:
 | 
	
		
			
				|  |  | +      as_digits.PrintAsHexLower(static_cast<U>(v));
 | 
	
		
			
				|  |  | +      break;
 | 
	
		
			
				|  |  | +    case FormatConversionChar::X:
 | 
	
		
			
				|  |  | +      as_digits.PrintAsHexUpper(static_cast<U>(v));
 | 
	
		
			
				|  |  | +      break;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    case FormatConversionChar::u:
 | 
	
		
			
				|  |  | +      as_digits.PrintAsDec(static_cast<U>(v));
 | 
	
		
			
				|  |  | +      break;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    case FormatConversionChar::d:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::i:
 | 
	
		
			
				|  |  | +      as_digits.PrintAsDec(v);
 | 
	
		
			
				|  |  | +      break;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    case FormatConversionChar::a:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::e:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::f:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::g:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::A:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::E:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::F:
 | 
	
		
			
				|  |  | +    case FormatConversionChar::G:
 | 
	
		
			
				|  |  | +      return ConvertFloatImpl(static_cast<double>(v), conv, sink);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    default:
 | 
	
		
			
				|  |  | +      return false;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (conv.conversion_char() == ConversionChar::c)
 | 
	
		
			
				|  |  | -    return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
 | 
	
		
			
				|  |  | -  if (!FormatConversionCharIsIntegral(conv.conversion_char())) return false;
 | 
	
		
			
				|  |  | -  if (!FormatConversionCharIsSigned(conv.conversion_char()) &&
 | 
	
		
			
				|  |  | -      IsSigned<T>::value) {
 | 
	
		
			
				|  |  | -    using U = typename MakeUnsigned<T>::type;
 | 
	
		
			
				|  |  | -    return FormatConvertImpl(static_cast<U>(v), conv, sink).value;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (conv.is_basic()) {
 | 
	
		
			
				|  |  | +    sink->Append(as_digits.with_neg_and_zero());
 | 
	
		
			
				|  |  | +    return true;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  return ConvertIntImplInner(v, conv, sink);
 | 
	
		
			
				|  |  | +  return ConvertIntImplInnerSlow(as_digits, conv, sink);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  template <typename T>
 | 
	
	
		
			
				|  | @@ -296,7 +364,9 @@ ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec conv,
 | 
	
		
			
				|  |  |      sink->Append("(nil)");
 | 
	
		
			
				|  |  |      return {true};
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  return {ConvertIntImplInner(v.value, conv, sink)};
 | 
	
		
			
				|  |  | +  IntDigits as_digits;
 | 
	
		
			
				|  |  | +  as_digits.PrintAsHexLower(v.value);
 | 
	
		
			
				|  |  | +  return {ConvertIntImplInnerSlow(as_digits, conv, sink)};
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // ==================== Floats ====================
 |