[libc++] Fixes std::to_chars for bases != 10.

While working on D70631, Microsoft's unit tests discovered an issue.
Our `std::to_chars` implementation for bases != 10 uses the range
`[first,last)` as temporary buffer. This violates the contract for
to_chars:
[charconv.to.chars]/1 http://eel.is/c++draft/charconv#to.chars-1
`to_chars_result to_chars(char* first, char* last, see below value, int base = 10);`
"If the member ec of the return value is such that the value is equal to
the value of a value-initialized errc, the conversion was successful and
the member ptr is the one-past-the-end pointer of the characters
written."

Our implementation modifies the range `[member ptr, last)`, which causes
Microsoft's test to fail. Their test verifies the buffer
`[member ptr, last)` is unchanged. (The test is only done when the
conversion is successful.)

While looking at the code I noticed the performance for bases != 10 also
is suboptimal. This is tracked in D97705.

This patch fixes the issue and adds a benchmark. This benchmark will be
used as baseline for D97705.

Reviewed By: #libc, Quuxplusone, zoecarver

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

NOKEYCHECK=True
GitOrigin-RevId: 9393060f908b95f30267f2122b3a2aadb698aadb
diff --git a/benchmarks/to_chars.bench.cpp b/benchmarks/to_chars.bench.cpp
new file mode 100644
index 0000000..1a3dc64
--- /dev/null
+++ b/benchmarks/to_chars.bench.cpp
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <array>
+#include <charconv>
+#include <random>
+
+#include "benchmark/benchmark.h"
+#include "test_macros.h"
+
+static const std::array<unsigned, 1000> input = [] {
+  std::mt19937 generator;
+  std::uniform_int_distribution<unsigned> distribution(0, std::numeric_limits<unsigned>::max());
+  std::array<unsigned, 1000> result;
+  std::generate_n(result.begin(), result.size(), [&] { return distribution(generator); });
+  return result;
+}();
+
+static void BM_to_chars_good(benchmark::State& state) {
+  char buffer[128];
+  int base = state.range(0);
+  while (state.KeepRunningBatch(input.size()))
+    for (auto value : input)
+      benchmark::DoNotOptimize(std::to_chars(buffer, &buffer[128], value, base));
+}
+BENCHMARK(BM_to_chars_good)->DenseRange(2, 36, 1);
+
+static void BM_to_chars_bad(benchmark::State& state) {
+  char buffer[128];
+  int base = state.range(0);
+  struct sample {
+    unsigned size;
+    unsigned value;
+  };
+  std::array<sample, 1000> data;
+  // Assume the failure occurs, on average, halfway during the conversion.
+  std::transform(input.begin(), input.end(), data.begin(), [&](unsigned value) {
+    std::to_chars_result result = std::to_chars(buffer, &buffer[128], value, base);
+    return sample{unsigned((result.ptr - buffer) / 2), value};
+  });
+
+  while (state.KeepRunningBatch(data.size()))
+    for (auto element : data)
+      benchmark::DoNotOptimize(std::to_chars(buffer, &buffer[element.size], element.value, base));
+}
+BENCHMARK(BM_to_chars_bad)->DenseRange(2, 36, 1);
+
+int main(int argc, char** argv) {
+  benchmark::Initialize(&argc, argv);
+  if (benchmark::ReportUnrecognizedArguments(argc, argv))
+    return 1;
+
+  benchmark::RunSpecifiedBenchmarks();
+}