Help us understand the problem. What is going on with this article?

数値とマッチする正規表現

初めに

正規表現を使って数値を検索する機会は多くあると思います。
単純な数値でしたら簡単に検索できます。
しかし、実数や指数表記のものを対象にした場合は、細かい所が上手く行かない事があります。
そこで数値を検索する正規表現をまとめてみたいと思いました。

ここではjavascriptで実行できるものを考えています。
また括弧は全て(?:) 非格納括弧(non-capturing group) を使います。

一覧

  • [+-]?\d+
    整数
  • [+-]?(?:0[xX])?[0-9a-fA-F]+[hH]?
    16進数
  • [+-]?\d+(?:\.\d+)?
    実数(0省略を許さない)
  • [+-]?(?:\d+\.?\d*|\.\d+)
    実数(0省略を許す)
  • [+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?
    e表記の指数表記(0省略を許さない)
  • [+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?
    e表記の指数表記(0省略を許す)
  • [+-]?\d+(?:\.\d+)?(?:(?:[eE][+-]?\d+)|(?:\*10\^[+-]?\d+))?
    10の累乗およびe表記の指数表記(0省略を許さない)
  • [+-]?(?:\d+\.?\d*|\.\d+)(?:(?:[eE][+-]?\d+)|(?:\*10\^[+-]?\d+))?
    10の累乗およびe表記の指数表記(0省略を許す)

整数

  • \d+
  • [+-]?\d+

まずは整数を考えてみます。
これは、単純です。
[0-9]+ もしくは \d+ です。
[0-9]\d は同じことなので、以下では全て \d を使います。
ただ数値には、符号が付くことが有りますのでそのことを考慮しなければいけない事もあります。
そこで符号を考慮した整数用正規表現は、[+-]?\d+ 辺りになるでしょう。

16進数

  • [0-9a-fA-F]+
  • [+-]?(?:0[xX])?[0-9a-fA-F]+[hH]?

次に16進数を考えてみます。
一番単純なものは、[0-9a-fA-F]+ でしょう。
場合によっては接頭辞が付くことが有ります。
接頭辞を考慮すると、(?:0[xX])?[0-9a-fA-F]+ となります。
接尾辞が付くこともあります。
その場合は、[0-9a-fA-F]+[hH]? です。
さらに符号を考慮し全てに対応したものは、[+-]?(?:0[xX])?[0-9a-fA-F]+[hH]? となります。
大文字や小文字のみマッチさせたい場合は、適宜修正してください。

実数

  • \d+(?:\.\d+)?: 符号なし0省略なし
  • \d+\.?\d*|\.\d+: 符号なし0省略あり
  • [+-]?\d+(?:\.\d+)?: 符号あり0省略なし
  • [+-]?(?:\d+\.?\d*|\.\d+): 符号あり0省略あり
  • [+-]?\d*\.?\d+(?:(?<!\.\d+)\.)?: 符号あり0省略あり別解

一番単純なものは、\d+(?:\.\d+)? 辺りでしょうか。
これは以下のようなものにマッチします。

//マッチするもの
123
1.23
12.3

ただし、以下のように小数点の前後の0を省略した場合は、小数点を含みません。

//一部がマッチ
.123
123.

大部分はこれで十分でしょうが、時には0の省略を含めたい事もあります。
そこで改良を考えてみます。
すぐに思いつくのは、 \d*\.?\d* あたりでしょうか。

//マッチするもの
123
1.23
12.3
.123
123.
.

だだ、これだと小数点単独のものと空文字列にもマッチしてしまう事が問題です。
余計なものは、手作業やプログラムで取り去ってしまうというのも1つの方法です。
また、\b\d*\.?\d*\b^\d*\.?\d*$ としても問題ないようでしたら実用には耐えられそうです。
ですが、やはり使い勝手が悪いので他のも考えてみます。
\d+\.?\d* にしてみました。

//マッチするもの
123
1.23
12.3
123.

小数点の後の0を省略したものが、マッチ出来るようになりました。
しかし、小数点の前の0を省略したものとはマッチしません。
そこで \d*\.?\d+ も試してみます。

//マッチするもの
123
1.23
12.3
.123

小数点の前の0を省略したものが、マッチ出来るようになりました。
かわりに、小数点の後の0を省略したものが出来なくなりました。
1つずつでは、足りないので2つ合わせてみます。
単純に組み合わせて、\d+\.?\d*|\d*\.?\d+としてみます。

//マッチするもの
123
1.23
12.3
123.
.123

目的のものが、マッチするようになりました。
しかし、効率も良くない(はず)の上に、美しくない正規表現です。
四苦八苦して改良したところ、筆者の能力では作れるものの中では、\d+\.?\d*|\.\d+ が一番効率が良いようです。
もう少し綺麗な記述をしたいところですが、上にあげたものを使い分ければ事足りることでしょう。

実数別解

@juthaDDA さんのコメントから
[+-]?(?:\d*\.)?\d+(?:(?<!(\.\d+))\.\d*)?
という表現を教えて頂きました。
マッチするものは、

123
1.23
12.3
.123
123.

と結果に遜色はありません。
また、

.123.
123.456.789

と文字列が来た場合は、
上は、.123 にマッチし、
下は、123.456.789 にマッチします。
どう解釈してよいか分からない文字列の場合でも、一番良いマッチになっていると思います。

ただ、若干冗長な表現になってしまっています。
そこで失礼ながら改良を試みます。

後半部分の (?:(?<!(\.\d+))\.\d*)? は、

123.

. のみにマッチしています。
つまり、この部分は、. のみを対象とした正規表現に変換出来そうです。
少し改良して (?:(?<!(\.\d+))\.)? としてもマッチする内容に変わりはないようです。
また、前半部分の (?:(?<!(\.\d+))\.\d*)? 部分からは、括弧を省略できます。
以上をふまえて、

  • [+-]?\d*\.?\d+(?:(?<!\.\d+)\.)?

を別解として採用させていただきます。

更なる考察

上で改良した正規表現は、JavaScriptで使用するという前提は満たしているのですが、他の言語に移植することを考えると否定後読みに量指定子を使っているのが問題になります。
そこで否定後読みや量指定子を削除することを考えてみます。

ざっくりと削って [+-]?\d*\.?\d+\.? としてみます。
これがマッチするものは

123
1.23
12.3
.123
123.

です。
ただし、

.123.
123.456.789

このような文字列が来た場合は、
上は、.123. にマッチし
下は、123.456.789 にマッチしてしまいます。

そこで、
[+-]?\d*\.?\d+\.?(?!\d)
としてみると、下のマッチが 123.456.789 となり、問題が一部解消されます。
しかし、依然として上のマッチは、.123. なので問題は全て解消されません。

筆者は、否定後読みや量指定子を削除することは出来ませんでした。

指数表記

指数部のみ
- [eE][+-]?\d+
- \*10\^[+-]?\d+
- [eE][+-]?\d+|\*10\^[+-]?\d+

0省略を許さない
- [+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?
- [+-]?\d+(?:\.\d+)?(?:\*10\^[+-]?\d+)?
- [+-]?\d+(?:\.\d+)?(?:(?:[eE][+-]?\d+)|(?:\*10\^[+-]?\d+))?

0省略を許す
- [+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?
- [+-]?(?:\d+\.?\d*|\.\d+)(?:\*10\^[+-]?\d+)?
- [+-]?(?:\d+\.?\d*|\.\d+)(?:(?:[eE][+-]?\d+)|(?:\*10\^[+-]?\d+))?

指数表記には、10の累乗を使ったものとe(E)を使ったものがあります。
まずは、eを使った表記を考えます。
指数表記は、単純に実数の後にe1なとが付いたものになります。
そのため実数とマッチする正規表現に、eと指数部にマッチする正規表現を付ければ良いようです。
eと指数部にマッチするものは、[eE][+-]?\d+ です。
上で求めた実数の正規表現と組み合わせると、[+-]?\d+(?:\.\d+)?[eE][+-]?\d+ が指数表記の正規表現になります。
これだと指数表記のみとのマッチになります。

//マッチするもの
123e1
123e-1
1.23e1
1.23e+1

実数もマッチさせたい場合は、 [+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)? で良いでしょう。

//マッチするもの
123
1.23
12.3
123e1
123E-1
1.23E1
1.23e+1

上記のものは、0の省略を許さないものです。
ここでも0の省略を許すと、[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)? となります。

//マッチするもの
123
1.23
12.3
.123
123.
123e1
123E-1
1.23E1
1.23e+1
.123e-1
123.e1

次に、10の累乗を使った表記を考えます。
10の累乗を使った表記を表す良い方法を知らないので、仮に *10^5 のように表すとします。
そうすると指数部にマッチする正規表現は、 \*10\^[+-]?\d+ となります。
これと実数を表す正規表現を組み合わせると、 [+-]?\d+(?:\.\d+)?(?:\*10\^[+-]?\d+)? あたりがもう一つの指数表記用の正規表現でしょう。

指数部を10累乗表記とe表記どちらにも対応させる事を考えます。
単純に組み合わせると、 [eE][+-]?\d+|\*10\^[+-]?\d+ になります。
((?:[eE]|(?:\*10\^))[+-]?\d+ とも表記できますが、単純にこの部分だけを考えた場合は、効率が悪くなるようです。)
これに実数部分を組み合わせると、[+-]?\d+(?:\.\d+)?(?:(?:[eE][+-]?\d+)|(?:\*10\^[+-]?\d+))? が両対応の正規表現です。
例によって0省略を許す場合は、[+-]?(?:\d+\.?\d*|\.\d+)(?:(?:[eE][+-]?\d+)|(?:\*10\^[+-]?\d+))? となります。

最後に

長々と書いた割には、いまいちな記事になってしまいました。
もっと良い記述があれば教えてもらいたいです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away