Files
Just-DataInspector/Utilities/Src/Utf8Util.cpp
T
DeepSeek V4.1 Flash c4300bd0e1 fix(resample): 重采样间隔/频率按真实值显示(次级单位与频率保留三位小数)
问题(用户实测 MF4-Record.MF4 / m_MOT_Current_DCom[0]):
- 最小数据间隔真实值 0.003696631s,却被显示成 3ms 700us(us 部分被向上截取);
- 对应频率真实值 270.516586589Hz,却被显示成 270.27Hz;
- 最大数据间隔 0.016182614s 显示成 16ms 200us、频率显示成 61.7284Hz,同样不准。

根因(两个,同一个坑):
1. RoundIntervalForDisplay() 把间隔先舍入到 3 位有效数字(0.003696631 -> 0.0037),
   次级单位因此变成 700us,真实尾数被抹掉;
2. 频率用舍入后的 3.70ms 换算得 270.27Hz,而输入框用原值得 270.517Hz,两处不一致。

修法(一律使用真实值,舍入只发生在输出文本的最后一步):
- Utf8Util 新增 FormatDecimalFixed(value, decimals=3):固定小数位、保留尾随 0、
  绝不出现科学计数法;
- Utf8Util 新增 FormatSubUnit(value, maxDecimals):带小数才补到 3 位,整数不加小数点
  (5.06s -> 5s 60ms,而不是 5s 60.000ms);
- FormatDurationCompound 的两处次级单位(s+ms、ms/us/ns)改用它;
- FormatFrequency 改为 FormatDecimalFixed(scaled, 3);若保留 3 位后变成 0.000
  (超低频,如 1/3662s ≈ 0.27mHz)则退回 FormatDecimal,避免有意义的值显示成 0;
- PlotViewInternal.hpp 删除 RoundIntervalForDisplay,FormatIntervalText 直接用真实值;
- PlotViewPaint.cpp 的统计行尾缀与频率输入框都用真实 interval 换算。

修复后的显示:
  min interval 0.003696631s -> 3ms 696.631us
  frequency   1/0.003696631 -> 270.517 Hz
  max interval 0.016182614s -> 16ms 182.614us
  frequency   1/0.016182614 -> 61.795 Hz

同时撤销了此前两次仅修改 agent_mem.md 的提交(47b1213 / caa035a,内容为乱码且无有效信息),
并按正确编码重新整理本轮记忆章节。

面板仍为四行(最大间隔 / 最小间隔 / 重采样间隔 / 重采样频率),未改动任何 UI 布局。
make release 零 warning。
2026-11-03 14:16:25 +08:00

