PostgreSQL 18がやってくる(10) Add roman support for to_number function

# はじめに
にゃーん。
楽しい5月連休も今日が最終日。残念ながら明日からは仕事をしなければなりません。

今日は小ネタ。``TO_NUMBER``/``TO_CHAR``でローマ数字をサポートした、というものです。
[平たい顔属](https://dic.pixiv.net/a/%E5%B9%B3%E3%81%9F%E3%81%84%E9%A1%94%E6%97%8F)なので、普段はあまり使わない（映画やゲームのナンバリングくらいでしか使わない？）ローマ数字ですが、PostgreSQLでも今まではサポートしてなかったようです。

# 概要

[Commitfest 2025-01](https://commitfest.postgresql.org/51/)でコミットされた項目である。

|タイトル|[Add roman support for to_number function](https://commitfest.postgresql.org/patch/5221/)|
|:--|:--|
|Topic|Performance|
|Created|2024-08-30 07:22:58|
|Last modified|2025-01-22 20:20:02|
|Emails|[[PATCH] Add roman support for to_number function](https://www.postgresql.org/message-id/flat/CAMWA6ybh4M1VQqpmnu2tfSwO+3gAPeA8YKnMHVADeB=XDEvT_A@mail.gmail.com)|

# PostgreSQL 18での改善内容
PostgreSQL 18では以下の機能が追加された。
* ``TO_NUMBER``関数で、ローマ数字から数値への変換が可能になった。
* ``TO_CHAR``関数で数値からローマ数字への変換が可能になった。

## TO_NUMBER()
ローマ数字から数値に変換する場合には、``TO_NUMBER``の第2引数に``RN``または``FMRN``を指定する。
ただ、``TO_NUMBER``の場合には、ローマ数字文字列をtrimしてから処理するっぽいので、``RN``を指定しても``FMRN``を指定しても効果は変わらないようだ。

```sql
postgres=# SELECT to_number('I', 'RN');
 to_number
-----------
         1
(1 row)

postgres=# SELECT to_number('I', 'FMRN');
 to_number
-----------
         1
(1 row)

postgres=# SELECT to_number('    I      ', 'RN');
 to_number
-----------
         1
(1 row)

postgres=# SELECT to_number('    I      ', 'FMRN');
 to_number
-----------
         1
(1 row)
```

他のローマ数字の例も試してみる（フォーマット指定は``RN``のみ試行）。
```sql
postgres=# SELECT to_number('XVIII','RN');
 to_number
-----------
        18
(1 row)

postgres=# SELECT to_number('xvIIi','RN');
 to_number
-----------
        18
(1 row)

postgres=# SELECT to_number('CCCXXXIV','RN');
 to_number
-----------
       334
(1 row)

postgres=# SELECT to_number('MMMCMXCIX','RN');
 to_number
-----------
      3999
(1 row)
```
ローマ数字のアルファベットは大文字も小文字も（混在しても）指定可能である。

正負記号（``+``や``-``）を頭につけた場合はエラーになる。
数字文字列途中にローマ数字で規定された英文字（``I``,``V``,``X``,``L``,``C``,``D``,``M``）が含まれていた場合には、そこまでの文字列を元に数値への変換を行う。

```sql
postgres=# SELECT to_number('CCCXXXIV','RN');
 to_number
-----------
       334
(1 row)

postgres=# SELECT to_number('-CCCXXXIV','RN');
ERROR:  invalid Roman numeral
postgres=# SELECT to_number('+CCCXXXIV','RN');
ERROR:  invalid Roman numeral
postgres=# SELECT to_number('CCC.XXXIV','RN');
 to_number
-----------
       300
(1 row)

postgres=# SELECT to_number('CCC XXXIV','RN');
 to_number
-----------
       300
(1 row)

```

## TO_CHAR()
数値からローマ数字に変換する場合には、``TO_CHAR``の第2引数に``RN``または``FMRN``を指定する。
* ``RN``を指定した場合は、右詰め文字列となる。
* ``FMRN``を指定した場合、左詰め文字列となる。

```sql
postgres=# SELECT to_char(1, 'RN');
     to_char
-----------------
               I
(1 row)

postgres=# SELECT to_char(1, 'FMRN');
 to_char
---------
 I
(1 row)

postgres=# SELECT to_char(334, 'RN');
     to_char
-----------------
        CCCXXXIV
(1 row)

postgres=# SELECT to_char(334, 'FMRN');
 to_char
----------
 CCCXXXIV
(1 row)

postgres=# SELECT to_char(3999, 'RN');
     to_char
-----------------
       MMMCMXCIX
(1 row)

postgres=# SELECT to_char(3999, 'FMRN');
  to_char
-----------
 MMMCMXCIX
(1 row)

```

## ローマ数字の範囲って？
そういえばローマ数字の範囲ってどうなっている？と思い。Wikpedia記事（[ローマ数字](https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%9E%E6%95%B0%E5%AD%97)）を見てみた。

> 現代の一般的な表記法では、1以上4000未満の数を表すことができる。 

とのこと。

では、``TO_CHAR()``の第1引数に、0、負の数、小数、4000以上の数、を指定したらどうなるのか。

```sql
postgres=# SELECT to_char(0, 'RN');
     to_char
-----------------
 ###############
(1 row)

postgres=# SELECT to_char(-1, 'RN');
     to_char
-----------------
 ###############
(1 row)

postgres=# SELECT to_char(3.4, 'RN');
     to_char
-----------------
             III
(1 row)

postgres=# SELECT to_char(3.5, 'RN');
     to_char
-----------------
              IV
(1 row)

postgres=# SELECT to_char(3999, 'RN');
     to_char
-----------------
       MMMCMXCIX
(1 row)

postgres=# SELECT to_char(4000, 'RN');
     to_char
-----------------
 ###############
(1 row)
```
* 0や負数、4000以上の数字を指定した場合はエラーにならず``###############``という文字列が返却される。
  * ``TO_NUMBER``でフォーマットに誤りが合った場合にはエラーになるが、``TO_CHAR()``ではエラーにならない。これは他の``TO_CHAR()``の流儀に倣ったものなのだろう。
* 小数を指定した場合には、それを``ROUND()``した結果をローマ数字に変換する（実際にコードを確認したわけでなく挙動からの推測）。


# おわりに
大昔、自分も[ksj](https://github.com/nuko-yokohama/ksj)という漢数字型を扱えるジョーク定義型を作ったことがある[^1]ので、こういう変な(?)数値記法を可能にする改善は面白いなあと思っています。

**Make Roma Great Again!**

[^1]: 全然メンテナンスしていないのでたぶん、最近の環境だとビルドできないと思う・・・申し訳ない。