diff --git a/internal/eval/numeric.go b/internal/eval/numeric.go index 71bbc4f..4af0bef 100644 --- a/internal/eval/numeric.go +++ b/internal/eval/numeric.go @@ -74,7 +74,14 @@ func classifyMatch(text string, start, end int) (numberCandidate, bool) { return numberCandidate{}, false // e.g. the "64" in "LP64" } if end < len(text) && isWordByte(text[end]) { - return numberCandidate{}, false // e.g. "24bytes" with no separator + // Permit a multiplier suffix ("64x" = 64 times): a single trailing + // x/X with nothing word-like after it. Hex ("0x2A") and identifiers + // ("float64x2") keep a word byte after the x and stay rejected. + isMultiplier := (text[end] == 'x' || text[end] == 'X') && + (end+1 >= len(text) || !isWordByte(text[end+1])) + if !isMultiplier { + return numberCandidate{}, false // e.g. "24bytes" with no separator + } } compound := start > 0 && text[start-1] == '-' && start-2 >= 0 && isWordByte(text[start-2]) diff --git a/internal/eval/numeric_test.go b/internal/eval/numeric_test.go index c5afcb6..b225a5c 100644 --- a/internal/eval/numeric_test.go +++ b/internal/eval/numeric_test.go @@ -26,6 +26,9 @@ func TestExtractLastNumber(t *testing.T) { {"platform qualifier with parenthetical acronym", "24 bytes on a 64-bit (LP64) platform.", 24, false}, {"digits glued into an identifier via underscore", "sizeof = 24 bytes on x86_64", 24, false}, {"digits glued into an identifier via hyphen (bogus sign)", "sizeof = 24 bytes on x86-64", 24, false}, + {"multiplier suffix accepted", "The compression ratio is 64x.", 64, false}, + {"multiplier suffix at end of text", "speedup: 8x", 8, false}, + {"hex literal never a multiplier", "the flag is 0x2A so the answer is 42", 42, false}, {"hyphen-compound number accepted as a last resort", "It is a 24-byte struct.", 24, false}, // Documented limitation, not a regression: the heuristic has no // notion of "the total" vs. "a breakdown component" and returns