bool Buffer::IToASCII()

in src/butil/strings/safe_sprintf.cc [275:421]


bool Buffer::IToASCII(bool sign, bool upcase, int64_t i, int base,
                      char pad, size_t padding, const char* prefix) {
  // Sanity check for parameters. None of these should ever fail, but see
  // above for the rationale why we can't call CHECK().
  DEBUG_CHECK(base >= 2, "");
  DEBUG_CHECK(base <= 16, "");
  DEBUG_CHECK(!sign || base == 10, "");
  DEBUG_CHECK(pad == '0' || pad == ' ', "");
  DEBUG_CHECK(padding <= kSSizeMax, "");
  DEBUG_CHECK(!(sign && prefix && *prefix), "");

  // Handle negative numbers, if the caller indicated that |i| should be
  // treated as a signed number; otherwise treat |i| as unsigned (even if the
  // MSB is set!)
  // Details are tricky, because of limited data-types, but equivalent pseudo-
  // code would look like:
  //   if (sign && i < 0)
  //     prefix = "-";
  //   num = abs(i);
  int minint = 0;
  uint64_t num;
  if (sign && i < 0) {
    prefix = "-";

    // Turn our number positive.
    if (i == std::numeric_limits<int64_t>::min()) {
      // The most negative integer needs special treatment.
      minint = 1;
      num = static_cast<uint64_t>(-(i + 1));
    } else {
      // "Normal" negative numbers are easy.
      num = static_cast<uint64_t>(-i);
    }
  } else {
    num = static_cast<uint64_t>(i);
  }

  // If padding with '0' zero, emit the prefix or '-' character now. Otherwise,
  // make the prefix accessible in reverse order, so that we can later output
  // it right between padding and the number.
  // We cannot choose the easier approach of just reversing the number, as that
  // fails in situations where we need to truncate numbers that have padding
  // and/or prefixes.
  const char* reverse_prefix = NULL;
  if (prefix && *prefix) {
    if (pad == '0') {
      while (*prefix) {
        if (padding) {
          --padding;
        }
        Out(*prefix++);
      }
      prefix = NULL;
    } else {
      for (reverse_prefix = prefix; *reverse_prefix; ++reverse_prefix) {
      }
    }
  } else
    prefix = NULL;
  const size_t prefix_length = reverse_prefix - prefix;

  // Loop until we have converted the entire number. Output at least one
  // character (i.e. '0').
  size_t start = count_;
  size_t discarded = 0;
  bool started = false;
  do {
    // Make sure there is still enough space left in our output buffer.
    if (count_ >= size_) {
      if (start < size_) {
        // It is rare that we need to output a partial number. But if asked
        // to do so, we will still make sure we output the correct number of
        // leading digits.
        // Since we are generating the digits in reverse order, we actually
        // have to discard digits in the order that we have already emitted
        // them. This is essentially equivalent to:
        //   memmove(buffer_ + start, buffer_ + start + 1, size_ - start - 1)
        for (char* move = buffer_ + start, *end = buffer_ + size_ - 1;
             move < end;
             ++move) {
          *move = move[1];
        }
        ++discarded;
        --count_;
      } else if (count_ - size_ > 1) {
        // Need to increment either |count_| or |discarded| to make progress.
        // The latter is more efficient, as it eventually triggers fast
        // handling of padding. But we have to ensure we don't accidentally
        // change the overall state (i.e. switch the state-machine from
        // discarding to non-discarding). |count_| needs to always stay
        // bigger than |size_|.
        --count_;
        ++discarded;
      }
    }

    // Output the next digit and (if necessary) compensate for the most
    // negative integer needing special treatment. This works because,
    // no matter the bit width of the integer, the lowest-most decimal
    // integer always ends in 2, 4, 6, or 8.
    if (!num && started) {
      if (reverse_prefix > prefix) {
        Out(*--reverse_prefix);
      } else {
        Out(pad);
      }
    } else {
      started = true;
      Out((upcase ? kUpCaseHexDigits : kDownCaseHexDigits)[num%base + minint]);
    }

    minint = 0;
    num /= base;

    // Add padding, if requested.
    if (padding > 0) {
      --padding;

      // Performance optimization for when we are asked to output excessive
      // padding, but our output buffer is limited in size.  Even if we output
      // a 64bit number in binary, we would never write more than 64 plus
      // prefix non-padding characters. So, once this limit has been passed,
      // any further state change can be computed arithmetically; we know that
      // by this time, our entire final output consists of padding characters
      // that have all already been output.
      if (discarded > 8*sizeof(num) + prefix_length) {
        IncrementCount(padding);
        padding = 0;
      }
    }
  } while (num || padding || (reverse_prefix > prefix));

  // Conversion to ASCII actually resulted in the digits being in reverse
  // order. We can't easily generate them in forward order, as we can't tell
  // the number of characters needed until we are done converting.
  // So, now, we reverse the string (except for the possible '-' sign).
  char* front = buffer_ + start;
  char* back = GetInsertionPoint();
  while (--back > front) {
    char ch = *back;
    *back = *front;
    *front++ = ch;
  }

  IncrementCount(discarded);
  return !discarded;
}