1 /// A few helper functions for manipulating English words. Extremely basic.
2 module arsd.english;
3 
4 /++
5 	Given a non-one `count` argument, will attempt to return the plural version of `word`. Only handles basic cases. If count is one, simply returns the word.
6 
7 	I originally wrote this for cases like `You have {n} {messages|plural(n)}` in web templates.
8 +/
9 string plural(int count, string word, string pluralWord = null) {
10 	if(count == 1 || word.length == 0)
11 		return word; // it isn't actually plural
12 
13 	if(pluralWord !is null)
14 		return pluralWord;
15 
16 	switch(word[$ - 1]) {
17 		case 's':
18 		case 'a', 'e', 'i', 'o', 'u':
19 			return word ~ "es";
20 		case 'f':
21 			return word[0 .. $-1] ~ "ves";
22 		case 'y':
23 			return word[0 .. $-1] ~ "ies";
24 		default:
25 			return word ~ "s";
26 	}
27 }
28 
29 /// Given an integer, tries to write out the long form number. For example, -5 becomes "negative five".
30 string numberToEnglish(long number) {
31 	string word;
32 	if(number == 0)
33 		return "zero";
34 
35 	if(number < 0) {
36 		word = "negative";
37 		number = -number;
38 	}
39 
40 	while(number) {
41 		if(number < 100) {
42 			if(number < singleWords.length) {
43 				word ~= singleWords[cast(int) number];
44 				break;
45 			} else {
46 				auto tens = number / 10;
47 				word ~= tensPlaceWords[cast(int) tens];
48 				number = number % 10;
49 				if(number)
50 					word ~= "-";
51 			}
52 		} else if(number < 1_000) {
53 			auto hundreds = number / 100;
54 			word ~= onesPlaceWords[cast(int) hundreds] ~ " hundred";
55 			number = number % 100;
56 			if(number)
57 				word ~= " and ";
58 		} else if(number < 1_000_000) {
59 			auto thousands = number / 1_000;
60 			word ~= numberToEnglish(thousands) ~ " thousand";
61 			number = number % 1_000;
62 			if(number)
63 				word ~= ", ";
64 		} else if(number < 1_000_000_000) {
65 			auto millions = number / 1_000_000;
66 			word ~= numberToEnglish(millions) ~ " million";
67 			number = number % 1_000_000;
68 			if(number)
69 				word ~= ", ";
70 		} else if(number < 1_000_000_000_000) {
71 			auto n = number / 1_000_000_000;
72 			word ~= numberToEnglish(n) ~ " billion";
73 			number = number % 1_000_000_000;
74 			if(number)
75 				word ~= ", ";
76 		} else if(number < 1_000_000_000_000_000) {
77 			auto n = number / 1_000_000_000_000;
78 			word ~= numberToEnglish(n) ~ " trillion";
79 			number = number % 1_000_000_000_000;
80 			if(number)
81 				word ~= ", ";
82 		} else {
83 			import std.conv;
84 			return to!string(number);
85 		}
86 	}
87 
88 	return word;
89 }
90 
91 unittest {
92 	assert(numberToEnglish(1) == "one");
93 	assert(numberToEnglish(5) == "five");
94 	assert(numberToEnglish(13) == "thirteen");
95 	assert(numberToEnglish(54) == "fifty-four");
96 	assert(numberToEnglish(178) == "one hundred and seventy-eight");
97 	assert(numberToEnglish(592) == "five hundred and ninety-two");
98 	assert(numberToEnglish(1234) == "one thousand, two hundred and thirty-four");
99 	assert(numberToEnglish(10234) == "ten thousand, two hundred and thirty-four");
100 	assert(numberToEnglish(105234) == "one hundred and five thousand, two hundred and thirty-four");
101 }
102 
103 enum onesPlaceWords = [
104 	"zero",
105 	"one",
106 	"two",
107 	"three",
108 	"four",
109 	"five",
110 	"six",
111 	"seven",
112 	"eight",
113 	"nine",
114 ];
115 
116 enum singleWords = onesPlaceWords ~ [
117 	"ten",
118 	"eleven",
119 	"twelve",
120 	"thirteen",
121 	"fourteen",
122 	"fifteen",
123 	"sixteen",
124 	"seventeen",
125 	"eighteen",
126 	"nineteen",
127 ];
128 
129 enum tensPlaceWords = [
130 	null,
131 	"ten",
132 	"twenty",
133 	"thirty",
134 	"forty",
135 	"fifty",
136 	"sixty",
137 	"seventy",
138 	"eighty",
139 	"ninety",
140 ];
141 
142 /*
143 void main() {
144 	import std.stdio;
145 	foreach(i; 3433000 ..3433325)
146 	writeln(numberToEnglish(i));
147 }
148 */