数をフランス語の文字列表現に変換する (0-9223372036854775807)

「数をフランス語の文字列表現に変換する (0-999999)」の続きです。

0から999999までの数をフランス語の文字列表現に変換する Scheme コードを再掲します。

(define fst17s
  #("zéro" "un" "deux" "trois" "quatre" "cinq" "six" "sept" "huit" "neuf"
    "dix" "onze" "douze" "treize" "quatorze" "quinze" "seize"))

(define mul10s #("" "dix" "vingt" "trente" "quarante" "cinquante" "soixante"))

(define (convert<17 n) (vector-ref fst17s n))
(define convert<10 convert<17)

(define (convert<70 n)
  (if (< n 17) (convert<17 n)
    (let ((qth (vector-ref mul10s (quotient n 10))) (r (modulo n 10)))
      (case r ((0) qth) ((1) (string-append qth " et un"))
              (else (string-append qth "-" (convert<10 r)))))))

(define convert<20 convert<70)

(define (convert<80 n)
  (if (< n 70) (convert<70 n)
    (string-append (vector-ref mul10s 6) (if (= n 71) " et " "-")
                   (convert<20 (- n 60)))))

(define (convert<100 n . singular?)
  (define name-of-80 (string-append (convert<10 4) "-" (convert<70 20)))

  (cond ((< n 80) (convert<80 n))
        ((= n 80) (if (and (pair? singular?) (car singular?)) name-of-80
                    (string-append name-of-80 "s")))
        (else (string-append name-of-80 "-" (convert<20 (- n 80))))))

(define (convert<1000 n . singular?)
  (define (conv<100 n) (apply convert<100 n singular?))
  (define (convert-q*100 q)
    (if (= q 1) "cent" (string-append (convert<10 q) " cent")))

  (if (< n 100) (conv<100 n)
    (let ((q (quotient n 100)) (r (modulo n 100)))
      (if (= r 0)
        (if (or (and (pair? singular?) (car singular?)) (= q 1))
          (convert-q*100 q)
          (string-append (convert-q*100 q) "s"))
        (string-append (convert-q*100 q) " " (conv<100 r))))))

(define (convert<10^6 n)
  (if (< n 1000) (convert<1000 n)
    (let* ((q (quotient n 1000)) (r (modulo n 1000))
           (str-q*1000 (if (= q 1)
                         "mille"
                         (string-append (convert<1000 q #t) " mille"))))
      (if (= r 0) str-q*1000 (string-append str-q*1000 " " (convert<1000 r))))))

これまでに出てきた数詞(fst17s の要素、mul10s の要素、cent、mille)と "million" という名詞を組み合わせて、109-1までの数を表すことができます。

106-1までの変換はすでにあります。106から109-1までの数の表し方の基本的な形式は、「変換したい数を106で割った商を表す言葉」、「"million" または "millions"」、「変換したい数を106で割った余りを表す言葉」の3つをこの順に並べるというものです。
2つめは、商が1のとき "million"、2以上のとき "millions" になります*1
3つめは、余りが0のときは置かれません*2

"million" は名詞なので、数詞のときのような複雑なルールはありません。
106で割った商が1になるような言葉も「変換したい数を106で割った商を表す言葉」である "un" から始まり、いきなり "million" から始まったりしません。
後ろに数詞が続くかどうかと無関係に、商が1なら "million"、2以上なら "millions" です。
また、直前の "vingts" や "cents" から "s" が消えるというルールもありません。

このあと "milliard"、"billion"、"trillion" という名詞が出てきます。

では、109-1以下の数を変換するプロシージャを定義します。

(define %10^6 1000000)
(define (convert<10^9 n)
  (if (< n %10^6) (convert<10^6 n)
    (let* ((q (quotient n %10^6)) (r (modulo n %10^6))
           (str-q*10^6 (string-append (convert<1000 q)
                                      (if (= q 1) " million" " millions"))))
      (if (= r 0) str-q*10^6 (string-append str-q*10^6 " " (convert<10^6 r))))))

109-1の次の数である109は "un milliard" です。数詞と "million"、"milliard" を組み合わせて、1012-1までの数を表すことができます。

1012-1以下の数を変換するプロシージャを定義します(convert<10^9 とほとんど同じです)。

(define %10^9 1000000000)
(define (convert<10^12 n)
  (if (< n %10^9) (convert<10^9 n)
    (let* ((q (quotient n %10^9)) (r (modulo n %10^9))
           (str-q*10^9 (string-append (convert<1000 q)
                                      (if (= q 1) " milliard" " milliards"))))
      (if (= r 0) str-q*10^9 (string-append str-q*10^9 " " (convert<10^9 r))))))

上の3つのコードを Schemoid に貼り付けて、テストしてみます。
こちらのサイトで(学生への)問題用とされている数と「ややこしさの極致」とされている数を変換させてみました。

(convert<10^12 1080080)
(convert<10^12 80080080)
(convert<10^12 200200200)
(convert<10^12 202202202)
(convert<10^12 1001001100)

"un million quatre-vingt mille quatre-vingts"
"quatre-vingts millions quatre-vingt mille quatre-vingts"
"deux cents millions deux cent mille deux cents"
"deux cent deux millions deux cent deux mille deux cent deux"
"un milliard un million mille cent"

問題ないですね。

1012-1の次の数である1012は "un billion" です。数詞と、"million"、"milliard"、"billion" を組み合わせて、1018-1までの数を表すことができます。正書法では、"mille millions" や "mille milliards" のような書き方はしません*3が、1015は "mille billions" です。

1018-1以下の数を変換するプロシージャを定義しましょう(convert<10^9 や convert<10^12 とほとんど同じですが、商を変換するプロシージャは convert<1000 ではなく convert<10^6 です。1000000000000に L がついているのは JScheme の仕様上の理由*4からであり、他の処理系で動かす場合にはこの部分を変える必要があるかもしれません)。

(define %10^12 1000000000000L)
(define (convert<10^18 n)
  (if (< n %10^12) (convert<10^12 n)
    (let* ((q (quotient n %10^12)) (r (modulo n %10^12))
           (str-q*10^12 (string-append (convert<10^6 q)
                                       (if (= q 1) " billion" " billions"))))
      (if (= r 0) str-q*10^12
        (string-append str-q*10^12 " " (convert<10^12 r))))))

続けて1024-1以下の数を変換するプロシージャを定義しましょう*5

(define %10^18 1000000000000000000L)
(define (convert<10^24 n)
  (if (< n %10^18) (convert<10^18 n)
    (let* ((q (quotient n %10^18)) (r (modulo n %10^18))
           (str-q*10^18 (string-append (convert<10^6 q)
                                       (if (= q 1) " trillion" " trillions"))))
      (if (= r 0) str-q*10^18
        (string-append str-q*10^18 " " (convert<10^18 r))))))

小ネタ

convert<10^9 や convert<10^12 では106や109で割った商を変換するのに convert<1000 を用いていますが、上限が1000より大きな変換プロシージャならどれを使っても構いません。例えば convert<10^6 でも問題なく動きます。
そこで本当に convert<10^6 を使うことにすると*6、上の4つのプロシージャはマクロを使って簡潔に定義することができます*7

(define-macro (define-convert sup inf delim)
  (let ((inf-val (string->symbol (string-append "%" inf)))
        (inf-proc (string->symbol (string-append "convert<" inf))))
    `(define (,(string->symbol (string-append "convert<" sup)) n)
       (if (< n ,inf-val) (,inf-proc n)
         (let* ((q (quotient n ,inf-val)) (r (modulo n ,inf-val))
                (str-q*10^k (string-append (convert<10^6 q)
                                           " " ,delim (if (= q 1) "" "s"))))
           (if (= r 0) str-q*10^k
             (string-append str-q*10^k " " (,inf-proc r))))))))

(define %10^6 1000000)
(define %10^9 1000000000)
(define %10^12 1000000000000L)
(define %10^18 1000000000000000000L)

(define-convert "10^9" "10^6" "million")
(define-convert "10^12" "10^9" "milliard")
(define-convert "10^18" "10^12" "billion")
(define-convert "10^24" "10^18" "trillion")

例えば(macroexpand '(define-convert "10^9" "10^6" "million"))して、改行やインデントを入れてみると(展開されすぎて外側がよくわからないことになっていますが)convert<10^9 の最初の定義とほぼ同じものが得られました*8

(set! convert<10^9
  (set-procedure-name!
    (lambda (n)
      (if (< n %10^6) (convert<10^6 n)
        (let* ((q (quotient n %10^6)) (r (modulo n %10^6))
               (str-q*10^k (string-append (convert<10^6 q) " " "million"
                                          (if (= q 1) "" "s"))))
          (if (= r 0) str-q*10^k
            (string-append str-q*10^k " " (convert<10^6 r)))))
      ) 'convert<10^9))

(小ネタ終わり)

JScheme の整数型の上限まで変換プロシージャができたので、無印な convert を定義します。

(define convert convert<10^24)

convert は旧正書法による表現を返します。今度は新正書法の変換プロシージャを定義しましょう。新正書法では、すべての語が "-" で繋がれます。convert の結果のスペースをハイフンに書き替えるだけで新正書法による表現が得られます*9

(use-module "elf/iterate.scm")
(define (space->hyphen s)
  (list->string (map* (lambda (c) (if (char-whitespace? c) #\- c)) s)))
(define (convert1990 n) (space->hyphen (convert n)))

ここまでのコードをまとめます*10

(define fst17s
  #("zéro" "un" "deux" "trois" "quatre" "cinq" "six" "sept" "huit" "neuf"
    "dix" "onze" "douze" "treize" "quatorze" "quinze" "seize"))

(define mul10s #("" "dix" "vingt" "trente" "quarante" "cinquante" "soixante"))

(define (convert<17 n) (vector-ref fst17s n))
(define convert<10 convert<17)

(define (convert<70 n)
  (if (< n 17) (convert<17 n)
    (let ((qth (vector-ref mul10s (quotient n 10))) (r (modulo n 10)))
      (cond ((= r 0) qth) ((= r 1) (string-append qth " et un"))
            (else (string-append qth "-" (convert<10 r)))))))

(define convert<20 convert<70)

(define (convert<80 n)
  (if (< n 70) (convert<70 n)
    (string-append (vector-ref mul10s 6) (if (= n 71) " et " "-")
                   (convert<20 (- n 60)))))

(define (convert<100 n . singular?)
  (define name-of-80 (string-append (convert<10 4) "-" (convert<70 20)))

  (cond ((< n 80) (convert<80 n))
        ((= n 80) (if (and (pair? singular?) (car singular?)) name-of-80
                    (string-append name-of-80 "s")))
        (else (string-append name-of-80 "-" (convert<20 (- n 80))))))

(define (convert<1000 n . singular?)
  (define (conv<100 n) (apply convert<100 n singular?))
  (define (convert-q*100 q)
    (if (= q 1) "cent" (string-append (convert<10 q) " cent")))

  (if (< n 100) (conv<100 n)
    (let ((q (quotient n 100)) (r (modulo n 100)))
      (if (= r 0)
        (if (or (and (pair? singular?) (car singular?)) (= q 1))
          (convert-q*100 q)
          (string-append (convert-q*100 q) "s"))
        (string-append (convert-q*100 q) " " (conv<100 r))))))

(define (convert<10^6 n)
  (if (< n 1000) (convert<1000 n)
    (let* ((q (quotient n 1000)) (r (modulo n 1000))
           (str-q*1000 (if (= q 1)
                         "mille"
                         (string-append (convert<1000 q #t) " mille"))))
      (if (= r 0) str-q*1000 (string-append str-q*1000 " " (convert<1000 r))))))

(define %10^6 1000000)
(define (convert<10^9 n)
  (if (< n %10^6) (convert<10^6 n)
    (let* ((q (quotient n %10^6)) (r (modulo n %10^6))
           (str-q*10^6 (string-append (convert<1000 q)
                                      (if (= q 1) " million" " millions"))))
      (if (= r 0) str-q*10^6 (string-append str-q*10^6 " " (convert<10^6 r))))))

(define %10^9 1000000000)
(define (convert<10^12 n)
  (if (< n %10^9) (convert<10^9 n)
    (let* ((q (quotient n %10^9)) (r (modulo n %10^9))
           (str-q*10^9 (string-append (convert<1000 q)
                                      (if (= q 1) " milliard" " milliards"))))
      (if (= r 0) str-q*10^9 (string-append str-q*10^9 " " (convert<10^9 r))))))

(define %10^12 1000000000000L)
(define (convert<10^18 n)
  (if (< n %10^12) (convert<10^12 n)
    (let* ((q (quotient n %10^12)) (r (modulo n %10^12))
           (str-q*10^12 (string-append (convert<10^6 q)
                                       (if (= q 1) " billion" " billions"))))
      (if (= r 0) str-q*10^12
        (string-append str-q*10^12 " " (convert<10^12 r))))))

(define %10^18 1000000000000000000L)
(define (convert<10^24 n)
  (if (< n %10^18) (convert<10^18 n)
    (let* ((q (quotient n %10^18)) (r (modulo n %10^18))
           (str-q*10^18 (string-append (convert<10^6 q)
                                       (if (= q 1) " trillion" " trillions"))))
      (if (= r 0) str-q*10^18
        (string-append str-q*10^18 " " (convert<10^18 r))))))

(define convert convert<10^24)

(use-module "elf/iterate.scm")
(define (space->hyphen s)
  (list->string (map* (lambda (c) (if (char-whitespace? c) #\- c)) s)))
(define (convert1990 n) (space->hyphen (convert n)))

263-1未満の数を20個ランダムに選んで変換してみます。

(define nums '(94409306437765120L 189724449738631169L 913751038158116864L 1027398546617633792L 1136783708594936836L 2302488749307554820L 2561710153035968512L 2606365600194276352L 2768331595429106696L 3171386267040874503L 3401263846392744962L 4190166744010341379L 4534679118436768776L 4539314953599455237L 4689859166385398793L 5324729142378048516L 5954852475502087181L 6207770618918668303L 7196198135367503880L 8338637271413235729L))
(for-each (lambda (n) (display n) (display ":") (newline) (display (convert n)) (newline) (newline)) nums)

94409306437765120L:
quatre-vingt-quatorze mille quatre cent neuf billions trois cent six milliards quatre cent trente-sept millions sept cent soixante-cinq mille cent vingt

189724449738631169L:
cent quatre-vingt-neuf mille sept cent vingt-quatre billions quatre cent quarante-neuf milliards sept cent trente-huit millions six cent trente et un mille cent soixante-neuf

913751038158116864L:
neuf cent treize mille sept cent cinquante et un billions trente-huit milliards cent cinquante-huit millions cent seize mille huit cent soixante-quatre

1027398546617633792L:
un trillion vingt-sept mille trois cent quatre-vingt-dix-huit billions cinq cent quarante-six milliards six cent dix-sept millions six cent trente-trois mille sept cent quatre-vingt-douze

1136783708594936836L:
un trillion cent trente-six mille sept cent quatre-vingt-trois billions sept cent huit milliards cinq cent quatre-vingt-quatorze millions neuf cent trente-six mille huit cent trente-six

2302488749307554820L:
deux trillions trois cent deux mille quatre cent quatre-vingt-huit billions sept cent quarante-neuf milliards trois cent sept millions cinq cent cinquante-quatre mille huit cent vingt

2561710153035968512L:
deux trillions cinq cent soixante et un mille sept cent dix billions cent cinquante-trois milliards trente-cinq millions neuf cent soixante-huit mille cinq cent douze

2606365600194276352L:
deux trillions six cent six mille trois cent soixante-cinq billions six cents milliards cent quatre-vingt-quatorze millions deux cent soixante-seize mille trois cent cinquante-deux

2768331595429106696L:
deux trillions sept cent soixante-huit mille trois cent trente et un billions cinq cent quatre-vingt-quinze milliards quatre cent vingt-neuf millions cent six mille six cent quatre-vingt-seize

3171386267040874503L:
trois trillions cent soixante et onze mille trois cent quatre-vingt-six billions deux cent soixante-sept milliards quarante millions huit cent soixante-quatorze mille cinq cent trois

3401263846392744962L:
trois trillions quatre cent un mille deux cent soixante-trois billions huit cent quarante-six milliards trois cent quatre-vingt-douze millions sept cent quarante-quatre mille neuf cent soixante-deux

4190166744010341379L:
quatre trillions cent quatre-vingt-dix mille cent soixante-six billions sept cent quarante-quatre milliards dix millions trois cent quarante et un mille trois cent soixante-dix-neuf

4534679118436768776L:
quatre trillions cinq cent trente-quatre mille six cent soixante-dix-neuf billions cent dix-huit milliards quatre cent trente-six millions sept cent soixante-huit mille sept cent soixante-seize

4539314953599455237L:
quatre trillions cinq cent trente-neuf mille trois cent quatorze billions neuf cent cinquante-trois milliards cinq cent quatre-vingt-dix-neuf millions quatre cent cinquante-cinq mille deux cent trente-sept

4689859166385398793L:
quatre trillions six cent quatre-vingt-neuf mille huit cent cinquante-neuf billions cent soixante-six milliards trois cent quatre-vingt-cinq millions trois cent quatre-vingt-dix-huit mille sept cent quatre-vingt-treize

5324729142378048516L:
cinq trillions trois cent vingt-quatre mille sept cent vingt-neuf billions cent quarante-deux milliards trois cent soixante-dix-huit millions quarante-huit mille cinq cent seize

5954852475502087181L:
cinq trillions neuf cent cinquante-quatre mille huit cent cinquante-deux billions quatre cent soixante-quinze milliards cinq cent deux millions quatre-vingt-sept mille cent quatre-vingt-un

6207770618918668303L:
six trillions deux cent sept mille sept cent soixante-dix billions six cent dix-huit milliards neuf cent dix-huit millions six cent soixante-huit mille trois cent trois

7196198135367503880L:
sept trillions cent quatre-vingt-seize mille cent quatre-vingt-dix-huit billions cent trente-cinq milliards trois cent soixante-sept millions cinq cent trois mille huit cent quatre-vingts

8338637271413235729L:
huit trillions trois cent trente-huit mille six cent trente-sept billions deux cent soixante et onze milliards quatre cent treize millions deux cent trente-cinq mille sept cent vingt-neuf


()

263-1 を変換してみます。

(convert 9223372036854775807L)

"neuf trillions deux cent vingt-trois mille trois cent soixante-douze billions trente-six milliards huit cent cinquante-quatre millions sept cent soixante-quinze mille huit cent sept"

正書法だと次のようになります。

(convert1990 9223372036854775807L)

"neuf-trillions-deux-cent-vingt-trois-mille-trois-cent-soixante-douze-billions-trente-six-milliards-huit-cent-cinquante-quatre-millions-sept-cent-soixante-quinze-mille-huit-cent-sept"

JScheme の整数型で表せる最大の値まで変換できたのでここまでにしておきます。

続きます。

*1:単数のときは単数形になり複数のときは複数形になるというただそれだけの話なのですが、"vingt" や "cent" のときはもう少しややこしかった…。

*2:置くものがありません。"zéro" があるか。

*3:正書法でなくても "mille milliards" とは書かない気がする。

*4:JScheme では64ビット整数をリテラルで書くときはLという接尾辞が必要です。

*5:実際には、1019の時点で JScheme では桁溢れを起こします。

*6:別にしなくてもパラメータを1個増やせばいいだけですが。

*7:JScheme には define-syntax がありません。伝統的なマクロならあります。

*8:商の変換プロシージャが違うのは最初に書いたとおり。あとは変数名が違うことと "million" の前後のスペースや "s" が別立てになってること以外に違いはありませんね。

*9:map* は総称的な map で、リスト以外にも文字列やベクターなどにも適用できます。ただし JScheme 限定です。ほかの処理系なら string-map とかもっと便利なのがあるかもしれません。なければ s に string->list を適用して普通の map を使うのが簡単だと思います(走査回数が増えますがもともと速度は必要とされていないと思うので)。

*10:convert<70 の case を cond に変えました。JScheme では (case r ((0) …) …) と書くと r が 0L のとき、(0) で拾えないのです。変な仕様だと思いますが、0が exact で0Lが(なぜか)inexact なので一貫してはいます。