Files
Just-DataInspector/Utilities/Src/Utf8Util.cpp
T
DeepSeek-V4.1-Flash 74456b9305 fix(formula,resample): 公式映射卡顿根治 + 语法/输入框完善 + 重采样面板三处修正
一、「公式映射」卡顿(用户报告:只是点进输入框就卡,编辑过公式后彻底没法用)
1. 真因:菜单内联编辑提交时无条件回写初值 ⇒ 点进输入框再点别处就把
   formula = "f(t)" 写进样式;而 HasFormula() 只判 !empty(),于是
   DrawTransformOrderPreserving() 变 false、ComputeLaneRange() 每帧逐点扫描
   整条信号(2580 万采样 ≈ 960ms/帧)。
   修法:新增 FormulaIsIdentity()(空串与 f(t) 等价);提交时归一化后恒等即清空;
   BuildLaneVisuals() 为「网格幅值范围」加指纹缓存(数据指针/长度/统计值 +
   影响数值的样式字段),键不变直接复用(实测 0.0005ms)。
2. 真实公式仍然贵:新增 PlotView::AcquireFormulaEnvelope()(变换后的逐分组
   包络,65536 组,按需算一次并缓存),BuildPolyline 的包络带改用它。
   实测:静止 215→21ms、中键拖动 8ms、缩放最坏 843→81ms;极值仍逐点统计
   (不会漏尖峰)。
3. AcquireProgram 只比 data() 指针 + 长度 ⇒ std::wstring 短串走 SSO 时,
   等长改公式会一直复用旧程序(f(t)^2→f(t)^3 波形不更新)。改为按内容判定。

二、公式语法(Utilities/FormulaEval)
- 新增 ^ 幂(右结合,2^3^2=512、2^-1=0.5、-2^2=-4),支持 2^f(t) 与 f(t)^2;
- 常量 e / pi(大小写均接受,保存前统一小写);
- 数据点只允许 X / x / f(t):单独的 f 不再接受(且不归一化,直接报错);
- 数字扫描修正:2e 里的 e 不再被当成指数吞掉。

三、「公式映射」输入框(SignalPanelMenu)
- Y= 画在输入框内部(非编辑态),进入编辑态只显示公式本身;
- label 留空 ⇒ 输入框占满整行,与下方「系数调整」同宽。

四、「重采样」面板
- 修复点进间隔/频率输入框后输入框整块消失(paintOverlays 的 PaintEditField
  名单漏了 RESAMPLE_INTERVAL / RESAMPLE_FREQUENCY);
- 最大/最小数据间隔:3 位有效数字 + Δt 复合单位,末尾追加(频率)尾缀;
- 间隔显示自适应单位、编辑态用秒;频率显示自适应单位、编辑态用 Hz(均不带单位);
- 面板宽度按含(频率)尾缀的文本量宽。

五、十进制格式化(Utf8Util)
- 新增 FormatDecimal(绝不产生科学计数法);FormatDurationCompound /
  FormatDurationSeconds / FormatFrequency 改用它,Δt/间隔/频率读数不再出现 1e-05。

验证:离屏探针 .preflight/perf/probe/formulacheck_main.cpp 75/75;
make release / make debug 零 warning;Release 856,064 B。
2026-11-03 02:55:22 +08:00

828 lines
19 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"; /**< 单位名称。 */
};
}
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 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" ";
text += FormatDecimal(remainderMs);
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" ";
text += FormatDecimal(inNext);
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”,改为十进制形式。
return FormatDecimal(scaled) + 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