From 42a02903582d856135b7584694e3800c9a755175 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Thu, 15 Oct 2026 07:53:11 +0300 Subject: [PATCH] perf(grid): no regex in formatNum, no comma copy in selection stats GridSelectionCalcEngine.formatNum compiled two RegExp objects and ran two replace passes for every stat on every selection change, and compute made a replaceAll(',', '') copy for every selected cell. formatNum walks the code units from the end to drop trailing zeros and a dangling dot. compute strips commas only from cells that contain one. Output is unchanged. Closes #1531 Refs #1520 --- .../workspace/grid_selection_calc_engine.dart | 17 ++++++-- .../grid_selection_calc_format_test.dart | 40 +++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 test/features/workspace/grid_selection_calc_format_test.dart diff --git a/lib/features/workspace/grid_selection_calc_engine.dart b/lib/features/workspace/grid_selection_calc_engine.dart index b56d1291..dd9ba8c9 100644 --- a/lib/features/workspace/grid_selection_calc_engine.dart +++ b/lib/features/workspace/grid_selection_calc_engine.dart @@ -131,9 +131,11 @@ abstract final class GridSelectionCalcEngine { distinctSet.add(trimmed); - // Try parsing numeric values (stripping commas if present) - final normalized = trimmed.replaceAll(',', ''); - final parsed = double.tryParse(normalized); + // Try parsing numeric values (stripping commas if present). Most cells + // have none, so the stripped copy is only made when there is one. + final parsed = double.tryParse( + trimmed.contains(',') ? trimmed.replaceAll(',', '') : trimmed, + ); if (parsed != null && !parsed.isNaN && !parsed.isInfinite) { numericList.add(parsed); sum += parsed; @@ -260,6 +262,13 @@ abstract final class GridSelectionCalcEngine { } // Limit decimal precision to 4 decimal places final formatted = val.toStringAsFixed(4); - return formatted.replaceAll(RegExp(r'0+$'), '').replaceAll(RegExp(r'\.$'), ''); + // Drop the trailing zeros, then one trailing dot (`3.1400` -> `3.14`, + // `10.0000` -> `10`): an index walk, no regex (#1531). + var end = formatted.length; + while (end > 0 && formatted.codeUnitAt(end - 1) == 0x30) { + end--; + } + if (end > 0 && formatted.codeUnitAt(end - 1) == 0x2E) end--; + return end == formatted.length ? formatted : formatted.substring(0, end); } } diff --git a/test/features/workspace/grid_selection_calc_format_test.dart b/test/features/workspace/grid_selection_calc_format_test.dart new file mode 100644 index 00000000..45e2cc0d --- /dev/null +++ b/test/features/workspace/grid_selection_calc_format_test.dart @@ -0,0 +1,40 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/features/workspace/grid_selection_calc_engine.dart'; + +void main() { + group('GridSelectionCalcEngine.formatNum (#1531)', () { + test('trims trailing zeros and a dangling dot', () { + expect(GridSelectionCalcEngine.formatNum(null), '-'); + expect(GridSelectionCalcEngine.formatNum(3.0), '3'); + expect(GridSelectionCalcEngine.formatNum(3.14), '3.14'); + expect(GridSelectionCalcEngine.formatNum(0.5), '0.5'); + expect(GridSelectionCalcEngine.formatNum(-2.5), '-2.5'); + expect(GridSelectionCalcEngine.formatNum(1 / 3), '0.3333'); + expect(GridSelectionCalcEngine.formatNum(0.1 + 0.2), '0.3'); + expect(GridSelectionCalcEngine.formatNum(1234.5678), '1234.5678'); + }); + + test('values that round to a whole number keep their integer zeros', () { + expect(GridSelectionCalcEngine.formatNum(2.5000001), '2.5'); + expect(GridSelectionCalcEngine.formatNum(10.00001), '10'); + expect(GridSelectionCalcEngine.formatNum(100.00004), '100'); + expect(GridSelectionCalcEngine.formatNum(0.00001), '0'); + }); + }); + + group('GridSelectionCalcEngine.compute with thousands separators (#1520)', () { + test('commas are stripped only for parsing, distinct keeps the text', () { + final stats = GridSelectionCalcEngine.compute( + ['1,000', ' 2,500.5 ', 'NULL', '', 'abc', '3', '3'], + ); + expect(stats.totalCount, 7); + expect(stats.nullCount, 2); + expect(stats.distinctCount, 4); // '1,000', '2,500.5', 'abc', '3' + expect(stats.numericCount, 4); + expect(stats.sum, 3506.5); + expect(stats.min, 3); + expect(stats.max, 2500.5); + expect(stats.median, closeTo(501.5, 1e-9)); // (3 + 1000) / 2 + }); + }); +}