#include "Utf8Util.hpp" #include #include #include #include #include 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(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(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(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(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(a - L'A' + L'a'); } if (IsUpperAscii(b)) { b = static_cast(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(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(*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(*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(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(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(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 ---- // ⚠️⚠️ 与下面的亚秒分支同一原则:**先量化到整数皮秒,再分解**。 // 直接对 double 做 `floor` + `fraction * 1000` 会被浮点噪声坑到: // `0.9999999999s` 的余数四舍五入后是 `1000.000ms`(应进位成 `1s`), // 而 `5.0599999999999996s` 与 `5.06s` 会得到不同的尾数(用户 2026-11-03 报告)。 if (value >= 1.0) { const long long totalPs = static_cast(std::llround(value * 1.0e12)); long long wholeSeconds = totalPs / 1000000000000LL; const long long restPs = totalPs % 1000000000000LL; // 次级(ms)以「千分之一 ms」为整数单位,保证舍入一次到位。 long long milliMs = (restPs + 500000LL) / 1000000LL; if (milliMs >= 1000LL * 1000LL) { ++wholeSeconds; milliMs = 0; } std::wstring text = FormatDecimal(static_cast(wholeSeconds)) + L"s"; if (milliMs > 0) { // ⚠️ 次级单位(ms)**最多保留三位小数**(用户 2026-11-03 要求:`3ms 696.631us` // 这类显示要能看到真实尾数);整数则不加小数点(`5.06s` → `5s 60ms`)。 text += L" "; text += FormatSubUnit(static_cast(milliMs) / 1000.0, kDurationSubUnitDecimals); text += L"ms"; } return text; } // ---- 顶级单位低于 s:ms / us / ns / ps ---- // ⚠️⚠️ **必须以整数(皮秒)为唯一事实来源做分解**,不能对 double 反复 floor/除法。 // 同一个物理间隔(例如 `0.001s`)在两次独立计算里可能分别是 // `0.0010000000000000002` 与 `0.0009999999999999998`;任何"接近 1000 就进位" // 的浮点启发式都会在边界漏判,于是同一数值出现 `1ms` 与 `1000us` 两种显示, // 还会留下 `100us 0ns` 这种纯噪声尾数(用户 2026-11-03 报告)。 // 量化到 ps 之后噪声被彻底消除:同一数值必然得到同一字符串。 const long long totalPs = static_cast(std::llround(value * 1.0e12)); if (totalPs <= 0) { // 小于 0.5ps:无法量化,保留高精度小数直接显示。 return FormatDecimal(value / 1.0e-12) + L" ps"; } struct SubUnit { long long ps; const wchar_t* name; }; const SubUnit subUnits[] = { {1000000000LL, L"ms"}, {1000000LL, L"us"}, {1000LL, L"ns"}, {1LL, L"ps"}, }; const int subCount = 4; // 量化后达到 1s ⇒ 等价于上面的秒级分支(`999.9999999999s` → `1s`)。 if (totalPs >= 1000000000000LL) { long long wholeSeconds = totalPs / 1000000000000LL; const long long restPs = totalPs % 1000000000000LL; long long milliMs = (restPs + 500000LL) / 1000000LL; if (milliMs >= 1000LL * 1000LL) { ++wholeSeconds; milliMs = 0; } std::wstring text = FormatDecimal(static_cast(wholeSeconds)) + L"s"; if (milliMs > 0) { text += L" "; text += FormatSubUnit(static_cast(milliMs) / 1000.0, kDurationSubUnitDecimals); text += L"ms"; } return text; } // 选主单位:第一个"能被 totalPs 容纳"的单位。 int index = 0; while (index + 1 < subCount && totalPs < subUnits[index].ps) { ++index; } // 把 totalPs 分解成 [主单位整数] + [次级单位(千分之一为单位的整数)]。 // 抽成 lambda 是因为升级单位后需要用同一个 totalPs 重算,避免累积误差。 long long whole = 0; long long milliNext = 0; const auto decompose = [&]() { whole = totalPs / subUnits[index].ps; milliNext = 0; if (index + 1 >= subCount) { return; } const long long restPs = totalPs % subUnits[index].ps; const long long nextPs = subUnits[index + 1].ps; // 次级值保留 3 位小数 ⇒ 用「千分之一个次级单位」作为整数量纲。 milliNext = (restPs * 1000LL + nextPs / 2) / nextPs; // 次级舍入后达到 1000 个次级单位 ⇒ 进位到主单位。 if (milliNext >= 1000LL * 1000LL) { ++whole; milliNext = 0; } }; decompose(); // 主单位达到 1000 且没有次级尾数 ⇒ 升级到更高的主单位(`1000us` → `1ms`)。 while (index > 0 && whole >= 1000LL && milliNext == 0) { --index; decompose(); } // 主单位已是 ms 且恰好进位到 1000ms ⇒ 直接显示为秒。 if (index == 0 && milliNext == 0 && whole >= 1000LL) { return FormatDecimal(static_cast(whole) / 1000.0) + L"s"; } std::wstring text = FormatDecimal(static_cast(whole)); text += subUnits[index].name; if (milliNext > 0) { // ⚠️ 次级单位**最多保留三位小数**(用户 2026-11-03 要求): // `0.003696631s` → 顶级 `3ms` + 次级 `696.631us`(而不是 `3ms 700us`)。 text += L" "; text += FormatSubUnit(static_cast(milliNext) / 1000.0, 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"—"; } // ⚠️⚠️ **单位选择必须按「舍入后的显示值」判定,否则同一物理量会出现两种显示**: // `999.9999999 Hz` 走 Hz 分支显示 `1000.000 Hz`,而 `1000.0000001 Hz` 走 kHz 分支 // 显示 `1.000 kHz` —— 用户 2026-11-03 报告的 `1000.00 Hz` vs `1.000 kHz` 就是这个。 // 因此:先取单位,再用**保留 3 位小数后的值**判断是否达到 1000,达到就升级。 const wchar_t* units[] = {L"Hz", L"kHz", L"MHz", L"GHz"}; const double scales[] = {1.0, 1.0e3, 1.0e6, 1.0e9}; const int unitCount = 4; int unitIndex = 0; for (int i = unitCount - 1; i >= 0; --i) { if (hertz >= scales[i]) { unitIndex = i; break; } } // 向上校正:舍入到 3 位小数后若达到 1000 个当前单位,就升到上一级。 const double epsilon = 0.5 * std::pow(10.0, -kFrequencyDecimals); while (unitIndex + 1 < unitCount && (hertz / scales[unitIndex]) + epsilon >= 1000.0) { ++unitIndex; } // 向下校正:若当前单位下小于 1(极低频边界),降一级以免出现 `0.001 kHz` 这类。 while (unitIndex > 0 && (hertz / scales[unitIndex]) < 1.0 - epsilon) { --unitIndex; } const wchar_t* unit = units[unitIndex]; const double scaled = hertz / scales[unitIndex]; // ⚠️ 频率同样不得出现科学计数法(用户 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(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(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(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& 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(firstIndex); const long long last = static_cast(lastIndex); if (last < first) { return; } const long long count = last - first + 1; if (count > 512) { return; } ticks.reserve(static_cast(count)); for (long long index = first; index <= last; ++index) { ticks.push_back(static_cast(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(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