1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
type cents = int
let digits s = s <> "" && String.for_all (fun c -> c >= '0' && c <= '9') s
let of_string (s : string) : cents option =
let s = String.concat "" (String.split_on_char ',' (String.trim s)) in
let negative, s = if s <> "" && s.[0] = '-' then (true, String.sub s 1 (String.length s - 1)) else (false, s) in
let s = if s <> "" && s.[0] = '$' then String.sub s 1 (String.length s - 1) else s in
let whole, fraction =
match String.split_on_char '.' s with
| [ w ] -> (w, "")
| [ w; f ] -> (w, f)
| _ -> ("", "x")
in
let whole = if whole = "" && fraction <> "" then "0" else whole in
if digits whole && (fraction = "" || (digits fraction && String.length fraction <= 2)) then
let fraction = fraction ^ String.make (2 - String.length fraction) '0' in
let c = (int_of_string whole * 100) + int_of_string fraction in
Some (if negative then -c else c)
else None
let to_string (c : cents) : string =
let a = abs c in
let whole = string_of_int (a / 100) in
let n = String.length whole in
let buf = Buffer.create (n + 4) in
String.iteri
(fun i ch ->
if i > 0 && (n - i) mod 3 = 0 then Buffer.add_char buf ',';
Buffer.add_char buf ch)
whole;
Printf.sprintf "%s%s.%02d" (if c < 0 then "-" else "") (Buffer.contents buf) (a mod 100)
let ones =
[| ""; "one"; "two"; "three"; "four"; "five"; "six"; "seven"; "eight"; "nine"; "ten"; "eleven"; "twelve";
"thirteen"; "fourteen"; "fifteen"; "sixteen"; "seventeen"; "eighteen"; "nineteen" |]
let tens = [| ""; ""; "twenty"; "thirty"; "forty"; "fifty"; "sixty"; "seventy"; "eighty"; "ninety" |]
let below_thousand n =
let hundreds = n / 100 and rest = n mod 100 in
let rest_words =
if rest < 20 then ones.(rest)
else if rest mod 10 = 0 then tens.(rest / 10)
else tens.(rest / 10) ^ "-" ^ ones.(rest mod 10)
in
String.concat " " (List.filter (( <> ) "") [ (if hundreds > 0 then ones.(hundreds) ^ " hundred" else ""); rest_words ])
let rec in_words n =
let scales = [ (1_000_000_000, "billion"); (1_000_000, "million"); (1_000, "thousand") ] in
match List.find_opt (fun (s, _) -> n >= s) scales with
| Some (s, name) ->
let rest = n mod s in
in_words (n / s) ^ " " ^ name ^ if rest > 0 then " " ^ in_words rest else ""
| None -> below_thousand n
let words (c : cents) : string =
let c = abs c in
let dollars = if c / 100 = 0 then "zero" else in_words (c / 100) in
Printf.sprintf "%s%s and %02d/100" (String.uppercase_ascii (String.sub dollars 0 1)) (String.sub dollars 1 (String.length dollars - 1)) (c mod 100)