From fdf80d51ee78d32f0e1a0430c72f687076c94537 Mon Sep 17 00:00:00 2001 From: Art Leonard Date: Fri, 17 Jul 2026 15:05:10 -0700 Subject: [PATCH] perf: avoid metric normalization on symbol inputs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2e2e074c-44dc-42cd-978f-31553d7876dd --- src/Humanizer/MetricNumeralExtensions.cs | 46 ++++++++++++++++++--- tests/Humanizer.Tests/MetricNumeralTests.cs | 7 ++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Humanizer/MetricNumeralExtensions.cs b/src/Humanizer/MetricNumeralExtensions.cs index a2f31c58a..bd67ccef6 100644 --- a/src/Humanizer/MetricNumeralExtensions.cs +++ b/src/Humanizer/MetricNumeralExtensions.cs @@ -255,7 +255,11 @@ static string CleanRepresentation(string input) ArgumentNullException.ThrowIfNull(input); input = input.Trim(); - input = ReplaceNameBySymbol(input); + if (input.Length > 0 && char.IsLetter(input[^1]) && !HasSymbolSuffix(input)) + { + input = ReplaceNameBySymbol(input); + } + if (input.Length == 0 || input.IsInvalidMetricNumeral()) { throw new ArgumentException("Empty or invalid Metric string.", nameof(input)); @@ -264,6 +268,10 @@ static string CleanRepresentation(string input) return input.Replace(" ", string.Empty); } + static bool HasSymbolSuffix(string input) => + UnitPrefixes.ContainsKey(input[^1]) && + (input.Length == 1 || !char.IsLetter(input[^2])); + /// /// Build a number from a metric representation or from a number /// @@ -303,9 +311,15 @@ static double BuildMetricNumber(string input, char last) /// /// Metric representation with a name or a symbol /// A metric representation with a symbol - static string ReplaceNameBySymbol(string input) => - UnitPrefixes.Aggregate(input, (current, unitPrefix) => - current.Replace(unitPrefix.Value.Name, unitPrefix.Key.ToString())); + static string ReplaceNameBySymbol(string input) + { + foreach (var unitPrefix in UnitPrefixes) + { + input = input.Replace(unitPrefix.Value.Name, GetSymbolText(unitPrefix.Key)); + } + + return input; + } /// /// Build a Metric representation of the number. @@ -496,9 +510,31 @@ static string GetUnitText(char symbol, MetricNumeralFormats? formats) } } - return symbol.ToString(); + return GetSymbolText(symbol); } + static string GetSymbolText(char symbol) => + symbol switch + { + 'k' => "k", + 'M' => "M", + 'G' => "G", + 'T' => "T", + 'P' => "P", + 'E' => "E", + 'Z' => "Z", + 'Y' => "Y", + 'm' => "m", + 'μ' => "μ", + 'n' => "n", + 'p' => "p", + 'f' => "f", + 'a' => "a", + 'z' => "z", + 'y' => "y", + _ => symbol.ToString() + }; + /// /// Check if a Metric representation is out of the valid range. /// diff --git a/tests/Humanizer.Tests/MetricNumeralTests.cs b/tests/Humanizer.Tests/MetricNumeralTests.cs index b92082b6c..3842e5892 100644 --- a/tests/Humanizer.Tests/MetricNumeralTests.cs +++ b/tests/Humanizer.Tests/MetricNumeralTests.cs @@ -20,6 +20,13 @@ public class MetricNumeralTests public void FromMetric(double expected, string input) => Assert.Equal(expected, input.FromMetric()); + [Theory] + [InlineData(1E6, "1 mega")] + [InlineData(1E-18, "1a")] + [InlineData(1E-18, "1 a")] + public void FromMetricDistinguishesNamesFromSymbolSuffixes(double expected, string input) => + Assert.Equal(expected, input.FromMetric()); + [Theory] [InlineData("")] [InlineData(" ")]