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:
1 parent
0d7e176b3c
commit
dccbf8b1da
1 file changed
+20
-11
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user