Postanowiłem spróbować napisać wyrażenia regularne dla liczb zmiennoprzecinkowych. Materiał, na podstawie którego piszę, to: https://en.cppreference.com/w/cpp/language/floating_literal
Od razu disclaimer: Piszę to nie bezpośrednio na podstawie standardu, a jedynie na podstawie materiału napisanego na jego podstawie (https://en.cppreference.com/) – toteż należałoby jeszcze bezpośrednio sprawdzić zgodność ze standardem (standard dostępny jest za opłatą: https://isocpp.org/std/the-standard).
UPDATE: Poza standardem dostępnym za opłatą, jest jeszcze szkic (draft) jego najnowszej wersji, dostępny za darmo na GitHubie: https://github.com/cplusplus/draft. Są to źródła napisane w LaTeXu, dlatego też żeby zobaczyć wynikowy dokument PDF w pełnej okazałości, należy zainstalować dodatkowe oprogramowanie oraz zbudować projekt (wszystko wyjaśnione w README).
Żeby mieć jakiś track record, będę systematycznie edytował ten post (zamiast pisać wszystko od razu). Postanowiłem podzielić całość na takie warianty, jakie są wyróżnione na cppreference.com. Wyrażenia regularne sprawdzam za pomocą narzędzia https://regexr.com/. Wspomagam się kompilatorem C++ dostępnym online: https://ideone.com/ oraz stroną https://www.wolframalpha.com/. Wszystkie wyrażenia regularne były sprawdzane dla składni PCRE. Przyjąłem, że nie będę uwzględniać opcjonalnego separatora liczb `
(backquote, backgrave lub backtick, a po polsku, hm... nie znam odpowiednika nazw używanych w informatyce; w językoznawstwie, jako symbol akcentu, nazywa się "grawis").
Wariant (1)
Na pierwszy ogień idzie wariant o następującej składni: digit-sequence exponent suffix(optional).
Wyrażenie regularne:
[0-9]+[eE][+-]?[0-9]+[fFlL]?
gdzie:
-
[0-9]+
to digit sequence,
-
[eE][+-]?[0-9]+
to exponent (zapisywany w tzw. scientific E-notation),
-
[fFlL]?
to suffix(optional).
To wyrażenie z sukcesem dopasowuje następujące liczby:
0e5 0e5f 0e5F 0e5l 0e5L
0E5 0E5f 0E5F 0E5l 0E5L
0e+5 0e+5f 0e+5F 0e+5l 0e+5L
0E+5 0E+5f 0E+5F 0E+5l 0E+5L
(Swoją drogą, wszystkie podane liczby są równe 0).
W razie wątpliwości – tak, w tym wariancie nie ma kropki (co mnie trochę niepokoi – dlaczego C++ miałby uznawać liczbę bez części dziesiętnej za zmiennoprzecinkową?).
Wariant (2)
Na drugi ogień idzie wariant o następującej składni: digit-sequence . exponent(optional) suffix(optional).
Wyrażenie regularne:
[0-9]+\.([eE][+-]?[0-9]+)?[fFlL]?
gdzie:
-
[0-9]+
to digit sequence,
-
\.
to separator (tutaj – kropka dziesiętna; dla liczby w reprezentacji szesnastkowej oczywiście nie dziesiętna),
-
([eE][+-]?[0-9]+)?
to exponent(optional),
-
[fFlL]?
to suffix(optional).
To wyrażenie z sukcesem dopasowuje następujące liczby:
0. 0.f 0.F 0.l 0.L
0.e5 0.e5f 0.e5F 0.e5l 0.e5L
0.E5 0.E5f 0.E5F 0.E5l 0.E5L
0.e+5 0.e+5f 0.e+5F 0.e+5l 0.e5L
0.E+5 0.E+5f 0.E+5F 0.E+5l 0.E+5L
Pamiętaj, że poza składnią C++ – w zależności od przyjętej notacji w danym języku – separatorem może być także przecinek lub inny znak, występujący w piśmie, w którym zapisywany jest dany język. Zobacz także: https://en.wikipedia.org/wiki/Radix_point (Ponownie, wszystkie liczby są równe 0).
Wariant (3)
Następnie idzie wariant o takiej składni: digit-sequence(optional) . digit-sequence exponent(optional) suffix(optional).
Wyrażenie regularne:
[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[fFlL]?
gdzie:
-
[0-9]*
to digit-sequence(optional),
-
\.
to separator,
-
[0-9]+
to digit-sequence,
-
([eE][+-]?[0-9]+)?
to exponent(optional),
-
[fFlL]?
to suffix(optional).
To wyrażenie z sukcesem dopasowuje następujące liczby:
.7 .7f .7F .7l .7L
.7e5 .7e5f .7e5F .7e5l .7e5L
.7E5.7E5f .7E5F .7E5l .7E5L
.7e+5 .7e+5f .7e+5F .7e+5l .7e5L
.7E+5 .7E+5f .7E+5F .7E+5l .7E+5L
0.7 0.7f 0.7F 0.7l 0.7L
0.7e5 0.7e5f 0.7e5F 0.7e5l 0.7e5L
0.7E5 0.7E5f 0.7E5F 0.7E5l 0.7E5L
0.7e+5 0.7e+5f 0.7e+5F 0.7e+5l 0.7e5L
0.7E+5 0.7E+5f 0.7E+5F 0.7E+5l 0.7E+5L
Ten wariant różni się od poprzedniego (nr 2) tym, że poprzedni uwzględniał tylko liczby bez części ułamkowej, a z wymaganą częścią całkowitą (tj. np. "5."), podczas gdy w tym wariancie część ułamkowa musi być, a część całkowita jest opcjonalna (tj. np. "5." lub "5.2"). (Podane przykładowe liczby z "E-notacją" są równe 70000, a bez niej są równe 0.7).
Wariant (4)
W tym wariancie liczba jest w systemie szesnastkowym, a jej składnia odpowiada składni z wariantu nr 1 (który opisuje liczbę w systemie dziesiętnym). Liczba ma taką składnię: 0x | 0X hex-digit-sequence exponent suffix(optional).
Wyrażenie regularne:
0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fFlL]?
gdzie:
-
0[xX]
to tzw. przedrostek szesnastkowy (ang. hexadecimal prefix; tutaj możliwe powody, dlaczego składa się akurat ze znaków "0" oraz "x"),
-
[0-9a-fA-F]+
to hex-digit-sequence,
-
[pP][+-]?[0-9]+
to exponent,
-
[fFlL]?
to suffix(optional).
To wyrażenie z sukcesem dopasowuje następujące liczby:
0xA9p3 0xA9p3f 0xA9p3F 0xA9p3l 0xA9p3L
0xA9P3 0xA9P3f 0xA9P3F 0xA9P3l 0xA9P3L
0xA9p+3 0xA9p+3f 0xA9p+3F 0xA9p+3l 0xA9p+3L
0xA9P+3 0xA9P+3f 0xA9P+3F 0xA9P+3l 0xA9P+3L
0XA9p3 0XA9p3f 0XA9p3F 0XA9p3l 0XA9p3L
0XA9P3 0XA9P3f 0XA9P3F 0XA9P3l 0XA9P3L
0XA9p+3 0XA9p+3f 0XA9p+3F 0XA9p+3l 0XA9p+3L
0XA9P+3 0XA9P+3f 0XA9P+3F 0XA9P+3l 0XA9P+3L
Wartość części exponent – tj. [0-9]+
w tym wariancie – nie może być szesnastkowa (tj. musi składać się z ciągu cyfr od 0 do 9, czyli właśnie [0-9]+
). Uwaga: ta wartość jest traktowana jako wartość binarna (a nie np. szesnastkowa). Cytat z https://en.cppreference.com/w/cpp/language/floating_literal:
For a hexadecimal floating literal, the significand is interpreted as a hexadecimal rational number, and the digit-sequence of the exponent is interpreted as the integer power of 2 to which the significand has to be scaled.
double d = 0x1.2p3; // hex fraction 1.2 (decimal 1.125) scaled by 2^3, that is 9.0
## Wariant (5)
W tym wariancie liczba jest w systemie szesnastkowym, a jej składnia odpowiada składni z wariantu nr 2 (który opisuje liczbę w systemie dziesiętnym) – z tym, że _exponent_ **nie** jest opcjonalny. Liczba ma taką składnię: _**0x** | **0X** hex-digit-sequence . exponent suffix(optional)_.
Wyrażenie regularne:
0[xX][0-9a-fA-F]+.[pP][+-]?[0-9]+[fFlL]?
gdzie:
- `0[xX]` to przedrostek szesnastkowy,
- `[0-9a-fA-F]+` to _hex-digit-sequence_,
- `\.` to separator,
- `[pP][+-]?[0-9]+` to _exponent_,
- `[fFlL]?` to _suffix(optional)_.
To wyrażenie z sukcesem dopasowuje następujące liczby:
0x0.p5 0x0.p5f 0x0.p5F 0x0.p5l 0x0.p5L
0x0.P5 0x0.P5f 0x0.P5F 0x0.P5l 0x0.P5L
0x0.p+5 0x0.p+5f 0x0.p+5F 0x0.p+5l 0x0.p5L
0x0.P+5 0x0.P+5f 0x0.P+5F 0x0.P+5l 0x0.P+5L
0X0.p5 0X0.p5f 0X0.p5F 0X0.p5l 0X0.p5L
0X0.P5 0X0.P5f 0X0.P5F 0X0.P5l 0X0.P5L
0X0.p+5 0X0.p+5f 0X0.p+5F 0X0.p+5l 0X0.p5L
0X0.P+5 0X0.P+5f 0X0.P+5F 0X0.P+5l 0X0.P+5L
## Wariant (6)
W tym wariancie liczba jest w systemie szesnastkowym, a jej składnia odpowiada składni z wariantu nr 3 (który opisuje liczbę w systemie dziesiętnym) – z tym, że _exponent_ **nie** jest opcjonalny. Liczba ma taką składnię: _**0x** | **0X** hex-digit-sequence(optional) . hex-digit-sequence exponent suffix(optional)_.
Wyrażenie regularne:
0[xX][0-9a-fA-F]*.[0-9a-fA-F]+[pP][+-]?[0-9]+[fFlL]?
gdzie:
- `0[xX]` to przedrostek szesnastkowy,
- `[0-9a-fA-F]*` to _hex-digit-sequence(optional)_,
- `\.` to separator,
- `[0-9a-fA-F]+` to _hex-digit-sequence_,
- `[pP][+-]?[0-9]+` to _exponent_,
- `[fFlL]?` to _suffix(optional)_.
To wyrażenie z sukcesem dopasowuje następujące liczby:
0x.D7p5 0x.D7p5f 0x.D7p5F 0x.D7p5l 0x.D7p5L
0x.D7P5 0x.D7P5f 0x.D7P5F 0x.D7P5l 0x.D7P5L
0x.D7p+5 0x.D7p+5f 0x.D7p+5F 0x.D7p+5l 0x.D7p5L
0x.D7P+5 0x.D7P+5f 0x.D7P+5F 0x.D7P+5l 0x.D7P+5L
0x0.D7p5 0x0.D7p5f 0x0.D7p5F 0x0.D7p5l 0x0.D7p5L
0x0.D7P5 0x0.D7P5f 0x0.D7P5F 0x0.D7P5l 0x0.D7P5L
0x0.D7p+5 0x0.D7p+5f 0x0.D7p+5F 0x0.D7p+5l 0x0.D7p5L
0x0.D7P+5 0x0.D7P+5f 0x0.D7P+5F 0x0.D7P+5l 0x0.D7P+5L
## Uwagi końcowe
Mam nadzieję, że nigdzie się nie pomyliłem. **Jest to bardzo prawdopodobne**, bo to jednak analiza leksykalna – coś, co powinien wykonywać komputer (tj. lekser). Jeśli kto zauważy błąd, proszę o wskazanie miejsca.