From e41ff73cdd90b7cf54c7ae962272c0933898a299 Mon Sep 17 00:00:00 2001 From: Alexander Moisseev Date: Wed, 14 Jan 2026 19:11:50 +0300 Subject: [PATCH] [Fix] Use proper rounding for symbol frequency statistics - Replace incorrect floor() with round() in rounding functions to avoid losing small values - Increase counters API frequency precision from 3 to 6 decimal places (need 5 to avoid rspamc displaying values as multiples of 0.06, need 6 for /counters endpoint itself - no additional overhead as JSON stores double anyway) - Add frequency_stddev field to counters API output (fixes zero stdev in `rspamc counters` output) - Clarify `rspamc counters` table header with "avg (stddev)" subheading - Fix WebUI to preserve frequency precision before scaling Example for symbol with frequency 0.004772 hits/sec: - Before: /symbols returns 0.004772, /counters returns 0.004000, `rspamc counters` shows 0.240 - After: /symbols returns 0.004772, /counters returns 0.004772, `rspamc counters` shows 0.286 --- interface/js/app/symbols.js | 3 ++- src/client/rspamc.cxx | 3 +++ src/libserver/symcache/symcache_impl.cxx | 17 +++++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/interface/js/app/symbols.js b/interface/js/app/symbols.js index 540eaa6e31..576547b75e 100644 --- a/interface/js/app/symbols.js +++ b/interface/js/app/symbols.js @@ -68,7 +68,8 @@ define(["jquery", "app/common", "footable"], item.frequency = 0; } freqs.push(item.frequency); - item.frequency = Number(item.frequency).toFixed(2); + // Don't round yet, keep precision for scaling + item.frequency = Number(item.frequency); if (!(item.group in lookup)) { lookup[item.group] = 1; distinct_groups.push(item.group); diff --git a/src/client/rspamc.cxx b/src/client/rspamc.cxx index af3a13f0a5..369c6b31ef 100644 --- a/src/client/rspamc.cxx +++ b/src/client/rspamc.cxx @@ -1562,6 +1562,9 @@ rspamc_counters_output(FILE *out, ucl_object_t *obj) "Frequency", "Hits"); rspamc_print(out, " {} \n", emphasis_argument(dash_buf)); + rspamc_print(out, "| {:<4} | {:<{}} | {:^7} | {:^13} | {:^7} |\n", "", + "", max_len, + "", "avg (stddev)", ""); rspamc_print(out, "| {:<4} | {:<{}} | {:^7} | {:^13} | {:^7} |\n", "", "", max_len, "", "hits/min", ""); diff --git a/src/libserver/symcache/symcache_impl.cxx b/src/libserver/symcache/symcache_impl.cxx index 44b7a4bab1..e2be5c40eb 100644 --- a/src/libserver/symcache/symcache_impl.cxx +++ b/src/libserver/symcache/symcache_impl.cxx @@ -373,7 +373,7 @@ auto symcache::load_items() -> bool template static constexpr auto round_to_hundreds(T x) { - return (::floor(x) * 100.0) / 100.0; + return ::round(x * 100.0) / 100.0; } bool symcache::save_items() const @@ -1031,7 +1031,7 @@ auto symcache::counters() const -> ucl_object_t * auto *top = ucl_object_typed_new(UCL_ARRAY); constexpr const auto round_float = [](const auto x, const int digits) -> auto { const auto power10 = ::pow(10, digits); - return (::floor(x * power10) / power10); + return (::round(x * power10) / power10); }; for (auto &pair: items_by_symbol) { @@ -1049,8 +1049,11 @@ auto symcache::counters() const -> ucl_object_t * ucl_object_fromdouble(round_float(item->st->weight, 3)), "weight", 0, false); ucl_object_insert_key(obj, - ucl_object_fromdouble(round_float(parent->st->avg_frequency, 3)), + ucl_object_fromdouble(round_float(parent->st->avg_frequency, 6)), "frequency", 0, false); + ucl_object_insert_key(obj, + ucl_object_fromdouble(round_float(::sqrt(parent->st->stddev_frequency), 6)), + "frequency_stddev", 0, false); ucl_object_insert_key(obj, ucl_object_fromint(parent->st->total_hits), "hits", 0, false); @@ -1065,6 +1068,9 @@ auto symcache::counters() const -> ucl_object_t * ucl_object_insert_key(obj, ucl_object_fromdouble(0.0), "frequency", 0, false); + ucl_object_insert_key(obj, + ucl_object_fromdouble(0.0), + "frequency_stddev", 0, false); ucl_object_insert_key(obj, ucl_object_fromdouble(0.0), "hits", 0, false); @@ -1078,8 +1084,11 @@ auto symcache::counters() const -> ucl_object_t * ucl_object_fromdouble(round_float(item->st->weight, 3)), "weight", 0, false); ucl_object_insert_key(obj, - ucl_object_fromdouble(round_float(item->st->avg_frequency, 3)), + ucl_object_fromdouble(round_float(item->st->avg_frequency, 6)), "frequency", 0, false); + ucl_object_insert_key(obj, + ucl_object_fromdouble(round_float(::sqrt(item->st->stddev_frequency), 6)), + "frequency_stddev", 0, false); ucl_object_insert_key(obj, ucl_object_fromint(item->st->total_hits), "hits", 0, false); -- 2.47.3