Regex for numbers: integers, decimals and negative signs
There are two number presets and they overlap badly: the integer one finds both halves of a decimal separately, and the float one requires a decimal point. One pattern covers both.
At a glance
- Integer preset
- -?\b\d+\b, with the g flag
- Floating point preset
- -?\d+\.\d+, with the g flag
- Integer preset on 3.14
- returns 3 and 14 as two separate matches
- Float preset on 5
- no match, it requires a decimal point
- Combined form
- -?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?
- Still missed
- a leading decimal point, as in .5, and thousands separators
In detail
The two presets pull in different directions and neither is a general number matcher. The integer pattern has no notion of a decimal point, so scanning "3.14" returns 3 and 14 as two independent matches. The floating point pattern requires digits on both sides of a point, so it finds nothing at all in "5".
The word boundaries in the integer pattern sit in an interesting place: after the optional minus sign rather than before it. That means the sign is included whenever the digits that follow start a word, so scanning "a-7" returns -7 with the sign attached even though a reader would call that a subtraction. Whether that is right depends entirely on whether you are parsing an expression or extracting values.
One combined pattern covers the ordinary cases: an optional sign, one or more digits, an optional fractional part, and an optional exponent. It handles 1, -2.5 and 3e4 in a single pass, which is usually what a scanner over mixed text needs.
It still leaves things out, and the honest list is short but real. A leading decimal point with no integer part is matched only from the digit onward, so .5 yields 5. Thousands separators are not handled at all, and neither is a locale that uses a comma as the decimal mark. Any of those needs to be named in the pattern deliberately rather than assumed.
Examples
"Values: 42, -7, 0, 1000, 3.14 has integer part 3, and -99 is negative."
.match(/-?\b\d+\b/g);
// ["42", "-7", "0", "1000", "3", "14", "3", "-99"]
"Pi is 3.14159, temp is -40.0, speed is 299792458.0 m/s."
.match(/-?\d+\.\d+/g);
// ["3.14159", "-40.0", "299792458.0"]The integer preset returns both halves of 3.14 separately, because the full stop is not part of the pattern.
const num = /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g;
"1 -2.5 3e4 .5 1E-3".match(num);
// ["1", "-2.5", "3e4", "5", "1E-3"]The leading decimal point in .5 is not matched, so the value comes back as 5.
Things that catch people out
- The integer preset splits a decimal into two matches, so counting numbers in text with it overcounts every value that has a fractional part.
- The word boundary sits after the minus sign, so a subtraction such as a-7 yields -7 with the sign attached.
- Neither preset handles a leading decimal point, thousands separators, or a comma as the decimal mark, and each of those has to be added deliberately.
Try it: Regex Tester
Type a pattern and a test string, watch every match highlight as you go, read a plain English breakdown of each token, and check the numbered and named groups it captured.
Regex Tester
LOCAL · Browser-OnlyTest and debug regular expressions with live matching and highlights
Related Regex syntax
All regex syntax →\d: the digit class, and the digits it refuses
In JavaScript \d is exactly [0-9], even with the u flag. Here is why other engines disagree and what to use when you need Unicode digits.
Regex for IPv4 addresses with real octet range checks
The naive four groups of digits pattern accepts 999.999.999.999. Here is the alternation that enforces 0 to 255, and the leading zero case it still allows.
The ? quantifier: optional, and its two other jobs
A question mark after a token makes it optional. The same character also marks a lazy quantifier and opens a special group, which is why patterns look dense.
Regex to extract the text between two delimiters
A lazy quantifier is the usual answer and a negated character class is the better one. Neither can handle nesting, and that limit is worth knowing early.
The g flag: global matching and the lastIndex trap
What the g flag changes in a JavaScript regex, why a reused global pattern skips matches through lastIndex, and when matchAll is the safer choice.
The i flag: case insensitive matching
How the i flag folds case in literals and ranges, what it deliberately leaves alone, and the two non-ASCII characters it starts matching once u is on.
Tools for this
- Regex Tester Free online regex tester - test and debug regular expressions with live matching and highlights
- Regex Playground Regex playground with live match highlighting, named groups, find and replace, and shareable links