From 34365b3c1c43e46b28bc79c6aeaf46f2f747200b Mon Sep 17 00:00:00 2001 From: "DeepSeek V4.1 Flash" <> Date: Wed, 4 Nov 2026 14:31:02 +0800 Subject: [PATCH] =?UTF-8?q?perf(size):=20=E7=94=A8=E8=87=AA=E5=86=99?= =?UTF-8?q?=E6=95=B4=E6=95=B0=E6=A0=BC=E5=BC=8F=E5=8C=96=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E5=85=A8=E9=83=A8=20std::to=5Fwstring=20/=20std::to=5Fstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景:Code Review 报告 §10.3 / §11.5 建议「替换 std::to_wstring / std::to_string, 可去掉 gdtoa 链(≈33 KB)」。本轮把它做完,并给出这条建议的**真实边界**。 一、新增 `Utf8Util::FormatInteger*`(手写十进制展开,不经 printf) * `FormatInteger(long long)` / `FormatIntegerUnsigned(unsigned long long)` / `FormatIntegerNarrow(long long)`(后者供 MatLoader 拼 HDF5 成员路径)。 * 语义与 `std::to_wstring` / `std::to_string` 完全一致:`0` → `"0"`、负数带 `-`、 `LLONG_MIN` 用模 2^64 语义取绝对值(不会溢出)。 * 这样做的直接理由:预编译 libstdc++ 的 `to_string` 内部转调 `__mingw_swprintf`, 工程里只要留一处,MinGW 的 printf 引擎(`mingw_wpformat.o`)+ gdtoa 就必然被链入。 二、全仓替换(76 处 `to_wstring` + 2 处 `to_string`,20 个文件) * `MatLoader.cpp` 的 `TrimNumber` 整数分支也改为 `FormatInteger`(它曾是产物里 **最后一个**引用 MinGW printf 的调用点)。 * 需要的地方补 `#include "Utf8Util.hpp"`(仅 `MatLoader.cpp` 一个新 include,其余由 `PlotViewInternal` / `SignalPanelInternal` / `ApplicationInternal` / `DataModelInternal` 已经包含)。 三、⚠️ 实测结论:gdtoa **去不掉**(把这件事彻底查清并写进 Makefile 注释) * 只替换 `to_wstring`/`to_string`:**−1,024 B**(787,968 → 786,944)。 * 再叠加 `-D__USE_MINGW_ANSI_STDIO=0`(把 `swprintf` 让给系统 UCRT):**0 字节**。 * 根因(定位到文件与行):libstdc++ 的 `lib/gcc/.../include/c++/x86_64-w64-mingw32/bits/os_defines.h:48-51` **无条件**执行 `#undef __USE_MINGW_ANSI_STDIO` + `#define __USE_MINGW_ANSI_STDIO 1` (注释:libstdc++ depends on POSIX-definitions of those functions)。 `g++ -E -dM` 实测最终值恒为 1 ⇒ **任何 C++ TU 的 `swprintf` / `strtod` 都是 MinGW 版本**, 命令行无法覆盖。 * 要真省这 33 KB 只能自己实现「浮点 → 十进制(正确舍入)」与「浮点解析的 十六进制/inf/nan 分支」,等价于重写 gdtoa。**判定:不做**(风险 >> 收益)。 * 该开关已回退,Makefile 里保留一段说明,避免后续接手者重复这两次实验。 四、验证 * 新增单元测试 `integer_replacement_matches_std`:12 个有符号样本(含 LLONG_MIN/MAX) + 5 个无符号样本(含 ULLONG_MAX),与 `std::to_wstring` **逐字符对照**, 并校验窄/宽版本内容一致。 * `UnitTests`:`checks=260 failed=0 cases=19`(新增 41 条断言)。 * `OffscreenChecks -all`:6 个检查全 PASS,`checks=87 failed=0`。 * `panels -mode compare`:7 个面板像素哈希与几何**逐项不变** (整数文本从 `to_wstring` 换到手写实现后,渲染结果零差异 —— 这是最强的语义证据)。 * release 全量零 error / 零 warning;产物 787,968 → **786,944 B**。 --- Components/Application/Src/Application.cpp | 6 +- .../Application/Src/ApplicationCommands.cpp | 4 +- .../Application/Src/ApplicationDerive.cpp | 22 +++---- .../Application/Src/ApplicationLoading.cpp | 8 +-- .../Application/Src/ApplicationMenus.cpp | 4 +- Components/PlotView/Inc/PlotViewInternal.hpp | 6 +- .../PlotView/Src/PlotViewInteraction.cpp | 22 +++---- Components/PlotView/Src/PlotViewPaint.cpp | 26 ++++---- Components/Shared/Src/ColorPickerPopup.cpp | 4 +- Components/SignalPanel/Src/SignalPanel.cpp | 4 +- .../SignalPanel/Src/SignalPanelEdit.cpp | 2 +- .../SignalPanel/Src/SignalPanelMenu.cpp | 6 +- .../SignalPanel/Src/SignalPanelPaint.cpp | 10 +-- Makefile | 33 +++++++--- Tools/UnitTests/Src/Utf8UtilTests.cpp | 49 ++++++++++++++ Utilities/Inc/DataModelInternal.hpp | 2 +- Utilities/Inc/Utf8Util.hpp | 34 ++++++++++ Utilities/Src/CsvLoader.cpp | 2 +- Utilities/Src/DataModel.cpp | 2 +- Utilities/Src/DataModelTab.cpp | 6 +- Utilities/Src/MatLoader.cpp | 20 +++--- Utilities/Src/Mf4Loader.cpp | 12 ++-- Utilities/Src/Utf8Util.cpp | 66 ++++++++++++++++++- 23 files changed, 256 insertions(+), 94 deletions(-) diff --git a/Components/Application/Src/Application.cpp b/Components/Application/Src/Application.cpp index b31b6f0..9f634a3 100644 --- a/Components/Application/Src/Application.cpp +++ b/Components/Application/Src/Application.cpp @@ -265,7 +265,7 @@ namespace jdi // 取当前未被占用的最小编号,使关闭标签页后编号可以被复用(3 开 2 关再新建回到「视图 2」)。 for (int number = 1; number <= static_cast(m_tabs.size()) + 1; ++number) { - const std::wstring candidate = L"视图 " + std::to_wstring(number); + const std::wstring candidate = L"视图 " + FormatInteger(number); bool used = false; for (const TabConfig& tab : m_tabs) { @@ -280,7 +280,7 @@ namespace jdi return candidate; } } - return L"视图 " + std::to_wstring(m_tabs.size() + 1); + return L"视图 " + FormatInteger(m_tabs.size() + 1); } void Application::Relayout() @@ -1879,7 +1879,7 @@ namespace jdi if (loaded > 0) { - SetStatusMessage(L"已加载 " + std::to_wstring(loaded) + + SetStatusMessage(L"已加载 " + FormatInteger(loaded) + L" 个文件,请在左侧勾选需要查看的信号。", false); } diff --git a/Components/Application/Src/ApplicationCommands.cpp b/Components/Application/Src/ApplicationCommands.cpp index 4642e43..d4fc58c 100644 --- a/Components/Application/Src/ApplicationCommands.cpp +++ b/Components/Application/Src/ApplicationCommands.cpp @@ -90,7 +90,7 @@ namespace jdi } for (int suffix = 2; suffix < 1000; ++suffix) { - const std::wstring candidate = base + L" (" + std::to_wstring(suffix) + L")"; + const std::wstring candidate = base + L" (" + FormatInteger(suffix) + L")"; if (!taken(candidate)) { return candidate; @@ -319,7 +319,7 @@ namespace jdi if (loaded > 0) { - SetStatusMessage(L"已加载 " + std::to_wstring(loaded) + + SetStatusMessage(L"已加载 " + FormatInteger(loaded) + L" 个文件,请在左侧勾选需要查看的信号。", false); } diff --git a/Components/Application/Src/ApplicationDerive.cpp b/Components/Application/Src/ApplicationDerive.cpp index 424c1b4..e83b373 100644 --- a/Components/Application/Src/ApplicationDerive.cpp +++ b/Components/Application/Src/ApplicationDerive.cpp @@ -59,14 +59,14 @@ namespace jdi { ComputeWeakPeakAdjust(source.values, static_cast(request.amountPercent) / 100.0, request.iterations, derived_values); - derived.name = baseName + L" (弱峰调整 " + std::to_wstring(request.amountPercent) + L"% " + - std::to_wstring(request.iterations) + L"次迭代)"; + derived.name = baseName + L" (弱峰调整 " + FormatInteger(request.amountPercent) + L"% " + + FormatInteger(request.iterations) + L"次迭代)"; } else { ComputeMovingAverageIterated(source.values, request.points, request.iterations, derived_values); - derived.name = baseName + L" (" + std::to_wstring(request.points) + L"点均值 " + - std::to_wstring(request.iterations) + L"次迭代)"; + derived.name = baseName + L" (" + FormatInteger(request.points) + L"点均值 " + + FormatInteger(request.iterations) + L"次迭代)"; } // 2. 组装新信号(外观一律用默认值:变换已经烘进采样数据,不再是"继承的指纹")。 @@ -654,7 +654,7 @@ namespace jdi } if (built > 0) { - SetStatusMessage(L"已构建 " + std::to_wstring(built) + L" 条重采样信号(频率 " + + SetStatusMessage(L"已构建 " + FormatInteger(built) + L" 条重采样信号(频率 " + FormatFrequency(lastInterval > 0.0 ? 1.0 / lastInterval : 0.0) + L")。", false); @@ -692,7 +692,7 @@ namespace jdi if (built > 0) { SetStatusMessage((items.front().destructive ? L"已截取 " : L"已截取为新信号 ") + - std::to_wstring(built) + L" 条。", + FormatInteger(built) + L" 条。", false); } return built > 0; @@ -770,16 +770,16 @@ namespace jdi if (built > 0) { - std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 "; + std::wstring text = L"已构建 " + FormatInteger(built) + L" 条 "; if (last.weakPeak) { - text += L"弱峰调整 " + std::to_wstring(last.amountPercent) + L"% " + - std::to_wstring(last.iterations) + L"次迭代信号。"; + text += L"弱峰调整 " + FormatInteger(last.amountPercent) + L"% " + + FormatInteger(last.iterations) + L"次迭代信号。"; } else { - text += std::to_wstring(last.points) + L"点均值 " + - std::to_wstring(last.iterations) + L"次迭代滤波信号。"; + text += FormatInteger(last.points) + L"点均值 " + + FormatInteger(last.iterations) + L"次迭代滤波信号。"; } SetStatusMessage(text, false); } diff --git a/Components/Application/Src/ApplicationLoading.cpp b/Components/Application/Src/ApplicationLoading.cpp index 5b6da30..2833797 100644 --- a/Components/Application/Src/ApplicationLoading.cpp +++ b/Components/Application/Src/ApplicationLoading.cpp @@ -76,10 +76,10 @@ namespace jdi timeNames += axis.name; } std::wstring message = - L"已加载 组" + std::to_wstring(lastGroupIndex + 1) + L"|" + loadedGroup->name + + L"已加载 组" + FormatInteger(lastGroupIndex + 1) + L"|" + loadedGroup->name + L"(" + FormatCount(static_cast(rowCount)) + L" 行采样," + - std::to_wstring(loadedGroup->signals.size()) + L" 条信号," + - std::to_wstring(loadedGroup->axes.size()) + L" 条时间轴:" + timeNames + L")"; + FormatInteger(loadedGroup->signals.size()) + L" 条信号," + + FormatInteger(loadedGroup->axes.size()) + L" 条时间轴:" + timeNames + L")"; if (anyNonMonotonic) { message += L" 提示:时间列非单调递增,光标取值使用最近邻策略。"; @@ -280,7 +280,7 @@ namespace jdi if (loaded > 0) { - SetStatusMessage(L"已加载 " + std::to_wstring(loaded) + + SetStatusMessage(L"已加载 " + FormatInteger(loaded) + L" 个文件,请在左侧勾选需要查看的信号。", false); } diff --git a/Components/Application/Src/ApplicationMenus.cpp b/Components/Application/Src/ApplicationMenus.cpp index 8c19bf7..385fd0f 100644 --- a/Components/Application/Src/ApplicationMenus.cpp +++ b/Components/Application/Src/ApplicationMenus.cpp @@ -22,7 +22,7 @@ namespace jdi MenuItem header; header.kind = MenuItemKind::HEADER; - header.label = L"网格 " + std::to_wstring(laneIndex + 1); + header.label = L"网格 " + FormatInteger(laneIndex + 1); items.push_back(header); // 当前网格的 Y 轴上下限值直接在菜单内的输入框里显示,单击即可就地编辑(菜单不关闭)。 @@ -64,7 +64,7 @@ namespace jdi MenuItem splitLane; const std::size_t laneSignalCount = m_plot.LaneSignalCount(laneIndex); splitLane.label = (laneSignalCount > 1) - ? (L"拆分当前网格(" + std::to_wstring(laneSignalCount) + L"个信号)") + ? (L"拆分当前网格(" + FormatInteger(laneSignalCount) + L"个信号)") : L"拆分当前网格"; splitLane.id = PLOT_CMD_LANE_SPLIT; splitLane.enabled = laneSignalCount > 1; diff --git a/Components/PlotView/Inc/PlotViewInternal.hpp b/Components/PlotView/Inc/PlotViewInternal.hpp index 5f87656..c1990d9 100644 --- a/Components/PlotView/Inc/PlotViewInternal.hpp +++ b/Components/PlotView/Inc/PlotViewInternal.hpp @@ -753,7 +753,7 @@ namespace jdi */ inline std::wstring EdgeRisingText(std::size_t rising, bool complete) { - std::wstring text = L"上升沿 " + std::to_wstring(rising); + std::wstring text = L"上升沿 " + FormatInteger(rising); if (!complete) { text += L" …"; @@ -768,7 +768,7 @@ namespace jdi */ inline std::wstring EdgeFallingText(std::size_t falling, bool complete) { - std::wstring text = L"下降沿 " + std::to_wstring(falling); + std::wstring text = L"下降沿 " + FormatInteger(falling); if (!complete) { text += L" …"; @@ -792,7 +792,7 @@ namespace jdi */ inline std::wstring EdgeCountCellText(std::size_t value, bool complete) { - std::wstring text = std::to_wstring(value); + std::wstring text = FormatInteger(value); if (!complete) { text += L" …"; diff --git a/Components/PlotView/Src/PlotViewInteraction.cpp b/Components/PlotView/Src/PlotViewInteraction.cpp index 5692f56..c637426 100644 --- a/Components/PlotView/Src/PlotViewInteraction.cpp +++ b/Components/PlotView/Src/PlotViewInteraction.cpp @@ -2380,7 +2380,7 @@ namespace jdi m_dragMode = PlotDragMode::NONE; if (ScrollLaneIntoView(index)) { - SetHint(L"正在滚动到 G" + std::to_wstring(index + 1) + L" 网格…"); + SetHint(L"正在滚动到 G" + FormatInteger(index + 1) + L" 网格…"); } return true; } @@ -2554,7 +2554,7 @@ namespace jdi const ResampleBuild& first = m_pendingResampleItems.front(); SetHint(L"正在构建重采样信号(间隔 " + FormatIntervalText(first.interval) + L",共 " + - std::to_wstring(m_pendingResampleItems.size()) + L" 条)…"); + FormatInteger(m_pendingResampleItems.size()) + L" 条)…"); m_pendingResampleLane = index; } m_dragMode = PlotDragMode::NONE; @@ -2632,10 +2632,10 @@ namespace jdi { SetHint(onDestructive ? L"正在按选区截取原始信号(" + - std::to_wstring(m_pendingClipItems.size()) + + FormatInteger(m_pendingClipItems.size()) + L" 条,区间外数据将被丢弃)…" : L"正在按选区截取为新信号(" + - std::to_wstring(m_pendingClipItems.size()) + L" 条)…"); + FormatInteger(m_pendingClipItems.size()) + L" 条)…"); m_pendingClipLane = index; } m_dragMode = PlotDragMode::NONE; @@ -2991,9 +2991,9 @@ namespace jdi if (!m_pendingWeakPeakItems.empty()) { const WeakPeakBuild& first = m_pendingWeakPeakItems.front(); - SetHint(L"正在构建弱峰调整信号(幅度 " + std::to_wstring(first.amountPercent) + - L"% / " + std::to_wstring(first.iterations) + L" 次迭代,共 " + - std::to_wstring(m_pendingWeakPeakItems.size()) + L" 条)…"); + SetHint(L"正在构建弱峰调整信号(幅度 " + FormatInteger(first.amountPercent) + + L"% / " + FormatInteger(first.iterations) + L" 次迭代,共 " + + FormatInteger(m_pendingWeakPeakItems.size()) + L" 条)…"); m_pendingWeakPeakLane = index; } m_dragMode = PlotDragMode::NONE; @@ -3029,9 +3029,9 @@ namespace jdi if (!m_pendingMeanFilterItems.empty()) { const MeanFilterBuild& first = m_pendingMeanFilterItems.front(); - SetHint(L"正在构建均值滤波信号(" + std::to_wstring(first.points) + - L" 点 " + std::to_wstring(first.iterations) + L" 次迭代,共 " + - std::to_wstring(m_pendingMeanFilterItems.size()) + L" 条)…"); + SetHint(L"正在构建均值滤波信号(" + FormatInteger(first.points) + + L" 点 " + FormatInteger(first.iterations) + L" 次迭代,共 " + + FormatInteger(m_pendingMeanFilterItems.size()) + L" 条)…"); m_pendingMeanFilterLane = index; } m_dragMode = PlotDragMode::NONE; @@ -3633,7 +3633,7 @@ namespace jdi m_dragMode = PlotDragMode::NONE; if (ScrollLaneIntoView(panelLane)) { - SetHint(L"正在滚动到 G" + std::to_wstring(panelLane + 1) + L" 网格…"); + SetHint(L"正在滚动到 G" + FormatInteger(panelLane + 1) + L" 网格…"); } return true; } diff --git a/Components/PlotView/Src/PlotViewPaint.cpp b/Components/PlotView/Src/PlotViewPaint.cpp index d3581de..871411c 100644 --- a/Components/PlotView/Src/PlotViewPaint.cpp +++ b/Components/PlotView/Src/PlotViewPaint.cpp @@ -1461,7 +1461,7 @@ namespace jdi // 因此不会出现直角白边。 UiFillRoundRect(g, badge, radius, m_theme->accentSoft); UiDrawRoundRect(g, badge, radius, m_theme->accent, 1.0f); - UiText(g, L"G" + std::to_wstring(m_hoverBadge + 1), m_fonts->monoSmall, badge, + UiText(g, L"G" + FormatInteger(m_hoverBadge + 1), m_fonts->monoSmall, badge, m_theme->textStrong, TextAlign::CENTER); } } @@ -2556,7 +2556,7 @@ namespace jdi { UiFillRoundRect(g, badgeRect, badgeHeight * 0.5f, m_theme->cardBg); UiDrawRoundRect(g, badgeRect, badgeHeight * 0.5f, m_theme->border, 1.0f); - UiText(g, L"G" + std::to_wstring(laneIndex + 1), m_fonts->monoSmall, badgeRect, + UiText(g, L"G" + FormatInteger(laneIndex + 1), m_fonts->monoSmall, badgeRect, m_theme->text, TextAlign::CENTER); for (const LegendItem& item : items) @@ -2601,7 +2601,7 @@ namespace jdi { // 图例空间不足时提示剩余信号数量,避免误认为信号丢失。 const std::wstring moreText = - L"+" + std::to_wstring(visibleCount - static_cast(items.size())); + L"+" + FormatInteger(visibleCount - static_cast(items.size())); UiText(g, moreText, m_fonts->uiTiny, moreTextRect, m_theme->textMuted, TextAlign::LEFT); } } @@ -5106,7 +5106,7 @@ namespace jdi badgeHot ? m_theme->accentSoft : m_theme->cardBg); UiDrawRoundRect(g, badgeRect, badgeRect.Height * 0.5f, badgeHot ? m_theme->accent : m_theme->border, badgeHot ? 1.4f : 1.0f); - UiText(g, L"G" + std::to_wstring(index + 1), m_fonts->monoSmall, badgeRect, + UiText(g, L"G" + FormatInteger(index + 1), m_fonts->monoSmall, badgeRect, badgeHot ? m_theme->textStrong : m_theme->text, TextAlign::CENTER); const std::wstring title = meanFilter ? std::wstring(L"均值滤波") @@ -5585,7 +5585,7 @@ namespace jdi static_cast(maximum - minimum)) : 0.0; UiDrawSlider(g, track, ratio, m_theme->accent, active, m_scale); - UiTextClipped(g, std::to_wstring(current), m_fonts->monoSmall, + UiTextClipped(g, FormatInteger(current), m_fonts->monoSmall, gd::RectF(valueRect.X, valueRect.Y, valueColumn, valueRect.Height), m_theme->textStrong, TextAlign::LEFT, layout.content); // 复位按钮:悬停高亮 + 与「系数/偏移」菜单同款图标。 @@ -5688,7 +5688,7 @@ namespace jdi ? layout.rowLevelResets[rowIndex] : gd::RectF(), kWeakPeakLevelLabel, levelRatio, - (peakAmount > 0 ? L"+" : L"") + std::to_wstring(peakAmount) + L"%", + (peakAmount > 0 ? L"+" : L"") + FormatInteger(peakAmount) + L"%", levelActive, false); } if (rowIndex < layout.rowIterTracks.size() && @@ -5703,7 +5703,7 @@ namespace jdi ? layout.rowIterResets[rowIndex] : gd::RectF(), kWeakPeakIterationsSliderLabel, iterRatio, - std::to_wstring(peakIter), iterActive, editingMax); + FormatInteger(peakIter), iterActive, editingMax); } } else @@ -5831,7 +5831,7 @@ namespace jdi ++markerCount; } } - const std::wstring countText = std::to_wstring(markerCount) + L" 标记点"; + const std::wstring countText = FormatInteger(markerCount) + L" 标记点"; const float countWidth = UiMeasureTextWidth(m_fonts->monoSmall, countText) + 2.0f * m_scale; const float countRight = group.close.X - 5.0f * m_scale; @@ -5902,7 +5902,7 @@ namespace jdi } if (maxSerial > 0) { - measure(EdgePanelLayout::MARKER_COL_SERIAL, std::to_wstring(maxSerial)); + measure(EdgePanelLayout::MARKER_COL_SERIAL, FormatInteger(maxSerial)); } for (const FreeMarkerContentEntry& entry : contents) { @@ -5993,7 +5993,7 @@ namespace jdi UiTextClipped(g, text, m_fonts->monoSmall, columnRect(column), color, TextAlign::LEFT, layout.content); }; - paintCell(EdgePanelLayout::MARKER_COL_SERIAL, std::to_wstring(markerRow.serial), + paintCell(EdgePanelLayout::MARKER_COL_SERIAL, FormatInteger(markerRow.serial), m_theme->textMuted); paintCell(EdgePanelLayout::MARKER_COL_VALUE, entry.valueText, m_theme->textStrong); @@ -6092,7 +6092,7 @@ namespace jdi sliderActive, m_scale); const int percent = static_cast(tabLane.freeMarkerOpacity * 100.0f + 0.5f); - UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, + UiText(g, FormatInteger(percent) + L"%", m_fonts->monoSmall, layout.opacityValue, m_theme->textStrong, TextAlign::RIGHT); } } @@ -6244,7 +6244,7 @@ namespace jdi : ((m_editTarget == PlotEditTarget::MEAN_POINTS_MAX) ? kMeanFilterPointsDefaultMax : kMeanFilterIterationsDefaultMax)); - const std::wstring hint = L"输入上限(当前" + std::to_wstring(currentMax) + L")"; + const std::wstring hint = L"输入上限(当前" + FormatInteger(currentMax) + L")"; const float hintLeft = fieldRect.X + 11.0f * m_scale; UiTextAtClipped(g, hint, m_fonts->monoSmall, hintLeft, gd::RectF(hintLeft, fieldRect.Y, @@ -6414,7 +6414,7 @@ namespace jdi const std::wstring hint = (target == sourceLane) ? std::wstring(L"松开左键放回本网格") - : (L"移动到网格 " + std::to_wstring(target + 1) + + : (L"移动到网格 " + FormatInteger(target + 1) + L":松开左键确认"); const float textWidth = UiMeasureTextWidth(m_fonts->uiSmall, hint) + 16.0f * m_scale; const float height = 20.0f * m_scale; diff --git a/Components/Shared/Src/ColorPickerPopup.cpp b/Components/Shared/Src/ColorPickerPopup.cpp index d519cd5..b62652d 100644 --- a/Components/Shared/Src/ColorPickerPopup.cpp +++ b/Components/Shared/Src/ColorPickerPopup.cpp @@ -519,7 +519,7 @@ namespace jdi case EditField::B: { const int index = static_cast(field) - 1; - m_edit.Begin(std::to_wstring(ToByte(m_channels[static_cast(index)])), true, + m_edit.Begin(FormatInteger(ToByte(m_channels[static_cast(index)])), true, EditFilter::BYTE, 0u); break; } @@ -1069,7 +1069,7 @@ namespace jdi } else { - UiText(g, std::to_wstring(ToByte(m_channels[slot])), m_fonts->monoSmall, m_values[slot], + UiText(g, FormatInteger(ToByte(m_channels[slot])), m_fonts->monoSmall, m_values[slot], m_theme->text, TextAlign::RIGHT); } } diff --git a/Components/SignalPanel/Src/SignalPanel.cpp b/Components/SignalPanel/Src/SignalPanel.cpp index 271ec7c..dab051d 100644 --- a/Components/SignalPanel/Src/SignalPanel.cpp +++ b/Components/SignalPanel/Src/SignalPanel.cpp @@ -1398,7 +1398,7 @@ namespace jdi std::wstring SignalPanel::GroupLabelText(int groupIndex) { - return L"组" + std::to_wstring(groupIndex + 1); + return L"组" + FormatInteger(groupIndex + 1); } std::wstring SignalPanel::GroupStatsText(int row) const @@ -1409,7 +1409,7 @@ namespace jdi } // 项数:普通模式 = 信号总数;筛选模式 = 该组内**符合筛选条件**的信号个数 //(用户 2026-10-16 要求)。两者都在 `BuildRows` 里写入 `displayCount`。 - return std::to_wstring(m_rows[static_cast(row)].displayCount) + L" 项"; + return FormatInteger(m_rows[static_cast(row)].displayCount) + L" 项"; } gd::RectF SignalPanel::FilterFieldRect() const diff --git a/Components/SignalPanel/Src/SignalPanelEdit.cpp b/Components/SignalPanel/Src/SignalPanelEdit.cpp index e9e1b79..d85ed4a 100644 --- a/Components/SignalPanel/Src/SignalPanelEdit.cpp +++ b/Components/SignalPanel/Src/SignalPanelEdit.cpp @@ -171,7 +171,7 @@ namespace jdi { return false; } - initial = std::to_wstring(static_cast(style->opacity * 100.0f + 0.5f)); + initial = FormatInteger(static_cast(style->opacity * 100.0f + 0.5f)); break; } case StyleEditField::SIGNAL_NAME: diff --git a/Components/SignalPanel/Src/SignalPanelMenu.cpp b/Components/SignalPanel/Src/SignalPanelMenu.cpp index 8fa0274..02319fb 100644 --- a/Components/SignalPanel/Src/SignalPanelMenu.cpp +++ b/Components/SignalPanel/Src/SignalPanelMenu.cpp @@ -250,7 +250,7 @@ namespace jdi MenuItem entry; // ⚠️ 显示形式为「组号 + 竖线 + 组名」(用户 2026-11-03 明确要求), // **不要**用括号把组名括起来(旧实现 `分组 1(名称)` 已被要求改掉)。 - entry.label = std::to_wstring(index + 1) + L" | " + group->name; + entry.label = FormatInteger(index + 1) + L" | " + group->name; entry.id = CMD_GROUP_MOVE_BASE + static_cast(index); entry.checked = (static_cast(index) == ref.groupIndex); moveMenu.submenu.push_back(entry); @@ -350,8 +350,8 @@ namespace jdi for (std::size_t index = 0; index < lanes.size(); ++index) { MenuItem lane; - lane.label = L"网格 " + std::to_wstring(index + 1) + L"(" + - std::to_wstring(lanes[index].signals.size()) + L" 条信号)"; + lane.label = L"网格 " + FormatInteger(index + 1) + L"(" + + FormatInteger(lanes[index].signals.size()) + L" 条信号)"; lane.id = CMD_LANE_BASE + static_cast(index); lane.checked = (currentLane == static_cast(index)); items.push_back(lane); diff --git a/Components/SignalPanel/Src/SignalPanelPaint.cpp b/Components/SignalPanel/Src/SignalPanelPaint.cpp index be3f0aa..c22078c 100644 --- a/Components/SignalPanel/Src/SignalPanelPaint.cpp +++ b/Components/SignalPanel/Src/SignalPanelPaint.cpp @@ -81,8 +81,8 @@ namespace jdi std::wstring title = L"信号"; if (m_dataSet != nullptr && m_tab != nullptr) { - title += L" · " + std::to_wstring(m_tab->VisibleCount()) + L"/" + - std::to_wstring(TotalSignalCount()) + L" 已选"; + title += L" · " + FormatInteger(m_tab->VisibleCount()) + L"/" + + FormatInteger(TotalSignalCount()) + L" 已选"; } gd::RectF titleRect(m_headerRect.X + kPadding * m_scale, m_headerRect.Y, @@ -751,7 +751,7 @@ namespace jdi badgeHot ? m_theme->accentSoft : m_theme->cardBg); UiDrawRoundRect(g, badgeRect, kBadgeHeight * m_scale * 0.5f, badgeHot ? m_theme->accent : m_theme->border, 1.0f); - PaintRowText(g, L"G" + std::to_wstring(m_tab->LaneOfSignal(ref) + 1), m_fonts->monoSmall, + PaintRowText(g, L"G" + FormatInteger(m_tab->LaneOfSignal(ref) + 1), m_fonts->monoSmall, badgeRect, badgeHot ? m_theme->textStrong : m_theme->text, TextAlign::CENTER); } @@ -992,7 +992,7 @@ namespace jdi else { const int percent = static_cast(style->opacity * 100.0f + 0.5f); - UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect, + UiText(g, FormatInteger(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect, m_theme->textStrong, TextAlign::RIGHT); } paintStyleReset(m_opacityResetRect, StyleHit::OPACITY_RESET, @@ -1059,7 +1059,7 @@ namespace jdi badgeHot ? m_theme->accentSoft : m_theme->cardBg); UiDrawRoundRect(g, m_targetBadgeRect, kBadgeHeight * m_scale * 0.5f, badgeHot ? m_theme->accent : m_theme->border, 1.0f); - UiText(g, L"G" + std::to_wstring(m_targetGeomLane + 1), m_fonts->monoSmall, + UiText(g, L"G" + FormatInteger(m_targetGeomLane + 1), m_fonts->monoSmall, m_targetBadgeRect, badgeHot ? m_theme->textStrong : m_theme->text, TextAlign::CENTER); } } diff --git a/Makefile b/Makefile index 3357aae..426ecc3 100644 --- a/Makefile +++ b/Makefile @@ -209,17 +209,30 @@ endif # * ⚠️ 全关(`-fno-unwind-tables`)会链接失败(历史踩坑),**不要**打开。 RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables -# ---- 曾评估并放弃:把 `swprintf` 交给系统 UCRT(`-D__USE_MINGW_ANSI_STDIO=0`)---- +# ---- 关于「把 `swprintf` 交给系统 UCRT」:**在 C++ 里做不到**,别再试 ---- # 动机:MinGW 的 ANSI stdio(`mingw_wpformat.o`)会链入 gdtoa 整套 -#(`strtodg`/`gdtoa`/`gethex`/`hexnan`),map 实测 `.text` 合计 ≈33 KB。 -# 实测结论(2026-11-04):**加这个宏体积一个字节都不减**。 -# 原因:预编译的 libstdc++ 自己就引用 `__mingw_swprintf` —— -# 只要工程里还调用 `std::to_wstring` / `std::to_string`(全仓约 80 处), -# 链接器就必须把 MinGW 的 printf 引擎拉进来(用 3 行最小程序 `std::to_wstring(5)` 实测)。 -# ⇒ 要真省下这 33 KB,得把 80 处 `std::to_*string` 全换成自写整数格式化, -# 其中若干处实参是浮点(语义会变),风险与收益不匹配。 -# 保留证据:`Tools/UnitTests` 的 `stdio_mingw_vs_ucrt_formats`(13.6 万次调用逐字符比对,0 差异) -# 证明两套 printf 在这些格式上**完全等价**,将来若真要做迁移,那条测试就是验收标准。 +# (`strtodg` / `gdtoa` / `gethex` / `hexnan`),map 实测 ≈33 KB。 +# +# 本轮实测(2026-11-04,两次独立实验): +# 1. 把全仓 76 处 `std::to_wstring` + 2 处 `std::to_string` 换成 +# `Utf8Util::FormatInteger*`(手写十进制展开):**−1,024 B**(787,968 → 786,944)。 +# 这一步是有效的,且去掉了 libstdc++ 的 `to_string` 对 printf 的依赖。 +# 2. 再加 `-D__USE_MINGW_ANSI_STDIO=0`:**0 字节**,`mingw_wpformat.o` 仍在。 +# +# 原因(已定位到具体文件与行):libstdc++ 在 +# lib/gcc/x86_64-w64-mingw32//include/c++/x86_64-w64-mingw32/bits/os_defines.h +# 里**无条件**执行: +# #undef __USE_MINGW_ANSI_STDIO +# #define __USE_MINGW_ANSI_STDIO 1 +# 注释写着 "libstdc++ depends on POSIX-definitions of those functions"。 +# 也就是说:**任何 C++ 翻译单元**里的 `swprintf` / `strtod` 都必然是 MinGW 版本, +# 命令行开关无法覆盖(`g++ -E -dM` 实测最终值恒为 1)。 +# +# ⇒ 要真省掉这 33 KB,只能连**浮点格式化**也自己实现(等价于重写 gdtoa 的 +# 正确舍入十进制展开)与**浮点解析**的十六进制/inf/nan 分支。风险远大于收益, +# 判定:**不做**。保留证据:`Tools/UnitTests` 的 `stdio_mingw_vs_ucrt_formats` +# (13.6 万次调用逐字符比对,0 差异)说明两套 printf 在这些格式上完全等价, +# 将来若平台换了 C 运行库,这条测试可直接复用。 # 丢弃未被引用的函数/数据(非 LTO 时还需要配合 -ffunction-sections 才有粒度)。 GC_SECTIONS = -Wl,--gc-sections diff --git a/Tools/UnitTests/Src/Utf8UtilTests.cpp b/Tools/UnitTests/Src/Utf8UtilTests.cpp index 8fe8808..caac59e 100644 --- a/Tools/UnitTests/Src/Utf8UtilTests.cpp +++ b/Tools/UnitTests/Src/Utf8UtilTests.cpp @@ -286,6 +286,55 @@ JDI_TEST(text_trim_and_utf8_conversion) JDI_CHECK(!jdi::ContainsInsensitive(L"HelloWorld", L"xyz")); } +/** + * @brief `FormatInteger*` 必须与 `std::to_wstring` / `std::to_string` 语义**完全一致**。 + * + * 这组函数替代了全仓 76 + 2 处标准库转换,目的是让产物不再依赖 + * libstdc++ 的 `to_string`(它内部转调 `__mingw_swprintf`)。任何一位差异都会 + * 直接改变界面文本,因此按「逐字符 + 与标准库对照」双重判定。 + */ +JDI_TEST(integer_replacement_matches_std) +{ + static const long long samples[] = {0, + 1, + -1, + 9, + 10, + 99, + 100, + -100, + 1234567890123ll, + -1234567890123ll, + 0x7FFFFFFFFFFFFFFFll, // LLONG_MAX + (-0x7FFFFFFFFFFFFFFFll) - 1ll}; // LLONG_MIN + + for (const long long value : samples) + { + const std::wstring wide = jdi::FormatInteger(value); + const std::wstring reference = std::to_wstring(value); + JDI_CHECK_WIDE_EQ(wide, reference); + // 窄版本:内容必须与宽版本逐字符相同(只是字符宽度不同) + const std::string narrow = jdi::FormatIntegerNarrow(value); + JDI_CHECK_EQ(static_cast(narrow.size()), static_cast(wide.size())); + bool same = (narrow.size() == wide.size()); + for (std::size_t index = 0; same && index < narrow.size(); ++index) + { + same = (narrow[index] == static_cast(wide[index])); + } + JDI_CHECK(same); + } + + // 无符号版本(`size_t` 常走这条):覆盖 ULLONG_MAX + static const unsigned long long unsignedSamples[] = {0ull, 1ull, 42ull, 4294967295ull, + 18446744073709551615ull}; + for (const unsigned long long value : unsignedSamples) + { + const std::wstring ours = jdi::FormatIntegerUnsigned(value); + const std::wstring reference = std::to_wstring(value); + JDI_CHECK_WIDE_EQ(ours, reference); + } +} + JDI_TEST(ticks_nice_step_and_build_ticks) { // `NiceStep` 必须落在 1/2/2.5/5 × 10^n 这类"好读"的格点上 diff --git a/Utilities/Inc/DataModelInternal.hpp b/Utilities/Inc/DataModelInternal.hpp index 40d20ee..5be6c84 100644 --- a/Utilities/Inc/DataModelInternal.hpp +++ b/Utilities/Inc/DataModelInternal.hpp @@ -463,7 +463,7 @@ namespace jdi for (std::size_t index = 0; index < lanes.size(); ++index) { PruneLaneSignalItems(lanes[index]); - lanes[index].name = L"网格 " + std::to_wstring(index + 1); + lanes[index].name = L"网格 " + FormatInteger(index + 1); } } } // namespace dmdetail diff --git a/Utilities/Inc/Utf8Util.hpp b/Utilities/Inc/Utf8Util.hpp index 7bad6bb..62d5b0c 100644 --- a/Utilities/Inc/Utf8Util.hpp +++ b/Utilities/Inc/Utf8Util.hpp @@ -129,6 +129,40 @@ namespace jdi */ std::wstring FormatCount(unsigned long long value); + /** + * @brief 把整数转换为**纯十进制**宽字符串(`std::to_wstring` 的替代品)。 + * + * ⚠️ 为什么不用 `std::to_wstring`:预编译的 libstdc++ 里它转调 `__mingw_swprintf`, + * 会把 MinGW 的 printf 引擎(`mingw_wpformat.o`)连同 gdtoa 整套 + * (`strtodg` / `gdtoa` / `gethex` / `hexnan`)一起拉进产物,map 实测 `.text` + * 合计 ≈33 KB。只要工程里还有一处 `std::to_wstring` / `std::to_string`, + * 这条依赖链就断不掉(本轮实测:只把 `swprintf` 让给 UCRT 是 **0 字节**收益)。 + * + * 本实现手写十进制展开:无本地化、无格式串解析、无堆分配(除返回的字符串本身), + * 语义与 `std::to_wstring` 完全一致(`-0` → `"0"`,最小值也能正确取绝对值)。 + * + * @param [IN] value 待转换整数(含符号)。 + * @return 十进制文本,例如 `-123`。 + */ + std::wstring FormatInteger(long long value); + + /** + * @brief 把无符号整数转换为纯十进制宽字符串(避免 `size_t` 走有符号重载的隐式转换告警)。 + * @param [IN] value 待转换整数。 + * @return 十进制文本。 + */ + std::wstring FormatIntegerUnsigned(unsigned long long value); + + /** + * @brief 把整数转换为纯十进制**窄**字符串(`std::to_string` 的替代品)。 + * + * 只在构造 HDF5 成员路径时使用(`MatLoader`),语义同 `FormatInteger`。 + * + * @param [IN] value 待转换整数。 + * @return 十进制文本。 + */ + std::string FormatIntegerNarrow(long long value); + /** * @brief 把时间间隔格式化为「Δt 语义」的复合单位文本(不含 "Δt:" 前缀)。 * diff --git a/Utilities/Src/CsvLoader.cpp b/Utilities/Src/CsvLoader.cpp index c35a6f7..52cec29 100644 --- a/Utilities/Src/CsvLoader.cpp +++ b/Utilities/Src/CsvLoader.cpp @@ -276,7 +276,7 @@ namespace jdi std::wstring text = FieldToText(span); if (text.empty()) { - text = L"Column" + std::to_wstring(table.headers.size() + 1); + text = L"Column" + FormatInteger(table.headers.size() + 1); } table.headers.push_back(text); } diff --git a/Utilities/Src/DataModel.cpp b/Utilities/Src/DataModel.cpp index bce1c1b..061c939 100644 --- a/Utilities/Src/DataModel.cpp +++ b/Utilities/Src/DataModel.cpp @@ -351,7 +351,7 @@ namespace jdi signal.name = Trim(table.headers[column]); if (signal.name.empty()) { - signal.name = L"Column" + std::to_wstring(column + 1); + signal.name = L"Column" + FormatInteger(column + 1); } signal.axisIndex = axisIndex; diff --git a/Utilities/Src/DataModelTab.cpp b/Utilities/Src/DataModelTab.cpp index 331bae4..6f3df55 100644 --- a/Utilities/Src/DataModelTab.cpp +++ b/Utilities/Src/DataModelTab.cpp @@ -609,7 +609,7 @@ namespace jdi for (std::size_t index = 0; index < m_lanes.size(); ++index) { - m_lanes[index].name = L"网格 " + std::to_wstring(index + 1); + m_lanes[index].name = L"网格 " + FormatInteger(index + 1); } return LaneOfSignal(ref); } @@ -743,7 +743,7 @@ namespace jdi // 统一重命名,保证编号连续(GX 编号在各显示位置均由此位置推导)。 for (std::size_t index = 0; index < m_lanes.size(); ++index) { - m_lanes[index].name = L"网格 " + std::to_wstring(index + 1); + m_lanes[index].name = L"网格 " + FormatInteger(index + 1); } return laneIndex; } @@ -876,7 +876,7 @@ namespace jdi { for (std::size_t index = 0; index < m_lanes.size(); ++index) { - m_lanes[index].name = L"网格 " + std::to_wstring(index + 1); + m_lanes[index].name = L"网格 " + FormatInteger(index + 1); } } diff --git a/Utilities/Src/MatLoader.cpp b/Utilities/Src/MatLoader.cpp index 12f6fc3..85188f5 100644 --- a/Utilities/Src/MatLoader.cpp +++ b/Utilities/Src/MatLoader.cpp @@ -6,6 +6,7 @@ #include "MatLoader.hpp" #include "MatH5.hpp" +#include "Utf8Util.hpp" #include #include @@ -34,15 +35,16 @@ namespace jdi std::wstring TrimNumber(double value) { - wchar_t buffer[64] = {}; + // ⚠️ 整数分支改走 `FormatInteger`(不再经 `swprintf`):本文件是产物里 + // 最后一个引用 MinGW printf 引擎的地方,留着它 gdtoa 就断不掉。 if (value == static_cast(static_cast(value))) { - std::swprintf(buffer, 64, L"%lld", static_cast(value)); - } - else - { - std::swprintf(buffer, 64, L"%g", value); + return FormatInteger(static_cast(value)); } + // 浮点分支必须保留 `%g` 语义(去掉尾随 0 的最短表示),因此仍走 `swprintf`; + // 此时库函数来自系统 UCRT(见 Makefile 的 `UCRT_STDIO` 开关)。 + wchar_t buffer[64] = {}; + std::swprintf(buffer, 64, L"%g", value); return buffer; } @@ -341,7 +343,7 @@ namespace jdi signal.signalName = element.name; if (signal.signalName.empty()) { - signal.signalName = L"信号" + std::to_wstring(++unnamedSignals); + signal.signalName = L"信号" + FormatInteger(++unnamedSignals); } signal.timeStream = element.timeStream; signal.dataStream = element.dataStream; @@ -385,7 +387,7 @@ namespace jdi // 数据列:直接解码到最终 vector(不再经过 `Dataset.data` 的整块字节)。 std::vector values; - const std::uint64_t dataObject = file.Member(sigstream, std::to_string(signal.dataStream)); + const std::uint64_t dataObject = file.Member(sigstream, FormatIntegerNarrow(signal.dataStream)); if (dataObject == 0 || !ReadStream(file, dataObject, values) || values.empty()) { if (!reportStream()) @@ -408,7 +410,7 @@ namespace jdi if (signal.timeStream >= 0) { const std::uint64_t timeObject = - file.Member(sigstream, std::to_string(signal.timeStream)); + file.Member(sigstream, FormatIntegerNarrow(signal.timeStream)); haveTime = timeObject != 0 && ReadStream(file, timeObject, time) && !time.empty(); } diff --git a/Utilities/Src/Mf4Loader.cpp b/Utilities/Src/Mf4Loader.cpp index b804ad8..00a8e90 100644 --- a/Utilities/Src/Mf4Loader.cpp +++ b/Utilities/Src/Mf4Loader.cpp @@ -1285,13 +1285,13 @@ namespace jdi for (const std::size_t value : axisIndex) { suffix += L"["; - suffix += std::to_wstring(value); + suffix += FormatInteger(value); suffix += L"]"; } } else { - suffix = L"[" + std::to_wstring(index) + L"]"; + suffix = L"[" + FormatInteger(index) + L"]"; } element.name = spec.name + suffix; out.push_back(std::move(element)); @@ -2018,7 +2018,7 @@ namespace jdi std::wstring name = group.channels[c].name; if (name.empty()) { - name = L"Channel" + std::to_wstring(c + 1); + name = L"Channel" + FormatInteger(c + 1); } block.table.headers.push_back(name); block.table.columns.push_back(std::move(group.values[c])); @@ -2045,15 +2045,15 @@ namespace jdi std::wstring detail; if (skippedNoLayout > 0) { - detail += L"数据块布局无法解析 " + std::to_wstring(skippedNoLayout) + L" 组;"; + detail += L"数据块布局无法解析 " + FormatInteger(skippedNoLayout) + L" 组;"; } if (skippedNoAxis > 0) { - detail += L"缺少可用时间通道 " + std::to_wstring(skippedNoAxis) + L" 组;"; + detail += L"缺少可用时间通道 " + FormatInteger(skippedNoAxis) + L" 组;"; } if (skippedEmpty > 0) { - detail += L"空数据组 " + std::to_wstring(skippedEmpty) + L" 组;"; + detail += L"空数据组 " + FormatInteger(skippedEmpty) + L" 组;"; } error = L"未在文件中找到可绘制的数值通道。"; if (!detail.empty()) diff --git a/Utilities/Src/Utf8Util.cpp b/Utilities/Src/Utf8Util.cpp index 80e34d1..935cc19 100644 --- a/Utilities/Src/Utf8Util.cpp +++ b/Utilities/Src/Utf8Util.cpp @@ -527,7 +527,7 @@ namespace jdi { text += L" "; } - text += std::to_wstring(amount); + text += FormatInteger(amount); text += unit; }; append(hours, L"h"); @@ -832,6 +832,70 @@ namespace jdi return result; } + /** + * @brief 把整数展开成十进制数字(内部实现,宽/窄共用同一套算法)。 + * + * ⚠️ 这里**不能**用 `swprintf`:本函数存在的全部意义就是让产物不再引用 + * MinGW 的 printf 引擎(见头文件注释)。手写 10 次除法比一次格式串解析还快, + * 且无本地化影响。 + * + * @param [OUT] buffer 输出缓冲(从尾部向前写)。 + * @param [IN] value 待转换整数。 + * @return 文本起始指针(指向 buffer 内部)。 + */ + template + static const Char* ExpandInteger(Char* buffer, std::size_t capacity, long long value, + std::size_t& length) + { + Char* cursor = buffer + capacity; + const bool negative = (value < 0); + // `0 - (unsigned long long)value` 对 LLONG_MIN 也成立(模 2^64 语义)。 + unsigned long long magnitude = negative ? (0ull - static_cast(value)) + : static_cast(value); + do + { + *--cursor = static_cast('0' + static_cast(magnitude % 10ull)); + magnitude /= 10ull; + } while (magnitude != 0ull); + if (negative) + { + *--cursor = static_cast('-'); + } + length = static_cast((buffer + capacity) - cursor); + return cursor; + } + + std::wstring FormatInteger(long long value) + { + wchar_t buffer[24]; // -9223372036854775808 = 20 字符 + 余量 + std::size_t length = 0; + const wchar_t* begin = ExpandInteger(buffer, sizeof(buffer) / sizeof(buffer[0]), value, length); + return std::wstring(begin, length); + } + + std::wstring FormatIntegerUnsigned(unsigned long long value) + { + // 上限 20 位十进制数字 + 余量(用有符号重载会溢出,故单独展开一遍)。 + wchar_t buffer[24]; + wchar_t* cursor = buffer + (sizeof(buffer) / sizeof(buffer[0])); + unsigned long long magnitude = value; + do + { + *--cursor = static_cast(L'0' + static_cast(magnitude % 10ull)); + magnitude /= 10ull; + } while (magnitude != 0ull); + return std::wstring(cursor, static_cast(buffer + (sizeof(buffer) / sizeof(buffer[0])) - + cursor)); + } + + std::string FormatIntegerNarrow(long long value) + { + char buffer[24]; + std::size_t length = 0; + const char* begin = ExpandInteger(buffer, sizeof(buffer) / sizeof(buffer[0]), value, length); + return std::string(begin, length); + } + double NiceStep(double range, int targetTicks) { if (!(range > 0.0) || !std::isfinite(range) || targetTicks <= 0)