/** * @file FormulaEvalTests.cpp * @brief `Utilities/FormulaEval` 的语法 / 语义回归。 * * 重点用例: * * `program.matches_pointwise` —— 批量求值(`FormulaProgram`)与逐点求值 * (`EvaluateFormula`)必须**逐位相同**。第 32 轮的性能优化(常量折叠、幂次改写、 * `always_inline`)只允许改速度,任何一位差异都是回归。 * * 语法边界:`^` 右结合、`-2^2 = -4`、单独的 `f` 非法、`2e` 非法。 */ #include #include #include #include #include "DataModel.hpp" #include "FormulaEval.hpp" #include "UnitTestFramework.hpp" namespace { /** @brief 取 double 的位模式。 */ std::uint64_t BitsOf(const double value) { std::uint64_t bits = 0; __builtin_memcpy(&bits, &value, sizeof(bits)); return bits; } } // namespace JDI_TEST(formula_syntax_acceptance) { std::wstring error; // 合法写法 static const wchar_t* const valid[] = { L"f(t)", L"f(t)*2+1", L"f(t)^2", L"-f(t)", L"e*pi", L"2^-1", L"X", L"x", L"(f(t)-1)/(f(t)+1)", L"2*pi*f(t)", }; for (const wchar_t* const text : valid) { JDI_REQUIRE("valid formula", jdi::ValidateFormula(text, &error)); } // 非法写法 static const wchar_t* const invalid[] = { L"f", L"f(", L"f(t)+", L"f(t)*", L"()", L"2e", L"f(t) ** 2", L"*2", }; for (const wchar_t* const text : invalid) { const bool accepted = jdi::ValidateFormula(text, &error); if (accepted) { std::printf(" [INFO] unexpectedly accepted: \"%ls\"\n", text); } JDI_REQUIRE("invalid formula must be rejected", !accepted); } // 空串 = 不映射(合法) JDI_CHECK(jdi::ValidateFormula(L"", &error)); } JDI_TEST(formula_semantics) { // `^` 右结合:2^3^2 = 2^(3^2) = 512 JDI_CHECK_NEAR(jdi::EvaluateFormula(L"2^3^2", 0.0), 512.0, 1e-9); // 一元负号优先于幂:-2^2 = -(2^2) = -4 JDI_CHECK_NEAR(jdi::EvaluateFormula(L"-2^2", 0.0), -4.0, 1e-12); // 指数的负号:2^-1 = 0.5 JDI_CHECK_NEAR(jdi::EvaluateFormula(L"2^-1", 0.0), 0.5, 1e-12); // 常量 e / pi(大小写均可) JDI_CHECK_NEAR(jdi::EvaluateFormula(L"e", 0.0), 2.718281828459045, 1e-15); JDI_CHECK_NEAR(jdi::EvaluateFormula(L"pi", 0.0), 3.141592653589793, 1e-15); JDI_CHECK_NEAR(jdi::EvaluateFormula(L"PI", 0.0), 3.141592653589793, 1e-15); // 恒等写法 JDI_CHECK_NEAR(jdi::EvaluateFormula(L"f(t)", 3.25), 3.25, 0.0); // 真实使用过的表达式(第 32 轮用户输入):f(t)*2/1.25+14.2,f(2) = 3.2 + 14.2 JDI_CHECK_NEAR(jdi::EvaluateFormula(L"f(t)*2/1.25+14.2", 2.0), 17.4, 1e-12); // 幂次改写的等价性(实现会把 x^2/x^3/x^0.5 换成乘法/sqrt) static const double points[] = {-3.5, -1.0, -0.25, 0.0, 0.5, 1.0, 2.0, 7.25, 1.0e6}; for (const double point : points) { JDI_CHECK_BIT_EQUAL(jdi::EvaluateFormula(L"f(t)^2", point), point * point); JDI_CHECK_NEAR(jdi::EvaluateFormula(L"f(t)^0.5", point < 0.0 ? -point : point), std::sqrt(point < 0.0 ? -point : point), 0.0); } } JDI_TEST(formula_identity_helpers) { // `f(t)` 与空串等价(第 32 轮硬规则:点进输入框再点出去不得写入"恒等公式") JDI_CHECK(jdi::FormulaIsIdentity(L"")); JDI_CHECK(jdi::FormulaIsIdentity(L"f(t)")); JDI_CHECK(!jdi::FormulaIsIdentity(L"f(t)*2")); jdi::SignalStyle plain; JDI_CHECK(!plain.HasFormula()); plain.formula = L"f(t)"; JDI_CHECK(!plain.HasFormula()); plain.formula = L"f(t)*2"; JDI_CHECK(plain.HasFormula()); // 归一化:`X` / `F(T)` 统一写成 `f(t)`;单独的 `f` **不**归一化(非法写法,交给校验报错) JDI_CHECK_WIDE_EQ(jdi::NormalizeFormulaText(L"X"), std::wstring(L"f(t)")); JDI_CHECK_WIDE_EQ(jdi::NormalizeFormulaText(L"F(T)"), std::wstring(L"f(t)")); JDI_CHECK_WIDE_EQ(jdi::NormalizeFormulaText(L"f"), std::wstring(L"f")); } JDI_TEST(formula_program_matches_pointwise) { static const wchar_t* const formulas[] = { L"f(t)*2/1.25+14.2", L"f(t)^2", L"f(t)^3", L"(f(t)-1)/(f(t)+1)", L"2*pi*f(t)+e", L"-f(t)*0.5", L"f(t)^0.5", L"f(t)-2", }; std::mt19937 rng(20261105u); std::uniform_real_distribution distribution(-500.0, 500.0); for (const wchar_t* const formula : formulas) { jdi::FormulaProgram program(formula); JDI_REQUIRE("program must compile", program.Valid()); for (int iteration = 0; iteration < 4000; ++iteration) { const double x = distribution(rng); const double pointwise = jdi::EvaluateFormula(formula, x); const double batched = program.Evaluate(x); if (BitsOf(pointwise) != BitsOf(batched)) { std::printf(" [INFO] %ls x=%.17g pointwise=%.17g batched=%.17g\n", formula, x, pointwise, batched); JDI_CHECK_BIT_EQUAL(batched, pointwise); break; } } } } JDI_TEST(formula_invalid_falls_back_safely) { // 非法表达式:求值必须**原样返回采样值**(波形照常显示,不会因为写错公式变空) JDI_CHECK_NEAR(jdi::EvaluateFormula(L"f(t)@@@@", 1.5), 1.5, 0.0); JDI_CHECK_NEAR(jdi::EvaluateFormula(L"f(t)@@@@", -2.25), -2.25, 0.0); }