4

How can I create a regex that allows whole numbers, decimals, fractions, and fractions with decimals? The string can also have optional text, only at the end. So far I have this:

const re = /^\d*\.?\d*\/?\d*\.?\d*[a-z]*$/gi;

This allows double decimals in a whole number(ie: '23.23.23'), which I do not want. Can I modify this regex to allow two decimal points only if it is separated by a '/'?

Here are some examples that can pass:

  • 23.23/100km
  • 1/3
  • .23km
  • 1.mi
  • 1.2/2.1kg

Some examples that shouldn't pass:

  • 1a3km
  • 12.12.12
  • 1.2.3/12.13km
  • 12km/12.44km
  • Should you be allowing whitespace between the number and the units, in case the data is compliant with the SI way of using a space there? – Andrew Morton May 14 '21 at 19:38

3 Answers3

2

Use

^(?!.*\d+(?:\.\d+){2})\d*\.?\d*\/?\d*\.?\d*[a-z]*$

See proof. This expression disallows three numbers that have a period between one another thanks to (?!.*\d+(?:\.\d+){2}) negative lookahead.

EXPLANATION

--------------------------------------------------------------------------------
  ^                        the beginning of the string
--------------------------------------------------------------------------------
  (?!                      look ahead to see if there is not:
--------------------------------------------------------------------------------
    .*                       any character except \n (0 or more times
                             (matching the most amount possible))
--------------------------------------------------------------------------------
    \d+                      digits (0-9) (1 or more times (matching
                             the most amount possible))
--------------------------------------------------------------------------------
    (?:                      group, but do not capture (2 times):
--------------------------------------------------------------------------------
      \.                       '.'
--------------------------------------------------------------------------------
      \d+                      digits (0-9) (1 or more times
                               (matching the most amount possible))
--------------------------------------------------------------------------------
    ){2}                     end of grouping
--------------------------------------------------------------------------------
  )                        end of look-ahead
--------------------------------------------------------------------------------
  \d*                      digits (0-9) (0 or more times (matching
                           the most amount possible))
--------------------------------------------------------------------------------
  \.?                      '.' (optional (matching the most amount
                           possible))
--------------------------------------------------------------------------------
  \d*                      digits (0-9) (0 or more times (matching
                           the most amount possible))
--------------------------------------------------------------------------------
  \/?                      '/' (optional (matching the most amount
                           possible))
--------------------------------------------------------------------------------
  \d*                      digits (0-9) (0 or more times (matching
                           the most amount possible))
--------------------------------------------------------------------------------
  \.?                      '.' (optional (matching the most amount
                           possible))
--------------------------------------------------------------------------------
  \d*                      digits (0-9) (0 or more times (matching
                           the most amount possible))
--------------------------------------------------------------------------------
  [a-z]*                   any character of: 'a' to 'z' (0 or more
                           times (matching the most amount possible))
--------------------------------------------------------------------------------
  $                        before an optional \n, and the end of the
                           string
Ryszard Czech
  • 18,032
  • 4
  • 24
  • 37
  • 1
    The explanation says `?` is "(optional (matching the most amount possible))", but it actually means "0 or 1 times". – Andrew Morton May 14 '21 at 19:42
  • @AndrewMorton `?` is a greedy quantifier. It does match "the most amount possible". 1 or 0 times. Not 0 or 1 times. `??` is lazy, it matches "0 or 1 times". – Ryszard Czech May 14 '21 at 19:44
  • 1
    This solution does work, thanks! I was trying to use the lookahead method although I was using positive lookahead and did not fit it correctly. – idkwhatimdoing May 14 '21 at 19:49
  • 1
    @RyszardCzech Please see the [MDN documentation for quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Quantifiers). – Andrew Morton May 14 '21 at 19:54
  • @AndrewMorton Very interesting resource, however, I disagree with them. – Ryszard Czech May 14 '21 at 19:56
  • @RyszardCzech They're the people who are writing the software that does the regex (in Firefox), so I think they probably know what they are writing about. If you don't believe them, how about [Microsoft's docs](https://learn.microsoft.com/en-us/dotnet/standard/base-types/quantifiers-in-regular-expressions) or [RexEgg](https://www.rexegg.com/regex-quantifiers.html) or a random [JavaScript tutorial](https://www.javascripttutorial.net/regular-expression-quantifiers/) I found? – Andrew Morton May 14 '21 at 20:02
  • @AndrewMorton Great, next time you meet them, let them know they have a bug in the docs. – Ryszard Czech May 14 '21 at 20:05
  • @RyszardCzech Are you perhaps more familiar with another variety of regex which uses the `?` quantifier to mean something other than `{0,1}`? That is what it means in JavaScript, by definition. I'm just hoping that you can edit the explanation part of your answer to match with the way it actually works in JavaScript so that other people don't read it and get the wrong information. Even the page you linked to as proof says 0 or 1 times. – Andrew Morton May 14 '21 at 20:14
  • I do not want to edit the 100% correct description. You must understand lazy and greedy nature of `?` (1 or 0) and `??` (0 or 1) quantifiers. Have a look at https://stackoverflow.com/a/5583884/11329890. `?` = `{0,1}` = a greedy quantifier, matches the pattern one or zero times, i.e. attempts to find a match at least once. – Ryszard Czech May 14 '21 at 20:27
  • @RyszardCzech No, at *most* one time, not at least once. The "greedy" means that the match, if there is one, is included in the result. – Andrew Morton May 14 '21 at 21:19
  • @RyszardCzech Your "100% correct description" says "matching the most amount possible" without saying that the most amount possible is 1. It is 100% misleading ;) – Andrew Morton May 15 '21 at 20:36
  • 1
    I wonder if this is just a language issue. I can see how one can say that this is correct for `\.?`: "(optional (matching the most amount possible))", but it is quite confusing to native English speakers. Something like this might be clearer: "Matches a single `.` if one `.` is available and nothing if one is not." – Scott Sauyet May 17 '21 at 13:14
1

This should work

^[0-9]*(?:\.[0-9]*)?(?:\/[0-9]*(?:\.[0-9]*)?)?[a-zA-Z]*$
Alberto Lerda
  • 401
  • 2
  • 7
0

The pattern that you tried, consists of all optional parts because all the quantifiers are either ? for optional, or * for 0 or more times. As the / is also optional, you will match 12.12.12

You could make the part with the / part optional without making the / itself optional.

If you want to match at least a digit and prevent matching empty strings you can use a positive lookahead to match a least a digit.

^(?=[^\d\r\n]*\d)\d*\.?\d*(?:\/\d*\.?\d*)?[a-z]*$

Regex demo

The pattern matches

  • ^ Start of string
  • (?=[^\d\r\n]*\d) Assert at least a digit
  • \d*\.?\d* Match optional digits and .
  • (?: Non capture group
    • \/\d*\.?\d* Match / and optional digits and .
  • )? Close non capture group and make it optional
  • [a-z]* Optionally match chars a-z
  • $ End of string

If there should be at least a digit on each side of the / to not match for example ./.

^(?:\d*\.?\d+|\d+\.)(?:\/\d*\.?\d+|\d+\.)?[a-z]*$

The pattern matches

  • ^ Start of string
  • (?:\d*\.?\d+|\d+\.) Match either optional digits, optional . and 1+ digits OR match 1+ digits and .
  • (?: Non capture group
    • \/\d*\.?\d+|\d+\. Match / and the same as the first pattern again
  • )? Close non capture group and make it optional
  • [a-z]* Optionally match chars a-z
  • $ End of string

Regex demo

The fourth bird
  • 154,723
  • 16
  • 55
  • 70