Reflection files: the mmCIF and SHELX numbers formatted with std::to_chars

Writing the merged reflections - p.mtz, p.cif, p.hkl, ~250 MB for 4nwv - takes 5.3 s on the
Windows laptop against 0.46 s on Linux. The mmCIF rows (twelve "%.4f" fields each) and the
HKLF 4 records (two "%.2f") are formatted with snprintf, ~50 M calls for 4nwv; the likely
Windows cost is the MSVC runtime's printf (slower float formatting, locale handling per call) -
not measured there yet. Fmt and the HKLF 4 number now go through std::to_chars(fixed, precision) where the
standard library has it for floating point (__cpp_lib_to_chars: libstdc++ 11+, MSVC); libc++
before macOS 13.3 does not, and keeps snprintf.

The same digits: both round the exact binary value correctly, halfway cases to even. Checked
against glibc on 20 M float-valued doubles at 4 and 2 decimals and on 8 M exact binary
fractions (every tie), 0 differences. On this machine (gcc-toolset-13) 52 against 194 ns a
field. p.hkl, the MTZ data and p.cif (apart from its provenance lines) byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-17 12:42:09 +02:00
1 parent 0d7e176b3c
commit dccbf8b1da
1 file changed
+20 -11
+20 -11
View File
@@ -9,6 +9,7 @@
#include "../common/ParallelFor.h"
#include <algorithm>
#include <charconv>
#include <cmath>
#include <cctype>
#include <cstring>
@@ -45,16 +46,28 @@ std::string CurrentDateISO() {
return buf;
}
/// The C locale's "%.*f" of a finite double. std::to_chars where the standard library has it for
/// floating point: the same digits - both round the exact binary value, halfway cases to even - at a
/// quarter of snprintf's time, and with no locale to consult. snprintf elsewhere (libc++ before macOS
/// 13.3). 512 characters hold any finite double.
std::string FixedDigits(double val, int decimals) {
char buf[512];
#ifdef __cpp_lib_to_chars
const auto r = std::to_chars(buf, buf + sizeof(buf), val, std::chars_format::fixed, decimals);
return std::string(buf, r.ptr);
#else
const int n = std::snprintf(buf, sizeof(buf), "%.*f", decimals, val);
return std::string(buf, n);
#endif
}
/// Format a double with given decimal places; returns "?" for non-finite.
/// snprintf rather than an ostringstream: the reflection loop below calls this twelve times per
/// reflection, and building a stream (and its locale) per call dominated the time spent writing a
/// merged file. Same digits - both go through the C locale's %.*f.
/// Not an ostringstream: the reflection loop below calls this twelve times per reflection, and
/// building a stream (and its locale) per call dominated the time spent writing a merged file.
std::string Fmt(double val, int decimals = 4) {
if (!std::isfinite(val))
return "?";
char buf[512];
const int n = std::snprintf(buf, sizeof(buf), "%.*f", decimals, val);
return std::string(buf, n);
return FixedDigits(val, decimals);
}
/// Quote a CIF string value; returns "?" for empty.
@@ -691,11 +704,7 @@ namespace {
if (v.size() < width) s.append(width - v.size(), ' ');
s.append(v);
};
const auto num2 = [](double v) {
char b[64];
const int n = std::snprintf(b, sizeof b, "%.2f", v);
return std::string(b, static_cast<size_t>(std::clamp(n, 0, static_cast<int>(sizeof b) - 1)));
};
const auto num2 = [](double v) { return FixedDigits(v, 2); };
column(std::to_string(h), 4);
column(std::to_string(k), 4);
column(std::to_string(l), 4);