3

I think I created a working regular expression for what I need. Just wondering if anyone can break it or see a shorter way to write it.

The regular expression should validate the following...

  • Dollar sign optional
  • Negative numbers signified with parenthesis, not a minus
  • If negative, dollar sign should be outside the parenthesis
  • Commas are optional
  • Max number is 999999.99
  • Min number is (999999.99)
  • Decimals do not have to be supplied, but if so, no more than two digits

So here are some examples of valid ones...

9
$9
$0.99
($999,999.99)
(999999)
($999999)
(999,999)
99,999.9

This is what I have come up with:

^\$?(((\d{1,6}(\.\d{1,2})?)|(\d{1,3},\d{3}(\.\d{1,2})?)|\(((\d{1,6}(\.\d{1,2})?)|(\d{1,3},\d{3}(\.\d{1,2})?))\)))$

CORRECTION, my spec was wrong, if the dollar sign is used it must be INSIDE the parenthesis.

Machavity
  • 30,841
  • 27
  • 92
  • 100
gfrobenius
  • 123
  • 1
  • 4
  • 10
  • 2
    1: this might belong on [codereview.se]. 2: What language is the regex in? 3: what i18n considerations have you taken? 4: `1.000,00`, `3.000` – zzzzBov Dec 12 '12 at 21:06
  • You seem to have unnecessary many parenthesis. Do you need all of them for matching groups? – Bergi Dec 12 '12 at 21:07
  • possible duplicate of [What is "The Best" U.S. Currency RegEx?](http://stackoverflow.com/questions/354044/what-is-the-best-u-s-currency-regex) – Andy Lester Dec 12 '12 at 21:27

3 Answers3

11

Here is one shorter alternative (56 chars to your 114), which will work in almost all regex flavors:

^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}(,?\d{3})?(\.\d\d?)?\)?$

Example: http://www.rubular.com/r/qtYHEVzVK7

Explanation:

^                # start of string anchor
\$?              # optional '$'
(?=              # only match if inner regex can match (lookahead)
   \(.*\)          # both '(' and ')' are present
   |               # OR
   [^()]*$         # niether '(' or ')' are present
)                # end of lookaheand
\(?              # optional '('
\d{1,3}          # match 1 to 3 digits
(,?\d{3})?       # optionally match another 3 digits, preceeded by an optional ','
(\.\d\d?)?       # optionally match '.' followed by 1 or 2 digits
\)?              # optional ')'
$                # end of string anchor
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
3

Given your examples, the following regular expression will work:

/^(\$?(?(?=\()(\())\d+(?:,\d+)?(?:\.\d+)?(?(2)\)))$/gm

(note: flags and delimiters are language dependent)

This regex sets an unnecessary backreference merely to save regex-length. You can ignore the second backreference. If this is intolerable the expression will become quite a bit longer.

Have a look here: http://regex101.com/r/fH3lV1

nhahtdh
  • 55,989
  • 15
  • 126
  • 162
Firas Dib
  • 2,743
  • 19
  • 38
3

You can express "between one and six digits; comma before the last three digits is optional" a bit more tersely as \d{1,3}(,?\d{3})?. This also allows you to include only two copies of (\.\d{1,2})?: one for positive and one for negative, instead of one for positive-without-comma, one for positive-with-comma, etc.

Also, \d{1,2} can be shortened slightly to \d\d?, though I'm not sure if that's an improvement.

So, barring some notation like (?(1)) to test if a backreference is set, here's the shortest version I see:

^(\$?\d{1,3}(,?\d{3})?(\.\d\d?)?|\(\$?\d{1,3}(,?\d{3})?(\.\d\d?)?\))$

One perhaps-undesirable aspect of your regex, and of this one, is that they will allow something like $00,012.7, even though no one uses leading zeroes that way. You can address that by requiring the first digit to be nonzero, and then adding a special case to handle $0 and (0.12) and so on:

^(\$?(0|[1-9]\d{0,2}(,?\d{3})?)(\.\d\d?)?|\(\$?(0|[1-9]\d{0,2}(,?\d{3})?)(\.\d\d?)?\))$

Edited to add: using a lookahead assertion like F.J suggests in his/her answer, the latter can be shortened to:

^(?!\(.*[^)]$|[^(].*\)$)\(?\$?(0|[1-9]\d{0,2}(,?\d{3})?)(\.\d\d?)?\)?$
ruakh
  • 175,680
  • 26
  • 273
  • 307
  • I forgot about leading zeros. But your 2nd regexp example doesn't seem to run when tested, is it missing a parenthesis or something? – gfrobenius Dec 12 '12 at 21:49
  • Dang it, my spec was wrong, if the dollar sign is used it must be INSIDE the parenthesis. – gfrobenius Dec 12 '12 at 22:04
  • @gfrobenius: Re: second example: It works in Perl. I'm betting that ColdFusion doesn't like the `\d{,2}` notation; I'll change it to `\d{0,2}`. Re: dollar sign inside parentheses: no problem, I'll fix that, too. – ruakh Dec 12 '12 at 22:12