refactor ToExponentAndMantissa to use StrFormat instead of std::stringstream (#2138)

* perf: refactor ToExponentAndMantissa to use StrFormat instead of std::stringstream

* return pair instead of using output parameters

* Bump astral-sh/setup-uv from 7.3.0 to 7.3.1 (#2136)

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/eac588ad8def6316056a12d4907a9d4d84ff7a3b...5a095e7a2014a4212f075830d4f7277575a9d098)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 7.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dominic <510002+dmah42@users.noreply.github.com>

* lambda for mantissa formatting

* structured bindings and clearer code structure

* multiple fixes to avoid Windows x64 crashes (#2139)

* fix: cast size_t widths to int for variadic printer to avoid Windows x64 crashes

* extend minimum time as Windows can have a coarse timer

* vsnprintf can consume va_list so we need to copy it to avoid UB

* extend longer test runtime to other problematic tests

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
diff --git a/src/string_util.cc b/src/string_util.cc
index ae9667b..9a0d542 100644
--- a/src/string_util.cc
+++ b/src/string_util.cc
@@ -31,13 +31,15 @@
 
 const int64_t kUnitsSize = arraysize(kBigSIUnits);
 
-void ToExponentAndMantissa(double val, int precision, double one_k,
-                           std::string* mantissa, int64_t* exponent) {
-  std::stringstream mantissa_stream;
-
+std::pair<std::string, int64_t> ToExponentAndMantissa(double val, int precision,
+                                                      double one_k) {
+  std::string mantissa;
+  int64_t exponent = 0;
   if (val < 0) {
-    mantissa_stream << "-";
+    mantissa = "-";
     val = -val;
+  } else {
+    mantissa.clear();
   }
 
   // Adjust threshold so that it never excludes things which can't be rendered
@@ -49,41 +51,45 @@
   // Values in ]simple_threshold,small_threshold[ will be printed as-is
   const double simple_threshold = 0.01;
 
+  auto format_mantissa = [&](double v) { mantissa += StrFormat("%g", v); };
+
+  // Positive powers
   if (val > big_threshold) {
-    // Positive powers
     double scaled = val;
     for (size_t i = 0; i < arraysize(kBigSIUnits); ++i) {
       scaled /= one_k;
       if (scaled <= big_threshold) {
-        mantissa_stream << scaled;
-        *exponent = static_cast<int64_t>(i + 1);
-        *mantissa = mantissa_stream.str();
-        return;
+        format_mantissa(scaled);
+        exponent = static_cast<int64_t>(i + 1);
+        return std::make_pair(mantissa, exponent);
       }
     }
-    mantissa_stream << val;
-    *exponent = 0;
-  } else if (val < small_threshold) {
-    // Negative powers
+    format_mantissa(val);
+    exponent = 0;
+    return std::make_pair(mantissa, exponent);
+  }
+
+  // Negative powers
+  if (val < small_threshold) {
     if (val < simple_threshold) {
       double scaled = val;
       for (size_t i = 0; i < arraysize(kSmallSIUnits); ++i) {
         scaled *= one_k;
         if (scaled >= small_threshold) {
-          mantissa_stream << scaled;
-          *exponent = -static_cast<int64_t>(i + 1);
-          *mantissa = mantissa_stream.str();
-          return;
+          format_mantissa(scaled);
+          exponent = -static_cast<int64_t>(i + 1);
+          return std::make_pair(mantissa, exponent);
         }
       }
     }
-    mantissa_stream << val;
-    *exponent = 0;
-  } else {
-    mantissa_stream << val;
-    *exponent = 0;
+    format_mantissa(val);
+    exponent = 0;
+    return std::make_pair(mantissa, exponent);
   }
-  *mantissa = mantissa_stream.str();
+
+  format_mantissa(val);
+  exponent = 0;
+  return std::make_pair(mantissa, exponent);
 }
 
 std::string ExponentToPrefix(int64_t exponent, bool iec) {
@@ -104,11 +110,8 @@
 
 std::string ToBinaryStringFullySpecified(double value, int precision,
                                          Counter::OneK one_k) {
-  std::string mantissa;
-  int64_t exponent = 0;
-  ToExponentAndMantissa(value, precision,
-                        one_k == Counter::kIs1024 ? 1024.0 : 1000.0, &mantissa,
-                        &exponent);
+  auto [mantissa, exponent] = ToExponentAndMantissa(
+      value, precision, one_k == Counter::kIs1024 ? 1024.0 : 1000.0);
   return mantissa + ExponentToPrefix(exponent, one_k == Counter::kIs1024);
 }