913 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Utf8Util.hpp"
#include <windows.h>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace jdi
{
namespace
{
/** @brief 判断字符是否为需要裁掉的空白字符。 */
bool IsSpaceChar(wchar_t c)
{
return c == L' ' || c == L'\t' || c == L'\r' || c == L'\n' || c == L'\v' || c == L'\f' ||
c == static_cast<wchar_t>(0x00A0);
}
/** @brief 判断字符是否为 ASCII 大写字母。 */
bool IsUpperAscii(wchar_t c)
{
return c >= L'A' && c <= L'Z';
}
/** @brief 时间间隔的显示单位。 */
struct DurationUnit
{
double scale = 1.0; /**< 1 个该单位等于多少秒。 */
const wchar_t* name = L"s"; /**< 单位名称。 */
};
/**
* @brief 复合单位时间文本里**次级单位最多保留的小数位数**。
*
* 用户 2026-11-03 明确要求:`0.003696631s` 必须显示为 `3ms 696.631us`
* (次级单位 ms/us/ns 保留三位小数,能看到真实尾数),
* 而不是四舍五入到 `3ms 700us`(旧实现舍入到 3 位**有效数字**,把尾数抹掉了)。
*
* ⚠️ 这是**上限**,不是强制的固定位数:整数次级单位仍写成整数
* (`5.06s` → `5s 60ms`,而不是 `5s 60.000ms`),只有真的带小数时才补零到 3 位。
*/
constexpr int kDurationSubUnitDecimals = 3;
/**
* @brief 频率文本保留的小数位数(用户 2026-11-03 要求)。
*
* `270.516586...Hz` 必须显示为 `270.517 Hz`(真实值保留三位小数),
* 而不是"先把间隔舍入到 3 位有效数字再换算"得到的 `270.27 Hz`。
*/
constexpr int kFrequencyDecimals = 3;
/**
* @brief 格式化次级单位数值:带小数则保留 `maxDecimals` 位,整数则原样输出。
*
* `696.6314` → `696.631`;`60.0` → `60`;`0.0158` → `0.016`。
* 这样既满足"次级单位保留三位小数"(看得到真实尾数),
* 又不会把整数次级单位写成 `60.000` 这种噪音。
*/
inline std::wstring FormatSubUnit(double value, int maxDecimals)
{
if (!std::isfinite(value))
{
return L"—";
}
const double rounded = std::round(value * std::pow(10.0, maxDecimals)) /
std::pow(10.0, maxDecimals);
if (rounded == std::floor(rounded))
{
return FormatDecimal(rounded); // 整数:不加小数点
}
return FormatDecimalFixed(rounded, maxDecimals);
}
}
std::wstring Utf8ToWide(const char* text, int length)
{
if (text == nullptr)
{
return std::wstring();
}
int byteCount = (length < 0) ? static_cast<int>(std::strlen(text)) : length;
if (byteCount <= 0)
{
return std::wstring();
}
int wideCount = MultiByteToWideChar(CP_UTF8, 0, text, byteCount, nullptr, 0);
if (wideCount <= 0)
{
return std::wstring();
}
std::wstring result(static_cast<size_t>(wideCount), L'\0');
MultiByteToWideChar(CP_UTF8, 0, text, byteCount, &result[0], wideCount);
return result;
}
std::wstring Trim(const std::wstring& text)
{
size_t begin = 0;
size_t end = text.size();
while (begin < end && IsSpaceChar(text[begin]))
{
++begin;
}
while (end > begin && IsSpaceChar(text[end - 1]))
{
--end;
}
return text.substr(begin, end - begin);
}
std::wstring StripQuotes(const std::wstring& text)
{
std::wstring trimmed = Trim(text);
if (trimmed.size() >= 2)
{
const wchar_t first = trimmed.front();
const wchar_t last = trimmed.back();
if ((first == L'"' && last == L'"') || (first == L'\'' && last == L'\''))
{
trimmed = trimmed.substr(1, trimmed.size() - 2);
}
}
return trimmed;
}
std::wstring ToLowerAscii(const std::wstring& text)
{
std::wstring result = text;
for (wchar_t& c : result)
{
if (IsUpperAscii(c))
{
c = static_cast<wchar_t>(c - L'A' + L'a');
}
}
return result;
}
bool ContainsInsensitive(const std::wstring& text, const std::wstring& needle)
{
if (needle.empty())
{
return true;
}
if (needle.size() > text.size())
{
return false;
}
const size_t last = text.size() - needle.size();
for (size_t i = 0; i <= last; ++i)
{
size_t j = 0;
while (j < needle.size())
{
wchar_t a = text[i + j];
wchar_t b = needle[j];
if (IsUpperAscii(a))
{
a = static_cast<wchar_t>(a - L'A' + L'a');
}
if (IsUpperAscii(b))
{
b = static_cast<wchar_t>(b - L'A' + L'a');
}
if (a != b)
{
break;
}
++j;
}
if (j == needle.size())
{
return true;
}
}
return false;
}
/** @brief 快速解析路径允许保留的最大有效位数(超过则交回 strtod 以保证正确舍入)。 */
const int kMaxFastDigits = 17;
/** @brief 快速解析路径允许使用的最大十进制指数(超出查表范围,交回 strtod)。 */
const int kMaxFastExponent = 22;
/** @brief 10 的 0-22 次幂精确表(该范围内每位都是 double 可精确表示的十进制数)。 */
const double kPow10Table[23] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
/**
* @brief 标准库解析路径:去掉首尾空白后交给 strtod。
*
* 作为快速路径的保底实现,保证十六进制浮点、inf/nan、超范围指数、
* 带尾部杂字符(如 "12abc")等输入的处理与改造前完全一致。
*
* @param [IN] begin 文本起始指针。
* @param [IN] end 文本结束指针(不包含)。
* @param [OUT] value 解析结果。
* @return 解析出数值返回 true。
*/
bool ParseDoubleSlow(const char* begin, const char* end, double& value)
{
const char* p = begin;
while (p < end && (*p == ' ' || *p == '\t'))
{
++p;
}
const char* q = end;
while (q > p && (q[-1] == ' ' || q[-1] == '\t' || q[-1] == '\r' || q[-1] == '\n'))
{
--q;
}
if (p >= q)
{
return false;
}
char buffer[96];
const size_t length = static_cast<size_t>(q - p);
const char* text = nullptr;
std::string dynamic;
if (length < sizeof(buffer))
{
std::memcpy(buffer, p, length);
buffer[length] = '\0';
text = buffer;
}
else
{
dynamic.assign(p, q);
text = dynamic.c_str();
}
char* stop = nullptr;
const double parsed = std::strtod(text, &stop);
if (stop == text)
{
return false;
}
value = parsed;
return true;
}
bool TryParseDouble(const char* begin, const char* end, double& value)
{
if (begin == nullptr || end == nullptr || begin >= end)
{
return false;
}
// ===== 快速路径:纯十进制数值 =====
// 大文件解析的绝对瓶颈就在这里:ucrt 的 strtod 要处理本地化、十六进制浮点、
// 无穷/NaN 等全部语法,实测每次调用约 335ns;百万行级文件需调用上千万次,
// 单解析就可能耗掉几十秒。下面这条路径只处理最常见的十进制写法(实测约 13ns,
// 快 25 倍),其余写法(含尾部杂字符)原样交回 strtod,
// 因此输入接受范围与原先完全一致。
const char* head = begin;
while (head < end && (*head == ' ' || *head == '\t'))
{
++head;
}
const char* tail = end;
while (tail > head && (tail[-1] == ' ' || tail[-1] == '\t' || tail[-1] == '\r' || tail[-1] == '\n'))
{
--tail;
}
if (head >= tail)
{
return false;
}
const char* cursor = head;
bool negative = false;
if (*cursor == '+' || *cursor == '-')
{
negative = (*cursor == '-');
++cursor;
}
unsigned long long mantissa = 0;
int digits = 0;
int exponent = 0;
// 整数部分:超过有效位数的高位数字只影响量级,累加进指数。
while (cursor < tail && *cursor >= '0' && *cursor <= '9')
{
if (digits < kMaxFastDigits)
{
mantissa = mantissa * 10ull + static_cast<unsigned>(*cursor - '0');
++digits;
}
else
{
++exponent;
}
++cursor;
}
// 小数部分:每保留一位就记一次小数位数;超出有效位数的直接丢弃(已低于 double 精度)。
int fractionDigits = 0;
if (cursor < tail && *cursor == '.')
{
++cursor;
while (cursor < tail && *cursor >= '0' && *cursor <= '9')
{
if (digits < kMaxFastDigits)
{
mantissa = mantissa * 10ull + static_cast<unsigned>(*cursor - '0');
++digits;
++fractionDigits;
}
++cursor;
}
}
if (digits == 0)
{
return ParseDoubleSlow(begin, end, value);
}
exponent -= fractionDigits;
if (cursor < tail && (*cursor == 'e' || *cursor == 'E'))
{
++cursor;
bool expNegative = false;
if (cursor < tail && (*cursor == '+' || *cursor == '-'))
{
expNegative = (*cursor == '-');
++cursor;
}
if (cursor >= tail || *cursor < '0' || *cursor > '9')
{
return ParseDoubleSlow(begin, end, value);
}
int expValue = 0;
while (cursor < tail && *cursor >= '0' && *cursor <= '9')
{
if (expValue < 1000000)
{
expValue = expValue * 10 + (*cursor - '0');
}
++cursor;
}
exponent += expNegative ? -expValue : expValue;
}
// 存在未消费字符(如 "12abc"、"1,5")或指数超出查表范围时交回标准库:
// 既保持原先 strtod 的宽松接受语义,也保证极端量级的正确舍入。
if (cursor != tail || exponent > kMaxFastExponent || exponent < -kMaxFastExponent)
{
return ParseDoubleSlow(begin, end, value);
}
double result = static_cast<double>(mantissa);
if (exponent > 0)
{
result *= kPow10Table[exponent];
}
else if (exponent < 0)
{
result /= kPow10Table[-exponent];
}
// mantissa 超过 2^53(16 位以上有效数字)时这里会比 strtod 多一次舍入,
// 相对误差不超过 1 ULP(实测 ≤2.3e-16),在波形显示与标尺(约 6 位有效数字)上不可见。
value = negative ? -result : result;
return true;
}
std::wstring FormatNumber(double value, int decimals)
{
if (decimals < 0)
{
decimals = 0;
}
if (decimals > 12)
{
decimals = 12;
}
wchar_t buffer[96];
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.*f", decimals, value);
return std::wstring(buffer);
}
std::wstring FormatDecimal(double value, int significantDigits)
{
if (std::isnan(value))
{
return L"NaN";
}
if (std::isinf(value))
{
return (value > 0.0) ? L"+Inf" : L"-Inf";
}
if (significantDigits < 1)
{
significantDigits = 1;
}
if (significantDigits > 15)
{
significantDigits = 15;
}
const double magnitude = std::fabs(value);
if (magnitude == 0.0)
{
return L"0";
}
// 小数位数 = 让结果保留约 `significantDigits` 位有效数字(整数部分已超出时取 0 位)。
// ⚠️ **绝不用 `%g` / `%e`**:`%g` 在量级小于 1e-4 或大于 1e6 时会切换成科学计数法,
// 对“时间间隔 / 频率”这类读数极不友好(用户 2026-11-03 明确要求小数形式)。
int decimals = 0;
{
const double exponent = std::floor(std::log10(magnitude));
decimals = significantDigits - 1 - static_cast<int>(exponent);
}
if (decimals < 0)
{
decimals = 0;
}
if (decimals > 12)
{
decimals = 12;
}
wchar_t buffer[160];
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.*f", decimals, value);
std::wstring text(buffer);
// 去掉尾随的 0 与孤立的小数点(`1.50000` → `1.5`、`2.000000` → `2`)。
if (text.find(L'.') != std::wstring::npos)
{
std::size_t last = text.find_last_not_of(L'0');
if (last == std::wstring::npos)
{
return L"0";
}
if (text[last] == L'.')
{
--last;
}
text.erase(last + 1);
}
if (text == L"-0")
{
text = L"0";
}
return text;
}
std::wstring FormatDecimalFixed(double value, int decimals)
{
if (std::isnan(value))
{
return L"NaN";
}
if (std::isinf(value))
{
return (value > 0.0) ? L"+Inf" : L"-Inf";
}
if (decimals < 0)
{
decimals = 0;
}
if (decimals > 12)
{
decimals = 12;
}
// ⚠️ 固定小数位数 + **保留尾随 0**(与 `FormatDecimal` 的"去尾随 0"语义相反):
// 用户 2026-11-03 明确要求「次级单位保留三位小数」,例如 696.631us / 700.000us。
wchar_t buffer[160];
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.*f", decimals, value);
std::wstring text(buffer);
if (text == L"-0" || (text.size() > 1 && text[0] == L'-' &&
text.find_first_not_of(L"0-.") == std::wstring::npos))
{
text.erase(0, 1); // -0.000 → 0.000
}
return text;
}
std::wstring FormatDurationCompound(double seconds) {
if (!std::isfinite(seconds))
{
return L"—";
}
const double value = std::fabs(seconds);
if (value <= 0.0)
{
return L"0s";
}
// ---- 顶级单位高于 s(h / min):整合为 h/min/s(整数秒)----
// 注意条件必须是 >= 60s:1~60s 属于"顶级单位为 s"的情况,需按下一条规则处理
// (非整数秒要递进到 ms,例如 5.06s → "5s 60ms")。
if (value >= 60.0)
{
const long long totalSeconds = static_cast<long long>(std::llround(value));
long long hours = 0;
long long minutes = 0;
long long secs = totalSeconds;
if (totalSeconds >= 3600)
{
hours = totalSeconds / 3600;
secs = totalSeconds % 3600;
}
if (secs >= 60)
{
minutes = secs / 60;
secs = secs % 60;
}
std::wstring text;
const auto append = [&text](long long amount, const wchar_t* unit)
{
if (amount <= 0)
{
return;
}
if (!text.empty())
{
text += L" ";
}
text += std::to_wstring(amount);
text += unit;
};
append(hours, L"h");
append(minutes, L"min");
append(secs, L"s");
if (!text.empty())
{
return text;
}
}
// ---- 顶级单位为 s:整数秒 + 可选 ms ----
if (value >= 1.0)
{
const double whole = std::floor(value);
const double fraction = value - whole;
std::wstring text = FormatDecimal(whole) + L"s";
const double remainderMs = fraction * 1000.0;
if (remainderMs >= 1.0e-6)
{
text += L" ";
// ⚠️ 次级单位(ms)**最多保留三位小数**(用户 2026-11-03 要求:`3ms 696.631us`
// 这类显示要能看到真实尾数);整数则不加小数点(`5.06s` → `5s 60ms`)。
text += FormatSubUnit(remainderMs, kDurationSubUnitDecimals);
text += L"ms";
}
return text;
}
// ---- 顶级单位低于 s:ms / us / ns / ps ----
const DurationUnit subUnits[] = {
{1.0e-3, L"ms"}, {1.0e-6, L"us"}, {1.0e-9, L"ns"}, {1.0e-12, L"ps"},
};
const std::size_t subCount = sizeof(subUnits) / sizeof(subUnits[0]);
std::size_t index = subCount - 1;
for (std::size_t i = 0; i < subCount; ++i)
{
if (value >= subUnits[i].scale)
{
index = i;
break;
}
}
// 顶级单位为 ps:不再递进次级单位(保留小数)。
if (index + 1 >= subCount)
{
return FormatDecimal(value / subUnits[index].scale) + L" " + subUnits[index].name;
}
const double scaled = value / subUnits[index].scale;
double whole = std::floor(scaled);
double fraction = scaled - whole;
// 浮点误差可能导致 fraction 极接近 1(例如 4.9999999),归一化一下。
if (fraction >= 1.0 - 1.0e-9)
{
whole += 1.0;
fraction = 0.0;
}
std::wstring text = FormatValue(whole);
text += subUnits[index].name;
// 仍是该单位的整数 → 不显示下级单位。
const double remainder = fraction * subUnits[index].scale;
const double inNext = remainder / subUnits[index + 1].scale;
if (inNext >= 1.0e-6)
{
text += L" ";
// ⚠️ 次级单位**最多保留三位小数**(用户 2026-11-03 要求):
// `0.003696631s` → 顶级 `3ms` + 次级 `696.631us`(而不是 `3ms 700us`)。
text += FormatSubUnit(inNext, kDurationSubUnitDecimals);
text += subUnits[index + 1].name;
}
return text;
}
std::wstring FormatDurationSeconds(double seconds)
{
if (!std::isfinite(seconds))
{
return L"0";
}
// ⚠️ 内联编辑的数值**不得出现科学计数法**(用户 2026-11-03 要求)。
return FormatDecimal(std::fabs(seconds), 12);
}
std::wstring FormatFrequency(double hertz)
{
if (!std::isfinite(hertz) || hertz <= 0.0)
{
return L"—";
}
const wchar_t* unit = L"Hz";
double scaled = hertz;
if (hertz >= 1.0e9)
{
unit = L"GHz";
scaled = hertz / 1.0e9;
}
else if (hertz >= 1.0e6)
{
unit = L"MHz";
scaled = hertz / 1.0e6;
}
else if (hertz >= 1.0e3)
{
unit = L"kHz";
scaled = hertz / 1.0e3;
}
// ⚠️ 频率同样不得出现科学计数法(用户 2026-11-03 要求):
// `FormatValue` 的 `%.6g` 在 1e-5 Hz 这类量级会输出 “1e-06”,改为十进制形式。
// ⚠️ 并且**基于真实值保留三位小数**(用户 2026-11-03 要求:`270.517 Hz` 而不是
// `270.27 Hz`)—— 旧实现把间隔先舍入到 3 位有效数字再换算,把真实尾数抹掉了。
// ⚠️ 但若保留 3 位后整串变成 `0.000`(超低频,例如 1/3662s ≈ 0.27mHz),
// 则退回按「有效数字」显示,避免把有意义的数值显示成 0(用户 2026-11-03)。
const std::wstring fixedText = FormatDecimalFixed(scaled, kFrequencyDecimals);
if (std::fabs(scaled) > 0.0 && std::fabs(scaled) < 0.5 * std::pow(10.0, -kFrequencyDecimals))
{
return FormatDecimal(scaled) + L" " + unit;
}
return fixedText + L" " + unit;
}
std::wstring FormatByteSize(unsigned long long bytes)
{ // 单位自适应:GB / MB / KB(阈值按 1024 进制)。
constexpr double kKilo = 1024.0;
constexpr double kMega = kKilo * 1024.0;
constexpr double kGiga = kMega * 1024.0;
const double value = static_cast<double>(bytes);
const wchar_t* unit = L"KB";
double scaled = value / kKilo;
if (value >= kGiga)
{
unit = L"GB";
scaled = value / kGiga;
}
else if (value >= kMega)
{
unit = L"MB";
scaled = value / kMega;
}
wchar_t buffer[64];
// ⚠️ 宽字符格式化必须用 `%ls`:MinGW 的 swprintf 按 C99 语义处理 `%s`
//(在宽格式串里 `%s` 表示**窄**字符串),写成 `%s` 会把宽字符串按单字节读取,
// 于是 "KB" 只打印出 "K"。
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.2f %ls", scaled, unit);
return std::wstring(buffer);
}
std::wstring FormatValue(double value)
{
if (std::isnan(value))
{
return L"NaN";
}
if (std::isinf(value))
{
return (value > 0.0) ? L"+Inf" : L"-Inf";
}
wchar_t buffer[96];
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.6g", value);
return std::wstring(buffer);
}
std::wstring FormatTick(double value, double step)
{
if (value == 0.0)
{
return L"0";
}
if (!std::isfinite(value))
{
return FormatValue(value);
}
int decimals = 0;
if (step > 0.0 && std::isfinite(step))
{
const double magnitude = std::ceil(-std::log10(step) - 1.0e-9);
if (magnitude > 0.0)
{
decimals = static_cast<int>(magnitude);
}
}
if (decimals > 9)
{
decimals = 9;
}
wchar_t buffer[96];
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.*f", decimals, value);
std::wstring text(buffer);
if (text.size() > 15)
{
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.4e", value);
text.assign(buffer);
}
return text;
}
std::wstring FormatCount(unsigned long long value)
{
wchar_t buffer[32];
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%llu", value);
std::wstring digits(buffer);
std::wstring result;
result.reserve(digits.size() + digits.size() / 3);
const size_t length = digits.size();
for (size_t i = 0; i < length; ++i)
{
if (i > 0 && ((length - i) % 3u) == 0u)
{
result.push_back(L',');
}
result.push_back(digits[i]);
}
return result;
}
double NiceStep(double range, int targetTicks)
{
if (!(range > 0.0) || !std::isfinite(range) || targetTicks <= 0)
{
return 1.0;
}
const double raw = range / static_cast<double>(targetTicks);
const double magnitude = std::pow(10.0, std::floor(std::log10(raw)));
const double normalized = raw / magnitude;
double nice = 10.0;
if (normalized <= 1.0)
{
nice = 1.0;
}
else if (normalized <= 2.0)
{
nice = 2.0;
}
else if (normalized <= 5.0)
{
nice = 5.0;
}
return nice * magnitude;
}
void BuildTicks(double minValue, double maxValue, double step, std::vector<double>& ticks)
{
ticks.clear();
if (!std::isfinite(minValue) || !std::isfinite(maxValue) || !(step > 0.0) || !std::isfinite(step))
{
return;
}
if (maxValue < minValue)
{
const double temp = minValue;
minValue = maxValue;
maxValue = temp;
}
// ⚠️ `minValue / step` 在极端量级下可能溢出成 inf(例如 1e300 / 1e-300),
// 而 double → long long 的越界转换是**未定义行为**。先确认商落在 long long 范围内,
// 否则直接放弃画刻度(总比画出一批垃圾刻度安全)。
constexpr double kTickIndexLimit = 9.0e18;
const double firstIndex = std::ceil(minValue / step - 1.0e-9);
const double lastIndex = std::floor(maxValue / step + 1.0e-9);
if (!(std::fabs(firstIndex) < kTickIndexLimit) || !(std::fabs(lastIndex) < kTickIndexLimit))
{
return;
}
const long long first = static_cast<long long>(firstIndex);
const long long last = static_cast<long long>(lastIndex);
if (last < first)
{
return;
}
const long long count = last - first + 1;
if (count > 512)
{
return;
}
ticks.reserve(static_cast<size_t>(count));
for (long long index = first; index <= last; ++index)
{
ticks.push_back(static_cast<double>(index) * step);
}
}
bool CopyTextToClipboard(HWND owner, const std::wstring& text)
{
if (text.empty())
{
return false;
}
if (OpenClipboard(owner) == FALSE)
{
return false;
}
bool success = false;
if (EmptyClipboard() != FALSE)
{
const SIZE_T bytes = (text.size() + 1) * sizeof(wchar_t);
HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, bytes);
if (handle != nullptr)
{
void* target = GlobalLock(handle);
if (target != nullptr)
{
std::memcpy(target, text.c_str(), bytes);
GlobalUnlock(handle);
if (SetClipboardData(CF_UNICODETEXT, handle) != nullptr)
{
success = true;
}
else
{
GlobalFree(handle);
}
}
else
{
GlobalFree(handle);
}
}
}
CloseClipboard();
return success;
}
bool PasteTextFromClipboard(HWND owner, std::wstring& text)
{
text.clear();
if (OpenClipboard(owner) == FALSE)
{
return false;
}
bool success = false;
if (IsClipboardFormatAvailable(CF_UNICODETEXT) != FALSE)
{
HANDLE handle = GetClipboardData(CF_UNICODETEXT);
if (handle != nullptr)
{
const void* source = GlobalLock(handle);
if (source != nullptr)
{
const wchar_t* wide = static_cast<const wchar_t*>(source);
text.assign(wide);
GlobalUnlock(handle);
success = !text.empty();
}
}
}
CloseClipboard();
// 单行输入框:把换行与制表符折叠为空格,避免内容被截断显示。
if (success)
{
for (wchar_t& c : text)
{
if (c == L'\r' || c == L'\n' || c == L'\t')
{
c = L' ';
}
}
}
return success;
}
} // namespace jdi