nth 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +312 -3
  3. data/lib/nth.rb +1152 -15
  4. data/lib/nth/version.rb +1 -1
  5. data/nth.gemspec +13 -2
  6. metadata +40 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0c10f19b1c301e8ffc6f258130db8ffbd2f8c130
4
- data.tar.gz: 62eaf00edd9b43a9f90fb37fc999e63f716c8265
2
+ SHA256:
3
+ metadata.gz: 4a4fce17c6efcc75cf89e5b36527176f4b802181a2316604ee335d8b5dfbfe0a
4
+ data.tar.gz: 3638a58a6b22aea56b8595b2a0ffa06d95af0920bd9a55bb942bf1fd8521bc60
5
5
  SHA512:
6
- metadata.gz: f0cd825999996083de42303889eda82af352266d63541e31e44b9dd2bd71ef644e6315b65a9f62e4f81e84661c6877e13d3f0303d6661d2d506c50a1d3ca0c91
7
- data.tar.gz: 7583d55d091c35c55122db0edc64eaceb1f79ae99fa4fc7bf8bdfe5f3df899731e77689367c778eec13f188bc1c326446b94416524192d8185941cad795d59e7
6
+ metadata.gz: e922ea67a821019afcffcd1e0f51541bf920255a0b4a2da4bce8055d6904dd401bf5f7df31f5d0865ee231832834dc6fd8115b4bc95ab16dd339c9f8991fbcd1
7
+ data.tar.gz: c7e5ac4703133a07f5619e58f57dcd0471c92538ca23a2995dc8bbd254f02fae753e11c3f9c7ee42f614dd65de6487019b34b028821ef7d7a4468a481b8c36cf
data/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Nth
2
2
 
3
- For now I am reserving the name. The first real release will be version 0.1.0 some day soon.
3
+ The Nth gem is a collection of utilities that use named numbers and ordinals to represent or access data.
4
+ If you starting singing eight quintillion bottles of beer on the wall at the start of the big bang,
5
+ you would still be signing it today!
6
+ The gem supports numbers bigger than a milliquadragintillion (over 3000 zeros).
7
+ A way smaller number septemvigintillion is more than the number of atoms in the Universe; it only has 84 zeros.
8
+ Asside from ridiculously large numbers, there are also some useful utilities using named numbers.
9
+ As an example we can access the `#twelfth` character of a string.
10
+ Cookbook applications might like to use named measurements such as 'five and three quarters'.
4
11
 
5
12
  ## Installation
6
13
 
@@ -20,8 +27,310 @@ Or install it yourself as:
20
27
 
21
28
  ## Usage
22
29
 
23
- It would be fun to access the `nth` character of a String or the `nth` element of an Array using ordinals such as `#third` and `#fifth`.
24
- Another goal is to create an `#to_nth` method on Integer that does this: `5.to_nth == '5th'`. Also, `#to_ordinal` such as `12.to_ordinal == 'twelfth'`.
30
+ The Nth gem has methods that can be used out of the box. Instead you can install these methods to base classes such as `Integer` or `String`.
31
+ This document will first explain how to install various utilities or a subset of available utilities to base classes.
32
+ The last part of the document shows how to use some of the utilities without installing methods to base classes.
33
+ Note that access methods must be installed.
34
+
35
+ ### Access Methods
36
+
37
+ Instead of using the `[]` and `[]=` operators, on `Arrays` or `Strings` we can access elements using ordinals.
38
+ This can also be applied to custom objects provided certain methods have been implemented.
39
+ There are two mechanisms that can applied in order to endow a class with ordinal access.
40
+ The preferred means utilizes a generator that defines a subset of ordinal access methods.
41
+ The other uses the `method_missing` dynamic system of ghost methods which is less performant.
42
+ As you only get to override `method_missing` once, you should be careful that some other library has not already done this.
43
+ Note that both can be used at the same time; this allows for the more common ordinals to provide faster access.
44
+ These access methods must be installed on a base class before they become active.
45
+ Such installation should only be placed in your startup code.
46
+ This is best seen by example as follows:
47
+
48
+ ```ruby
49
+ require 'Nth'
50
+ "".respond_to? :first # false ... ordinal access not installed yet
51
+ "".respond_to? :last # false
52
+
53
+ Nth::install(:AccessOrdinals, String, 5)
54
+ # above installs: #first, #second, #third, #fourth, #fifth, and #last methods
55
+
56
+ str = "This is a test!"
57
+ str.first # 'T'
58
+ str.last # '!'
59
+ str.fourth # 's'
60
+ str.fifth # ' '
61
+ str.sixth # error not defined
62
+ str.fifth = '_' # str == "This_is a test!"
63
+
64
+ # add more ordinals ...
65
+ Nth::install(:AccessOrdinals, String, 25, '.') # '.' is the fill parameter ... ' ' is the default
66
+
67
+ str.methods.include? :twenty_fifth # true
68
+ str.methods.include? :twenty_fifth= # true
69
+
70
+ str = "testing "
71
+ str.twenty_fifth = " 1,2,3" # str == "testing ................ 1,2,3"
72
+
73
+ # add all ordinals using method missing ...
74
+ Nth::install(:AccessAllOrdinals, String, '?')
75
+
76
+ str.methods.include? :thirtieth # false ... not a real method
77
+ str.respond_to? :thirtieth # true ... but will respond to call
78
+
79
+ str.fourtieth = "$" # str == "testing ................ 1,2,3?????????$"
80
+ ```
81
+
82
+ From the above we have the first 25 ordinals defined with actual methods, with the remaining ordinals defined using ghost methods.
83
+ Now if you were to access the ordinal `str.tresvigintillionth = "oops!"`, your computer would run out of memory!
84
+ You can change the fill character by calling: `String.instance_variable_set :@default_insert, ' '`.
85
+ Now we can do the same thing with Arrays as follows:
86
+
87
+ ```ruby
88
+ require 'Nth'
89
+ Nth::install(:AccessOrdinals, Array, 15, "nada") # defaults to nil if no fill specified
90
+ array = [1,2,3]
91
+ array.fifth = 500 # [1, 2, 3, "nada", 500]
92
+ Nth::install(:AccessAllOrdinals, Array, nil)
93
+ array.tenth = "pi" # [1, 2, 3, "nada", 500, nil, nil, nil, nil, "pi"]
94
+ ```
95
+
96
+ From the above we have 15 actual ordinal methods, with the remainder implemented using ghost methods.
97
+ Note that you only get one ghost method per class so be judicious.
98
+ Custom objects call also install ordinal access provided that they implement the following methods:
99
+ `:[]`, `:[]=`, `size`. Note that Hash objects don't behave well as they are not ordered numerically like Array or String objects are.
100
+
101
+ ### Integer Methods
102
+
103
+ Integer methods must be installed before they become available.
104
+ Each method creates a string which represents the number in some way.
105
+ Integer methods are installed as follows: `Nth::install(:IntMethods)`.
106
+ Each method is documented in the following subsections.
107
+
108
+ #### Instance Method `nth`
109
+
110
+ This method creates a string comprising the number followed by a 2-letter suffix.
111
+ Larger numbers are grouped in 3 and delimited by a comma.
112
+ See the examples below:
113
+
114
+ ```ruby
115
+ require 'Nth'
116
+ Nth::install(:IntMethods)
117
+
118
+ 5.nth # "5th"
119
+ 1.nth # "1st"
120
+ 22.nth # "22nd"
121
+ 63.nth # "63rd"
122
+ 11.nth # "11th"
123
+ 0.nth # "0th"
124
+ -12345673.nth # "-12,345,673rd"
125
+ ```
126
+
127
+ #### Instance Method `to_number`
128
+
129
+ This method creates a string name that represents the number.
130
+ See the examples below:
131
+
132
+ ```ruby
133
+ require 'Nth'
134
+ Nth::install(:IntMethods)
135
+
136
+ 123456789.to_number # "one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine"
137
+ 600013417.to_number # "six hundred million thirteen thousand four hundred seventeen"
138
+ (10**105).to_number # "one quattuortrigintillion"
139
+ ```
140
+
141
+ #### Instance Method `to_ordinal`
142
+
143
+ This method creates a string name that represents the ordinal name of the number.
144
+ See the examples below:
145
+
146
+ ```ruby
147
+ require 'Nth'
148
+ Nth::install(:IntMethods)
149
+
150
+ 123456783.to_ordinal # "one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-third"
151
+ 600013412.to_ordinal # "six hundred million thirteen thousand four hundred twelfth"
152
+ (10**105).to_ordinal # "one quattuortrigintillionth"
153
+ ```
154
+
155
+ #### Instance Method `cents_to_dollars`
156
+
157
+ This method creates a string money name that could be used to print checks.
158
+ The integer represents the number of pennies.
159
+ See the examples below:
160
+
161
+ ```ruby
162
+ require 'Nth'
163
+ Nth::install(:IntMethods)
164
+
165
+ 1458312.cents_to_dollars # "fourteen thousand five hundred eighty-three dollars and twelve cents"
166
+ 101.cents_to_dollars # "one dollar and one cent"
167
+ 56.cents_to_dollars # "zero dollars and fifty-six cents"
168
+ 200.cents_to_dollars # "two dollars and zero cents"
169
+ ```
170
+
171
+ #### Instance Method `pence_to_pounds`
172
+
173
+ This method creates a string money name that could be used to print checks.
174
+ The integer represents the number of pence. This is the British version of `cents_to_dollars`.
175
+ See the examples below:
176
+
177
+ ```ruby
178
+ require 'Nth'
179
+ Nth::install(:IntMethods)
180
+
181
+ 1458312.pence_to_pounds # "fourteen thousand five hundred eighty-three pounds and twelve pence"
182
+ 101.pence_to_pounds # "one pound and one penny"
183
+ 56.pence_to_pounds # "zero pounds and fifty-six pence"
184
+ 200.pence_to_pounds # "two pounds and zero pence"
185
+ ```
186
+
187
+ ### Float Methods
188
+
189
+ Float methods must be installed before they become available.
190
+ There are a variety of methods that implement a variety of utilities.
191
+ Float methods are installed as follows: `Nth::install(:FloatMethods)`.
192
+ Each method is documented in the following subsections.
193
+
194
+ #### Instance Method `to_fractional_unit(unit_name, mode = :descriptive, tar=0.01)`
195
+
196
+ This method converts a floating point number to a unit based fraction with a multiplicity of formats.
197
+ The `unit_name` parameter use use the singular noun; the method will pluralized the noun when appropriate.
198
+ The `mode` parameter formats the resulting string; this will be explained by example later.
199
+ The `tar` parameter defines the target resolution of the fraction; it is the percent error of the fraction.
200
+ This routine is most useful for cookbook applications. See example below:
201
+
202
+ ```ruby
203
+ require 'Nth'
204
+ Nth::install(:FloatMethods) # do this once in the initialization section of your code
205
+
206
+ cups = 4.333333
207
+ cups.to_fractional_unit('cup') # "four and one third cups"
208
+ cups.to_fractional_unit('cup', :numeric) # "4 1/3 cups"
209
+ (1.0).to_fractional_unit('cup', :numeric) # "1 cup"
210
+
211
+ # mode :utf uses fraction glyphs if available ... uses digits for whole numbers
212
+ # mode :html uses fraction glyphs via html ampersand semicolon syntax
213
+ ```
214
+
215
+ #### Instance Method `inches_to_feet(mode = :descriptive, denom=16)`
216
+
217
+ This method converts floating point inches to a string comprising feet and inches.
218
+ The parameter `mode` defines how the resulting string is formatted.
219
+ The `denom` parameter specifies the target resolution; for proper tape measure formate, this should be a power of `2`.
220
+ See the examples below:
221
+
222
+ ```ruby
223
+ require 'Nth'
224
+ Nth::install(:FloatMethods)
225
+
226
+ data = 123.4567
227
+ data.inches_to_feet # "ten feet three and seven sixteenth inches"
228
+ data.inches_to_feet(:numeric_long) # "10 feet 3 7/16 inches"
229
+ data.inches_to_feet(:numeric_short) # "10ft 3 7/16in"
230
+ data.inches_to_feet(:numeric_short,32) # "10ft 3 15/32in"
231
+ (13.0).inches_to_feet # "one foot one inch"
232
+ # mode == :tics_utf uses utf tic marks
233
+ # mode == :tics_html same as above but uses html entities to represent tic marks
234
+ # FYI:: love to show you an example, but can't display UTF chars in *.md document
235
+ ```
236
+
237
+ #### Instance Method `inches_to_yards(mode = :descriptive, denom=16)`
238
+
239
+ Similar to `inches_to_feet`, but instead separates number into three fields: yards, feet, inches.
240
+ This uses the same parameters as `inches_to_feet`, except `tics_utf` and `tics_html` are not supported.
241
+
242
+
243
+ #### Instance Method `to_dms(frac_sec==2)`
244
+
245
+ This turns geographical floating point numbers into degrees, minutes, and seconds.
246
+ The routine produces utf chars that include degree glyphs as well as tic marks.
247
+ The parameter `frac_sec` defines the number of displayed fractional digits in the seconds field.
248
+
249
+ ### Nth Module direct methods
250
+
251
+ This section details various methods that can be used without having to perform an installation to a class.
252
+ There are several utility methods that are not documented as they are called by more useful methods.
253
+ There are also methods that are not available in the installed section that have some utility.
254
+
255
+ #### `Nth::pluralize(str)`
256
+
257
+ This seemingly unrelated method is used to pluralize unit names; but it can be used on other nouns as well.
258
+ This routine also handles most exceptions. See the example below:
259
+
260
+ ```ruby
261
+ require 'Nth'
262
+
263
+ Nth::pluralize('foot') # 'feet'
264
+ Nth::pluralize('octopus') # "octopi"
265
+ Nth::pluralize('box') # "boxes"
266
+ Nth::pluralize('wife') # "wives"
267
+ Nth::pluralize('kiss') # "kisses"
268
+ Nth::pluralize('fish') # "fish"
269
+ Nth::pluralize('die') # "dice"
270
+ ```
271
+
272
+ #### `Nth::get_number_name(int, *flags)`
273
+
274
+ This converts an integer to a named number. The `flags` parameter list defines the final string formatting.
275
+ This is best seen by example as follows:
276
+
277
+ ```ruby
278
+ require 'Nth'
279
+ # flags are as follows:
280
+ # :-_ separate each word with an underscore
281
+ # :- use dashes on some word combinations such as: twenty-six
282
+ # :plus_minus prefix number with 'plus' or 'minus' (default 'minus' if negative else '')
283
+ # :positive_negative prefix number with 'positive' or 'negative'
284
+
285
+ Nth::get_number_name(-13453) # "minus thirteen thousand four hundred fifty three"
286
+ Nth::get_number_name(13453, :-) # "thirteen thousand four hundred fifty-three"
287
+ Nth::get_number_name(13453, :_) # "thirteen_thousand_four_hundred_fifty_three"
288
+ Nth::get_number_name(17, :plus_minus) # "plus seventeen"
289
+ Nth::get_number_name(17, :positive_negative) # "positive seventeen"
290
+ Nth::get_number_name(-17, :_, :positive_negative) # "negative_seventeen"
291
+ ```
292
+
293
+ #### `Nth::get_ordinal_name(int, *flags)`
294
+
295
+ This routine uses the same flags as `get_number_name`, but instead produces ordinal names.
296
+ See the example below:
297
+
298
+ ```ruby
299
+ Nth::get_ordinal_name(-13453) # "minus thirteen thousand four hundred fifty third"
300
+ Nth::get_ordinal_name(-17, :_, :positive_negative) "negative_seventeenth"
301
+ ```
302
+
303
+ #### Integer Methods without installation
304
+
305
+ We can access without installation integer methods as follows:
306
+
307
+ ```ruby
308
+ Nth::IntMethods::to_nth_string(12345) # "12,345th"
309
+ Nth::IntMethods::to_nth_string(12345,false) # "12345th"
310
+ Nth::IntMethods::penny_to_dollar(12345) # "one hundred twenty-three dollars and forty-five cents"
311
+ Nth::IntMethods::pence_to_pound(12345) # "one hundred twenty-three pounds and forty-five pence"
312
+ ```
313
+
314
+ #### Float Methods without installation
315
+
316
+ We can access without installation float methods as follows:
317
+
318
+ ```ruby
319
+ Nth::FloatMethods::to_fractional_unit(3.666666, 'cup', :descriptive, 0.01) # "three and two third cups"
320
+ Nth::FloatMethods::to_fractional_unit(3.666666, 'cup', :numeric, 0.01) # "3 2/3 cups"
321
+ # also modes: :utf and :html
322
+
323
+ Nth::FloatMethods::inches_to_feet(14.63953, :descriptive, 64) # "one foot two and forty-one sixty-fourth inches"
324
+ Nth::FloatMethods::inches_to_feet(14.63953, :numeric_long, 64) # "1 foot 2 41/64 inches"
325
+ Nth::FloatMethods::inches_to_feet(14.63953, :numeric_short, 64) # "1ft 2 41/64in"
326
+ # also modes: :tics_utf and :tics_html
327
+
328
+ Nth::FloatMethods::inches_to_yards(136.67134, :descriptive, 16) # "three yards two feet four and eleven sixteenth inches"
329
+ Nth::FloatMethods::inches_to_yards(136.67134, :numeric_long, 16) # "3 yards 2 feet 4 11/16 inches"
330
+ Nth::FloatMethods::inches_to_yards(136.67134, :numeric_short, 16) # "3yd 2ft 4 11/16in"
331
+
332
+ Nth::FloatMethods::to_dms(-121.9361831121, 4) # can't display on *md file, but try yourself!
333
+ ```
25
334
 
26
335
  ## Development
27
336
 
data/lib/nth.rb CHANGED
@@ -1,25 +1,1162 @@
1
1
  require "nth/version"
2
+ require "primitive_wrapper"
2
3
 
3
4
  module Nth
4
- BASE_ORDINALS =
5
- ['zeroth','first','second','third','fourth','fifth','sixth','seventh','eighth','ninth','tenth',
6
- 'eleventh','twelfth','thirteenth','fourteenth','fifteenth','sixteenth','seventeenth','eighteenth','nineteenth']
7
- DEC_ORDINALS =
8
- ['tenth','twentieth', 'thirtieth', 'fourtieth', 'fiftieth', 'sixtieth', 'seventieth', 'eightieth', 'ninetieth', 'hundredth']
9
- EXP_ORDINALS = {100 => 'hundredth', 1000 => 'thousandth', 1000000 => 'millionth', 1000000000 => 'billionth', 1000000000000 => 'trillionth',
10
- 1000000000000000 => 'quadrillionth', 1000000000000000000=> 'quintillionth', 1000000000000000000000 => 'sextillionth',
11
- 1000000000000000000000000 => 'septillionth', 1000000000000000000000000000 => 'octillionth', 1000000000000000000000000000000 => 'nonillionth',
12
- 1000000000000000000000000000000000 => 'decillionth'}
13
- NUMBERS = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
14
- DEC_NUMBERS = ['ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
15
- module Access
16
- def method_missing(*plist, &block)
5
+
6
+ FRACTION_UTF = { "1/2" => '½', "1/4" => '¼', "3/4" => '¾', "1/3" => '', "2/3" => '',
7
+ "1/8" => '', "3/8" => '',"5/8" => '', "7/8" => '',
8
+ "1/5" => '⅕',"2/5" => '⅖', "3/5" => '⅗',"4/5" => '⅘',"1/6" => '⅙', "5/6" => '⅚',"1/9" => '⅑',"1/10" => '⅒'}
9
+ html = {}
10
+
11
+ NUMBERS = {0 => 'zero', 1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five', 6 => 'six', 7 => 'seven', 8 => 'eight', 9 => 'nine', 10 => 'ten',
12
+ 11 => 'eleven', 12 => 'twelve', 13 => 'thirteen', 14 => 'fourteen', 15 => 'fifteen', 16 => 'sixteen', 17 => 'seventeen', 18 => 'eighteen', 19 => 'nineteen',
13
+ 20 => 'twenty', 30 => 'thirty', 40 => 'forty', 50 => 'fifty', 60 => 'sixty', 70 => 'seventy', 80 => 'eighty', 90 => 'ninety',
14
+ 100 => 'hundred',
15
+ '10**3' => 'thousand',
16
+ '10**6' => 'million',
17
+ '10**9' => 'billion',
18
+ '10**12' => 'trillion',
19
+ '10**15' => 'quadrillion',
20
+ '10**18' => 'quintillion',
21
+ '10**21' => 'sextillion',
22
+ '10**24' => 'septillion',
23
+ '10**27' => 'octillion',
24
+ '10**30' => 'nonillion',
25
+ '10**33' => 'decillion',
26
+ '10**36' => 'undecillion',
27
+ '10**39' => 'duodecillion',
28
+ '10**42' => 'tredecillion',
29
+ '10**45' => 'quattuordecillion',
30
+ '10**48' => 'quindecillion',
31
+ '10**51' => 'sexdecillion',
32
+ '10**54' => 'septendecillion',
33
+ '10**57' => 'octodecillion',
34
+ '10**60' => 'novemdecillion', # rule generator makes this novendecillion
35
+ '10**63' => 'vigintillion',
36
+ '10**66' => 'unvigintillion',
37
+ '10**69' => 'duovigintillion',
38
+ '10**72' => 'tresvigintillion',
39
+ '10**75' => 'quattuorvigintillion',
40
+ '10**78' => 'quinquavigintillion',
41
+ '10**81' => 'sesvigintillion',
42
+ '10**84' => 'septemvigintillion',
43
+ '10**87' => 'octovigintillion',
44
+ '10**90' => 'novemvigintillion',
45
+ '10**93' => 'trigintillion',
46
+ '10**96' => 'untrigintillion',
47
+ '10**99' => 'duotrigintillion',
48
+ '10**102' => 'trestrigintillion',
49
+ '10**105' => 'quattuortrigintillion',
50
+ '10**108' => 'quinquatrigintillion',
51
+ '10**111' => 'sestrigintillion',
52
+ '10**114' => 'septentrigintillion',
53
+ '10**117' => 'octotrigintillion',
54
+ '10**120' => 'noventrigintillion',
55
+ '10**123' => 'quadragintillion',
56
+ '10**100' => 'googol',
57
+ '10**3003' => 'millinillion',
58
+ '10**3006' => 'millimillion',
59
+ '10**3009' => 'millibillion',
60
+ '10**3012' => 'millitrillion',
61
+ '10**3015' => 'milliquadrillion',
62
+ '10**3018' => 'milliquintillion',
63
+ '10**3021' => 'millisextillion',
64
+ '10**3024' => 'milliseptillion',
65
+ '10**3027' => 'millioctillion',
66
+ '10**3030' => 'millinonillion',
67
+ '10**3033' => 'millidecillion',
68
+ '10**3036' => 'milliundecillion',
69
+ '10**3039' => 'milliduodecillion',
70
+ '10**3042' => 'millitredecillion',
71
+ '10**3045' => 'milliquattuordecillion',
72
+ '10**3048' => 'milliquindecillion',
73
+ '10**3051' => 'millisexdecillion',
74
+ '10**3054' => 'milliseptendecillion',
75
+ '10**3057' => 'millioctodecillion',
76
+ '10**3060' => 'millinovemdecillion',
77
+ '10**3063' => 'millivigintillion',
78
+ '10**3066' => 'milliunvigintillion',
79
+ '10**3069' => 'milliduovigintillion',
80
+ '10**3072' => 'millitresvigintillion',
81
+ '10**3075' => 'milliquattuorvigintillion',
82
+ '10**3078' => 'milliquinquavigintillion',
83
+ '10**3081' => 'millisesvigintillion',
84
+ '10**3084' => 'milliseptemvigintillion',
85
+ '10**3087' => 'millioctovigintillion',
86
+ '10**3090' => 'millinovemvigintillion',
87
+ '10**3093' => 'millitrigintillion',
88
+ '10**3096' => 'milliuntrigintillion',
89
+ '10**3099' => 'milliduotrigintillion',
90
+ '10**3102' => 'millitrestrigintillion',
91
+ '10**3105' => 'milliquattuortrigintillion',
92
+ '10**3108' => 'milliquinquatrigintillion',
93
+ '10**3111' => 'millisestrigintillion',
94
+ '10**3114' => 'milliseptentrigintillion',
95
+ '10**3117' => 'millioctotrigintillion',
96
+ '10**3120' => 'millinoventrigintillion',
97
+ '10**3123' => 'milliquadragintillion'
98
+ }
99
+
100
+ # might go to just under Duomillinillion, but not until next revision
101
+ # to go higher, need the definitive naming scheme.
102
+
103
+ ORDINALS = {0 => 'zeroth', 1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth', 6 => 'sixth', 7 => 'seventh', 8 => 'eighth', 9 => 'ninth', 10 => 'tenth',
104
+ 11 => 'eleventh', 12 => 'twelfth', 13 => 'thirteenth', 14 => 'fourteenth', 15 => 'fifteenth', 16 => 'sixteenth', 17 => 'seventeenth', 18 => 'eighteenth', 19 => 'nineteenth',
105
+ 20 => 'twentieth', 30 => 'thirtieth', 40 => 'fortieth', 50 => 'fiftieth', 60 => 'sixtieth', 70 => 'seventieth', 80 => 'eightieth', 90 => 'ninetieth',
106
+ 100 => 'hundredth', -1 => 'last'
107
+ }
108
+
109
+ #NUMBERS.each_pair { |key,val| ords[key] = val + "th" if ords[key].nil? }
110
+ #ORDINALS = ords.freeze
111
+
112
+
113
+ ORD_FORMAT_ERROR = "ordinal format error"
114
+ SET_TRE = [90,900,70,700,60,600,10,200] # no need for extra 's'
115
+ SET_NOVE = [90,900] # no need for extra '?'
116
+
117
+ # see:: https://en.wikipedia.org/wiki/Names_of_large_numbers
118
+ PRE_TABLE = {}
119
+ PRE_TABLE["un"] = {:val=>1, :solo=>false, :sfx => [], :if => [], :rpl => [] }
120
+ PRE_TABLE["duo"] = {:val=>2, :solo=>false, :sfx => [], :if => [], :rpl => [] }
121
+ PRE_TABLE["tre"] = {:val=>3, :solo=>false, :sfx => [:s], :if => [:s,:x], :rpl => [] }
122
+ PRE_TABLE["quattuor"] = {:val=>4, :solo=>false, :sfx => [], :if => [], :rpl => [] }
123
+ PRE_TABLE["quinqua"] = {:val=>5, :solo=>false, :sfx => [], :if => [], :rpl => [] }
124
+ PRE_TABLE["se"] = {:val=>6, :solo=>false, :sfx => [:s,:x], :if => [:s,:x], :rpl => [] }
125
+ PRE_TABLE["septe"] = {:val=>7, :solo=>false, :sfx => [:m,:n], :if => [:m,:n],:rpl => [] }
126
+ PRE_TABLE["octo"] = {:val=>8, :solo=>false, :sfx => [], :if => [],:rpl => [] }
127
+ PRE_TABLE["nove"] = {:val=>9, :solo=>false, :sfx => [:m,:n], :if => [:m,:n], :rpl => [] }
128
+
129
+ PRE_TABLE["dec"] = {:val=>10, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n] }
130
+ PRE_TABLE["vigint"] = {:val=>20, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:m, :s] }
131
+ PRE_TABLE["trigint"] = {:val=>30, :solo=>true, :sfx => [:a], :if => [], :rpl => [:a,:i,:n, :s] }
132
+ PRE_TABLE["quadragint"] = {:val=>40, :solo=>true, :sfx => [:a], :if => [], :rpl => [:a,:i,:n, :s] } # quadragintillion ... the 'a' gets dropped here
133
+ PRE_TABLE["quinquagint"] = {:val=>50, :solo=>true, :sfx => [:a], :if => [], :rpl => [:a,:i,:n, :s] } # maybe best if we drop it here, then add, an `:extra` key
134
+ PRE_TABLE["sexagint"] = {:val=>60, :solo=>true, :sfx => [:a], :if => [], :rpl => [:a,:i,:n] }
135
+ PRE_TABLE["septuagint"] = {:val=>70, :solo=>true, :sfx => [:a], :if => [], :rpl => [:a,:i,:n] }
136
+ PRE_TABLE["octogint"] = {:val=>80, :solo=>true, :sfx => [:a], :if => [], :rpl => [:a,:i,:m, :x] }
137
+ PRE_TABLE["nonagint"] = {:val=>90, :solo=>true, :sfx => [:a], :if => [], :rpl => [:a,:i] }
138
+
139
+ PRE_TABLE["cent"] = {:val=>100, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n, :x] }
140
+ PRE_TABLE["ducent"] = {:val=>200, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n] }
141
+ PRE_TABLE["trecent"] = {:val=>300, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n, :s] }
142
+ PRE_TABLE["quadringent"] = {:val=>400, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n, :s] }
143
+ PRE_TABLE["quingent"] = {:val=>500, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n, :s] }
144
+ PRE_TABLE["sescent"] = {:val=>600, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n] }
145
+ PRE_TABLE["septingent"] = {:val=>700, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:n] }
146
+ PRE_TABLE["octingent"] = {:val=>800, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i,:m, :x] }
147
+ PRE_TABLE["nongent"] = {:val=>900, :solo=>true, :sfx => [:i], :if => [], :rpl => [:a,:i] }
148
+
149
+ # no example on how to combine with others ... but here it is:
150
+ # PRE_TABLE["millin"] = {:val=>1000, :solo=>false, :sfx => [:i], :if => [], :rpl => [:a,:i] }
151
+
152
+ # if you can find a reference for larger, then place here ... best so far ...
153
+ # http://answers.wikia.com/wiki/What_number_comes_after_millinillion
154
+ # problem with the above is inconsistency with former system.
155
+
156
+ pes = {}
157
+ pes['sheep']='sheep'
158
+ pes['series']='series'
159
+ pes['species']='species'
160
+ pes['deer']='deer'
161
+ pes['fish']='fish'
162
+ pes['child']='children'
163
+ pes['goose']='geese'
164
+ pes['man']='men'
165
+ pes['woman']='women'
166
+ pes['tooth']='teeth'
167
+ pes['foot']='feet'
168
+ pes['mouse']='mice'
169
+ pes['person']='people'
170
+ pes['ox']='oxen'
171
+ pes['photo']='photos'
172
+ pes['piano']='pianos'
173
+ pes['halo']='halos'
174
+ pes['roof']='roofs'
175
+ pes['belief']='beliefs'
176
+ pes['chef']='chefs'
177
+ pes['chief']='chiefs'
178
+ pes['fez']='fezzes'
179
+ pes['gas']='gasses'
180
+ pes['pion']='pions'
181
+ pes['criterion']='criteria'
182
+ pes['phenomenon']='phenomena'
183
+ pes['canto']='cantos'
184
+ pes['zero']='zeros'
185
+ pes['cello']='cellos'
186
+ pes['louse']='lice'
187
+ pes['die']='dice'
188
+ pes['child']='children'
189
+ pes['penny']='pence'
190
+ pes['person']='people'
191
+ pes['moose']='moose'
192
+ pes['swine']='swine'
193
+ pes['buffalo']='buffalo'
194
+ pes['shrimp']='shrimp'
195
+ pes['trout']='trout'
196
+ pes['aircraft']='aircraft'
197
+ pes['watercraft']='watercraft'
198
+ pes['hovercraft']='hovercraft'
199
+ pes['spacecraft']='spacecraft'
200
+
201
+ PLURAL_EXCPETIONS = pes.freeze
202
+
203
+ # utility methods: the gem pluralize too simple ... maybe you should fix this ...
204
+ # ::TODO:: move to new gem, then update this gem
205
+ def self.pluralize(str)
206
+ str = str.rstrip
207
+ tst = PLURAL_EXCPETIONS[str]
208
+ return tst unless tst.nil?
209
+ ch = str[-1]
210
+ sfx = str[-2..-1]
211
+ if ['ay','ey','oy','uy','iy'].include? sfx
212
+ return str + 's'
213
+ elsif ['is'].include? sfx # analysis analyses
214
+ return str[0..-3] +'es'
215
+ elsif ['us'].include? sfx # focus foci
216
+ return str[0..-3] +'i'
217
+ elsif sfx=='fe'
218
+ return str[0..-3] + 'ves'
219
+ elsif ch=='f'
220
+ return str[0..-2] + 'ves'
221
+ elsif ch=='y'
222
+ return str[0..-2] +'ies'
223
+ elsif ['s','x','z','o'].include? ch
224
+ return str +'es'
225
+ elsif ['ch', 'sh'].include? sfx
226
+ return str +'es'
227
+ elsif ['um'].include? sfx # datum data
228
+ return str[0..-3] +'a'
229
+ #elsif ['on'].include? sfx # criterion criteria <<< more the exception than the rule
230
+ # return str[0..-2] +'a' unless str[-3]=='o' # moon, spoon ... pion peon son won ton
231
+ end
232
+ return str + 's'
233
+ end
234
+
235
+ def self.lookup_prefix(str)
236
+ if (PRE_TABLE.include? str[0..11])
237
+ return [str[0..11], PRE_TABLE[str[0..11]]]
238
+ end
239
+ if (PRE_TABLE.include? str[0..10])
240
+ return [str[0..10], PRE_TABLE[str[0..10]]]
241
+ end
242
+ if (PRE_TABLE.include? str[0..9])
243
+ return [str[0..9], PRE_TABLE[str[0..9]]]
244
+ end
245
+ if (PRE_TABLE.include? str[0..8])
246
+ return [str[0..8], PRE_TABLE[str[0..8]]]
247
+ end
248
+ if (PRE_TABLE.include? str[0..7])
249
+ return [str[0..7], PRE_TABLE[str[0..7]]]
250
+ end
251
+ if (PRE_TABLE.include? str[0..6])
252
+ return [str[0..6], PRE_TABLE[str[0..6]]]
253
+ end
254
+ if (PRE_TABLE.include? str[0..5])
255
+ return [str[0..5], PRE_TABLE[str[0..5]]]
256
+ end
257
+ if (PRE_TABLE.include? str[0..4])
258
+ return [str[0..4], PRE_TABLE[str[0..4]]]
259
+ end
260
+ if (PRE_TABLE.include? str[0..3])
261
+ return [str[0..3], PRE_TABLE[str[0..3]]]
262
+ end
263
+ if (PRE_TABLE.include? str[0..2])
264
+ return [str[0..2], PRE_TABLE[str[0..2]]]
17
265
  end
266
+ if (PRE_TABLE.include? str[0..1])
267
+ return [str[0..1], PRE_TABLE[str[0..1]]]
268
+ end
269
+ return [nil,nil]
18
270
  end
271
+
272
+ def self.calculate_prefix(str) # see: https://en.wikipedia.org/wiki/Names_of_large_numbers
273
+ raise ORD_FORMAT_ERROR unless str.index('ii').nil?
274
+ str = str.dup
275
+ m = 0
276
+ if (str[0..4]=='milli')
277
+ m = 1000
278
+ str = str[5..-1]
279
+ end
280
+ n = 0
281
+ pre, props = lookup_prefix(str)
282
+ raise ORD_FORMAT_ERROR if pre.nil?
283
+ if props[:val] < 10
284
+ n += props[:val]
285
+ if props[:if].empty? # simple case
286
+ str = str[pre.length..-1]
287
+ pre, props = lookup_prefix(str)
288
+ else # tricky case
289
+ str = str[pre.length..-1]
290
+ pre, sfx = lookup_prefix(str)
291
+ if pre.nil?
292
+ xs = str[0]
293
+ str = str[1..-1]
294
+ pre, sfx = lookup_prefix(str)
295
+ raise ORD_FORMAT_ERROR if pre.nil?
296
+ ch = (props[:if] & sfx[:rpl]).first.to_s
297
+ unless ch==xs
298
+ ch = ch.to_sym
299
+ raise ORD_FORMAT_ERROR unless props[:if].include? ch
300
+ raise ORD_FORMAT_ERROR unless props[:sfx].include? xs.to_sym
301
+ end
302
+ else
303
+ raise ORD_FORMAT_ERROR unless (props[:if] & sfx[:rpl]).empty?
304
+ end
305
+ props = sfx
306
+ end
307
+ end
308
+ if pre && (props[:val] < 100)
309
+ str = str[pre.length..-1]
310
+ n += props[:val]
311
+ pre, sfx = lookup_prefix(str)
312
+ if pre.nil?
313
+ ch = str[0]
314
+ str = str[1..-1]
315
+ pre, sfx = lookup_prefix(str)
316
+ unless pre.nil?
317
+ cch = (sfx[:rpl] & props[:sfx]).first.to_s
318
+ raise ORD_FORMAT_ERROR unless cch == ch
319
+ end
320
+ props = sfx
321
+ end
322
+ end
323
+ if pre && (props[:val] < 1000)
324
+ str = str[pre.length..-1]
325
+ n += props[:val]
326
+ pre, sfx = lookup_prefix(str)
327
+ raise ORD_FORMAT_ERROR unless pre.nil? # too many!
328
+ end
329
+ n += m
330
+ raise ORD_FORMAT_ERROR if n <= 10
331
+ n *= 3
332
+ n = 10 ** (n+3)
333
+ return n
334
+ end
335
+
336
+ # this only looks up a single item... need to expand for other prefixes/
337
+ def self.lookup_number(num)
338
+ return eval(NUMBERS.key(num).to_s) if (NUMBERS.values.include? num)
339
+ if ("illion" == num[-6..-1])
340
+ # str = num[0..-7]
341
+ return calculate_prefix(num)
342
+ end
343
+ return nil
344
+ end
345
+
346
+ def self.lookup_ordinal(ordinal)
347
+ return 0 if (ordinal=="last")
348
+ return eval(ORDINALS.key(ordinal).to_s) if (ORDINALS.values.include? ordinal)
349
+ str = ordinal[0..-3]
350
+ return lookup_number(str)
351
+ end
352
+
353
+ def self.num_ord_string_to_ary(str)
354
+ return str.split('_') if str.index(' ').nil? # underscore format
355
+ ary = []
356
+ aaa = str.split ' '
357
+ aaa.each do |elm|
358
+ ary += elm.split '-'
359
+ end
360
+ return ary
361
+ end
362
+
363
+ def self.number_string_to_number(str_ary)
364
+ sum = 0
365
+ psum = 0
366
+ neg = false
367
+ if str_ary.kind_of? String
368
+ ary = num_ord_string_to_ary(str_ary)
369
+ else
370
+ ary = str_ary
371
+ end
372
+ ary.each do |elm|
373
+ if elm == 'minus'
374
+ neg = true
375
+ break
376
+ end
377
+ num = lookup_number(elm)
378
+ return nil if num.nil?
379
+ if num < 1000
380
+ if psum==0
381
+ psum = num
382
+ elsif num > psum
383
+ psum *= num
384
+ else
385
+ psum += num
386
+ end
387
+ else
388
+ sum += num * psum
389
+ psum = 0
390
+ end
391
+ end
392
+ sum += psum
393
+ sum = -sum if neg
394
+ return sum
395
+ end
396
+
397
+ def self.ordinal_string_to_number(str)
398
+ return -1 if str=="last"
399
+ sum = 0
400
+ ary = num_ord_string_to_ary(str)
401
+ nth = ary.pop
402
+ val = lookup_ordinal(nth)
403
+ return nil if val.nil?
404
+ ary.push get_number_name(val).split(' ').last
405
+ num = number_string_to_number(ary)
406
+ return nil if num.nil?
407
+ return num-1
408
+ end
409
+
410
+ ### these may need to be redone ...
411
+ def self.get_n_from_array(ary)
412
+ if("minus"==ary.first)
413
+ n = get_n_from_array(ary[1..-1])
414
+ return nil if n.nil?
415
+ return -n
416
+ end
417
+ a = lookup_number(ary[0])
418
+ if a.nil?
419
+ a = lookup_ordinal(a[0])
420
+ return nil if a.nil?
421
+ raise ORD_FORMAT_ERROR if is ary.size > 1
422
+ return a
423
+ end
424
+ b = lookup_number(ary[1])
425
+ if b.nil?
426
+ b = lookup_ordinal(ary[1])
427
+ raise ORD_FORMAT_ERROR if b.nil?
428
+ raise ORD_FORMAT_ERROR unless ary[2].nil?
429
+ if a > b # forty_fourth
430
+ else
431
+ end
432
+ else
433
+ end
434
+ nil
435
+ end
436
+
437
+ def self.get_n_from_string(ordinal)
438
+ n = lookup_ordinal(ordinal)
439
+ return n unless n.nil?
440
+ return get_n_from_array(ordinal.split('_'))
441
+ end
442
+
443
+
444
+ def self.get_number_struct(int)
445
+ neg = int < 0
446
+ int = -int if neg
447
+ dat = []
448
+ while(int > 0)
449
+ int, triplet = int.divmod 1000
450
+ dat.push triplet
451
+ end
452
+ dat.push neg ? :neg : :pos
453
+ end
454
+
455
+ def self.create_big_number_name(mult)
456
+ pre = ""
457
+ num = (mult-3) / 3
458
+ if (num >= 1000)
459
+ idx = "10**#{mult}"
460
+ rtn = NUMBERS[idx]
461
+ return rtn unless rtn.nil?
462
+ pre = "milli"
463
+ num -= 1000
464
+ raise "Number too big" if num >= 1000 # next revision may address larger numbers
465
+ end
466
+ huns, dec = num.divmod 100
467
+ tens, ones = dec.divmod 10
468
+ str = ""
469
+ unless ones.zero?
470
+ if (1==ones)
471
+ str = "un"
472
+ elsif (2==ones)
473
+ str = "duo"
474
+ elsif (3==ones)
475
+ str = "tre"
476
+ if [2,3,4,5,8].include? tens
477
+ str += 's'
478
+ elsif tens.zero?
479
+ if [1,3,4,5,8].include? huns
480
+ str += 's'
481
+ end
482
+ end
483
+ elsif (4==ones)
484
+ str = "quattuor"
485
+ elsif (5==ones)
486
+ str = "quinqua"
487
+ elsif (6==ones)
488
+ str = "se"
489
+ if [2,3,4,5].include? tens
490
+ str += 's'
491
+ elsif 8==tens
492
+ str += 'x'
493
+ elsif tens.zero?
494
+ if [3,4,5].include? huns
495
+ str += 's'
496
+ elsif [1,8].include? huns
497
+ str += 'x'
498
+ end
499
+ end
500
+ elsif (7==ones)
501
+ str = "septe"
502
+ if [1,8].include? tens
503
+ str += 'm'
504
+ elsif [1,3,4,5,6,7].include? tens
505
+ str += 'n'
506
+ elsif tens.zero?
507
+ if (1..7).include? huns
508
+ str += 'n'
509
+ elsif 8 == huns
510
+ str += 'm'
511
+ end
512
+ end
513
+ elsif (8==ones)
514
+ str = "octo"
515
+ elsif (9==ones)
516
+ str = "nove"
517
+ if [1,8].include? tens
518
+ str += 'm'
519
+ elsif [1,3,4,5,6,7].include? tens
520
+ str += 'n'
521
+ elsif tens.zero?
522
+ if (1..7).include? huns
523
+ str += 'n'
524
+ elsif 8 == huns
525
+ str += 'm'
526
+ end
527
+ end
528
+ end
529
+ end
530
+ unless tens.zero?
531
+ if (1==tens)
532
+ str += 'dec'
533
+ str += 'i' unless huns.zero?
534
+ elsif (2==tens)
535
+ str += 'vigint'
536
+ str += 'i' unless huns.zero?
537
+ elsif (3==tens)
538
+ str += 'trigint'
539
+ str += 'a' unless huns.zero?
540
+ elsif (4==tens)
541
+ str += 'quadragint'
542
+ str += 'a' unless huns.zero?
543
+ elsif (5==tens)
544
+ str += 'quinquagint'
545
+ str += 'a' unless huns.zero?
546
+ elsif (6==tens)
547
+ str += 'sexagint'
548
+ str += 'a' unless huns.zero?
549
+ elsif (7==tens)
550
+ str += 'septuagint'
551
+ str += 'a' unless huns.zero?
552
+ elsif (8==tens)
553
+ str += 'octogint'
554
+ str += 'a' unless huns.zero?
555
+ elsif (9==tens)
556
+ str += 'nonagint'
557
+ str += 'a' unless huns.zero?
558
+ end
559
+ end
560
+ unless huns.zero?
561
+ if (1==huns)
562
+ str += 'cent'
563
+ elsif (2==huns)
564
+ str += 'ducent'
565
+ elsif (3==huns)
566
+ str += 'trecent'
567
+ elsif (4==huns)
568
+ str += 'quadringent'
569
+ elsif (5==huns)
570
+ str += 'quingent'
571
+ elsif (6==huns)
572
+ str += 'sescent'
573
+ elsif (7==huns)
574
+ str += 'septingent'
575
+ elsif (8==huns)
576
+ str += 'octingent'
577
+ elsif (9==huns)
578
+ str += 'nongent'
579
+ end
580
+ end
581
+ return pre + str + 'illion'
582
+ end
583
+
584
+ def self.get_number_name(int, *flags)
585
+ ary = get_number_struct(int)
586
+ neg = ary.pop == :neg
587
+ return NUMBERS[0] if ary.empty?
588
+ ary.reverse!
589
+ mult = 0
590
+ str = ""
591
+ ch = flags.include?(:_) ? "_" : " "
592
+ tch = flags.include?(:-) ? "-" : ch
593
+ loop do
594
+ break if ary.empty?
595
+ num = ary.pop
596
+ hun, dec = num.divmod 100
597
+ tstr = ""
598
+ unless hun.zero?
599
+ tstr = NUMBERS[hun] + ch + NUMBERS[100]
600
+ unless dec.zero?
601
+ tstr += ch
602
+ end
603
+ end
604
+ unless dec.zero?
605
+ if NUMBERS[dec].nil?
606
+ tens, ones = dec.divmod 10
607
+ tstr += NUMBERS[tens*10] + tch + NUMBERS[ones]
608
+ else
609
+ tstr += NUMBERS[dec]
610
+ end
611
+ end
612
+ unless tstr.empty?
613
+ if mult.zero?
614
+ str = tstr
615
+ else
616
+ ms = "10**#{mult}"
617
+ mm = NUMBERS[ms] # if you run out, you will need a generator
618
+ mm = create_big_number_name(mult) if mm.nil?
619
+ if str.empty?
620
+ str = tstr + ch + mm
621
+ else
622
+ str = tstr + ch + mm + ch + str
623
+ end
624
+ end
625
+ end
626
+ mult += 3
627
+ end
628
+ if flags.include? :plus_minus
629
+ if neg # use default
630
+ return "minus" + ch + str
631
+ end
632
+ return "plus" + ch + str
633
+ end
634
+ if flags.include? :positive_negative
635
+ if neg # use default
636
+ return "negative" + ch + str
637
+ end
638
+ return "positive" + ch + str
639
+ end
640
+ if neg # use default
641
+ return "minus" + ch + str
642
+ end
643
+ return str
644
+ end
645
+
646
+ def self.get_ordinal_name(int, *flags)
647
+ return ORDINALS[0] if int.zero?
648
+ ch = flags.include?(:_) ? "_" : " "
649
+ tch = flags.include?(:-) ? "-" : ch
650
+ if int.negative?
651
+ if flags.include? :positive_negative
652
+ pre = "negative" + ch
653
+ else flags.include? :plus_minus
654
+ pre = "minus" + ch
655
+ end
656
+ else
657
+ if flags.include? :positive_negative
658
+ pre = "positive" + ch
659
+ elsif flags.include? :plus_minus
660
+ pre = "plus" + ch
661
+ else
662
+ pre = ""
663
+ end
664
+ end
665
+ flags = flags - [:positive_negative, :plus_minus]
666
+ int = int.abs
667
+ n1,n2 = int.divmod 100
668
+ unless n2.zero?
669
+ n1 *= 100
670
+ if n1.zero?
671
+ str = pre
672
+ else
673
+ str = pre + get_number_name(n1, *flags)
674
+ str += ch
675
+ end
676
+ if ORDINALS.include? n2
677
+ str += ORDINALS[n2]
678
+ return str
679
+ end
680
+ n1,n2 = n2.divmod 10
681
+ n1 *= 10
682
+ str += NUMBERS[n1] + tch + ORDINALS[n2]
683
+ return str
684
+ end
685
+ return pre + get_number_name(int, *flags) + "th"
686
+ end
687
+
688
+ def self.get_denominator_name(int)
689
+ return "half" if 2==int
690
+ return "quarter" if 4==int
691
+ return get_ordinal_name(int, :-)
692
+ end
693
+
694
+ # methods to include in base classes:
695
+ module AccessMethods
696
+ def self.get_ordinal_at(object, ordinal)
697
+ n = Nth::get_n_from_string(ordinal)
698
+ return nil if n.nil?
699
+ return object[n-1]
700
+ end
701
+ end
702
+
19
703
  module IntMethods
20
- def to_nth
704
+ def self.to_nth_string(int, comma=true)
705
+ # 0th, 1st, 2nd, 3rd, 4th, 5th, 6th, 7th, 8th, 9th
706
+ pre = int.negative? ? "-" : ""
707
+ int = int.abs
708
+ teen = int % 100
709
+ if comma
710
+ str = int.to_s.reverse.scan(/\d{1,3}/).join(',').reverse
711
+ else
712
+ str = int.to_s
713
+ end
714
+ if (teen >= 4) && (teen <= 19)
715
+ return pre + str + "th"
716
+ end
717
+ ones = int % 10
718
+ return (pre + str + "st") if ones == 1
719
+ return (pre + str + "nd") if ones == 2
720
+ return (pre + str + "rd") if ones == 3
721
+ return pre + str + "th"
722
+ end
723
+ def self.to_ordinal_string(int)
724
+ Nth::get_ordinal_name(int, :-)
725
+ end
726
+ def self.to_number_string(int)
727
+ Nth::get_number_name(int, :-)
21
728
  end
22
- def to_ordinal
729
+ def self.penny_to_dollar(int)
730
+ neg = int.negative? ? "minas " : ""
731
+ int = int.abs
732
+ dollars, cents = int.divmod 100
733
+ ds = dollars == 1 ? "" : "s"
734
+ cs = cents == 1 ? " cent" : " cents"
735
+ return neg + to_number_string(dollars) + " dollar#{ds} and " + to_number_string(cents) + cs
736
+ end
737
+ def self.pence_to_pound(int)
738
+ neg = int.negative? ? "minas " : ""
739
+ int = int.abs
740
+ pounds, pence = int.divmod 100
741
+ ps = pounds == 1 ? "" : "s"
742
+ pp = pence == 1 ? " penny" : " pence"
743
+ return neg + to_number_string(pounds) + " pound#{ps} and " + to_number_string(pence) + pp
744
+ end
745
+ end
746
+
747
+ module FloatMethods
748
+ # :numeric ... 3 1/4 cups
749
+ # :descriptive ... three and one quarter cups
750
+ # :utf ... 3¼ cups
751
+ # :html ... 3&amp;#188; cups
752
+ def self.to_fractional_unit(flt, unit_name, mode = :descriptive, tar=0.01) # inch, cup, teaspoon etc
753
+ if :numeric==mode
754
+ frac=Fraction(flt,tar).to_s(false).to_s # work_around for now ... bump revision on prim_wrap gem
755
+ ary = frac.split(' ')
756
+ if 1==ary.count
757
+ str = ary.first
758
+ if str.include? '/'
759
+ return "#{str} #{unit_name}"
760
+ else
761
+ if '1'==str
762
+ return "1 #{unit_name}"
763
+ else
764
+ return "#{str} #{Nth::pluralize(unit_name)}"
765
+ end
766
+ end
767
+ else
768
+ return "#{ary.first} #{ary.last} #{Nth::pluralize(unit_name)}"
769
+ end
770
+ elsif :descriptive==mode
771
+ ## get_denominator_name(int) get_number_name(int, *flags)
772
+ ##Nth::pluralize(str)
773
+ frac=Fraction(flt,tar).to_s(false).to_s
774
+ ary = frac.split(' ')
775
+
776
+ if 1==ary.count
777
+ str = ary.first
778
+ if str.include? '/'
779
+ ary = str.split('/')
780
+ top = ary.first.to_i
781
+ bot = ary.last.to_i
782
+ top = Nth::get_number_name(top, :-)
783
+ denom = Nth::get_denominator_name(bot)
784
+ return "#{top} #{denom} #{unit_name}"
785
+ else
786
+ if '1'==str
787
+ return "one #{unit_name}"
788
+ else
789
+ num = ary.first.to_i
790
+ name = Nth::get_number_name(num, :-)
791
+ return "#{name} #{Nth::pluralize(unit_name)}"
792
+ end
793
+ end
794
+ else
795
+ str = Nth::get_number_name(ary.first.to_i, :-) + " and "
796
+ ary = ary.last.split('/')
797
+ top = ary.first.to_i
798
+ bot = ary.last.to_i
799
+ denom = Nth::get_denominator_name(bot)
800
+ # denom = Nth::pluralize(denom) unless top==1
801
+ top = Nth::get_number_name(top, :-)
802
+ return str + "#{top} #{denom} #{Nth::pluralize(unit_name)}"
803
+ end
804
+ elsif [:utf,:html].include? mode
805
+ frac=Fraction(flt,tar).to_s(false).to_s # work_around for now ... bump revision on prim_wrap gem
806
+ ary = frac.split(' ')
807
+ if 1==ary.count
808
+ str = ary.first
809
+ if str.include? '/'
810
+ utf = FRACTION_UTF[str]
811
+ return "#{str} #{unit_name}" if utf.nil?
812
+ if :utf==mode
813
+ return "#{utf} #{unit_name}"
814
+ else
815
+ return '&#' + "#{utf.ord}; #{unit_name}"
816
+ end
817
+ else
818
+ if '1'==str
819
+ return "1 #{unit_name}"
820
+ else
821
+ return "#{str} #{Nth::pluralize(unit_name)}"
822
+ end
823
+ end
824
+ else
825
+ utf = FRACTION_UTF[ary.last]
826
+ if utf.nil?
827
+ utf = ' ' + ary.last
828
+ elsif :html==mode
829
+ utf = '&#' + "#{utf.ord};"
830
+ end
831
+ return "#{ary.first}#{utf} #{Nth::pluralize(unit_name)}"
832
+ end
833
+ else
834
+ raise "mode `#{mode.inspect}` is not defined"
835
+ end
836
+ end
837
+
838
+ # :numeric_long ... 3 feet 1 3/4 inches
839
+ # :numeric_short ... 3ft 1 3/4in
840
+ # :descriptive ... three feet one and three quarter inches
841
+ # :tics_utf ... 3′ 1¾″
842
+ # :tics_html ... above with html codes
843
+ def self.inches_to_feet(flt, mode = :descriptive, denom=16)
844
+ neg = flt.negative? ? "-" : ""
845
+ str = neg
846
+ flt = flt.abs
847
+ num = flt.floor
848
+ frac = flt - num
849
+ feet = (num / 12.0)
850
+ feet_flr = feet.floor
851
+ feet_frac = feet - feet_flr
852
+ inches = feet_frac * 12.0 + frac
853
+ # copy & paste
854
+ frac = Fraction(inches,denom).to_s(false).to_s
855
+ ary = frac.split(' ')
856
+ if [:tics_utf,:tics_html].include? mode
857
+ if mode == :tics_utf
858
+ inch_tic = '″'
859
+ foot_tic = '′'
860
+ else
861
+ inch_tic = '&#8243;'
862
+ foot_tic = '&#8242;'
863
+ end
864
+ if (feet_flr.zero?)
865
+ if 1==ary.count
866
+ return '0' + foot_tic if 0 == ary.first.to_i
867
+ end
868
+ else
869
+ str += feet_flr.zero? ? "" : "#{feet_flr}#{foot_tic} "
870
+ end
871
+
872
+ if 1==ary.count
873
+ return str + ary.first + inch_tic
874
+ else
875
+ str += ary.first
876
+ utf = FRACTION_UTF[ary.last]
877
+ if utf.nil?
878
+ str += ' ' + ary.last + inch_tic
879
+ else
880
+ if mode == :tics_utf
881
+ str += utf + inch_tic
882
+ else
883
+ str += '&#' + "#{utf.ord};" + inch_tic
884
+ end
885
+ end
886
+ return str
887
+ end
888
+ elsif [:numeric_long,:numeric_short].include? mode
889
+ if mode == :numeric_short
890
+ inch_name = 'in'
891
+ foot_name = 'ft'
892
+ else
893
+ inch_name = ' inch'
894
+ foot_name = feet_flr==1 ? ' foot' : ' feet'
895
+ end
896
+ if feet_flr.zero?
897
+ if 1==ary.count
898
+ return '0' + foot_name if 0 == ary.first.to_i
899
+ end
900
+ else
901
+ str += feet_flr.to_s + foot_name
902
+ end
903
+ if 1==ary.count
904
+ str += ' ' unless str.empty?
905
+ str += ary.first + inch_name
906
+ if mode == :numeric_long
907
+ str += 'es' unless ary.first.to_i == 1
908
+ end
909
+ else
910
+ str += ' ' unless str.empty?
911
+ str += ary.first + ' ' + ary.last + inch_name
912
+ str += 'es' unless mode == :numeric_short
913
+ end
914
+ return str
915
+ elsif :descriptive == mode
916
+ inch_name = ' inch'
917
+ foot_name = feet_flr==1 ? ' foot' : ' feet'
918
+ str = "minas " unless str.empty?
919
+ if feet_flr.zero?
920
+ if 1==ary.count
921
+ return 'zero' + foot_name if '0' == ary.first
922
+ end
923
+ else
924
+ str += Nth::get_number_name(feet_flr, :-) + foot_name
925
+ end
926
+ if 1==ary.count
927
+ ### fractional entities must not call #to_i
928
+ ### ok unless fractional
929
+ if ary.first.include? '/'
930
+ ary = ary.first.split('/')
931
+ str += ' ' unless str.empty?
932
+ str += Nth::get_number_name(ary.first.to_i, :-) + ' ' + Nth::get_denominator_name(ary.last.to_i) + inch_name
933
+ else
934
+ str += ' ' unless str.empty?
935
+ str += Nth::get_number_name(ary.first.to_i, :-) + inch_name
936
+ str += 'es' unless ary.first == '1'
937
+ end
938
+ else
939
+ str += ' ' unless str.empty?
940
+ str += Nth::get_number_name(ary.first.to_i, :-) + ' and '
941
+ ary = ary.last.split('/')
942
+ str += Nth::get_number_name(ary.first.to_i, :-) + ' ' + Nth::get_denominator_name(ary.last.to_i) + inch_name + 'es'
943
+ end
944
+ return str
945
+ else
946
+ raise "unsupported mode #{mode}"
947
+ end
948
+ end
949
+
950
+ # this would be nice on the fraction class too...
951
+
952
+ def self.inches_to_yards(flt, mode = :descriptive, denom=16)
953
+ raise 'unsupported mode' unless [:numeric_long,:numeric_short,:descriptive].include? mode
954
+ # try to use inches_to_feet
955
+ neg = flt.negative? ? true : false
956
+ pfx = neg ? "-" : ""
957
+ flt = flt.abs
958
+ yards = (flt / 36.0).floor
959
+ inches = flt - (yards * 36.0)
960
+ str = inches_to_feet(inches, mode, denom)
961
+ if yards==1
962
+ if :numeric_short==mode
963
+ return pfx + "1yd " + str
964
+ elsif :numeric_long==mode
965
+ return pfx + "1 yard " + str
966
+ else
967
+ pfx = "minas " if neg
968
+ return pfx + "one yard " + str
969
+ end
970
+ else
971
+ if :numeric_short==mode
972
+ return "#{yards}yd " + str
973
+ elsif :numeric_long==mode
974
+ return "#{yards} yards " + str
975
+ else
976
+ pfx = "minas " if neg
977
+ yy = Nth::get_number_name(yards, :-)
978
+ return "#{yy} yards " + str
979
+ end
980
+ end
981
+ end
982
+ def self.to_dms(val, sec_frac=2)
983
+ sgn = val.negative? ? -1.0 : 1.0
984
+ num = val.abs % 360
985
+ num *= sgn
986
+ if num > 180.0
987
+ num -= 360.0
988
+ elsif num < -180.0
989
+ num += 360.0
990
+ end
991
+ sgn = num.negative? ? "-" : ""
992
+ num = num.abs
993
+ deg = num.floor
994
+ num = (num-deg) * 60.0
995
+ min = num.floor
996
+ num = (num-min) * 60.0
997
+ if (sec_frac==0)
998
+ sec = num.round 0
999
+ frac = ""
1000
+ else
1001
+ sec = num.floor
1002
+ num = (num-sec) * (10 ** sec_frac)
1003
+ frac = ".#{num.floor}"
1004
+ end
1005
+ return "#{sgn}#{deg}°#{min}′#{sec}#{frac}″"
1006
+ end
1007
+ end
1008
+
1009
+ def self.install(pn = :None, *plist)
1010
+ if (pn==:IntMethods)
1011
+ Integer.class_eval do
1012
+ unless self.instance_methods.include? :nth
1013
+ def nth(comma=true)
1014
+ Nth::IntMethods.to_nth_string(self, comma)
1015
+ end
1016
+ end
1017
+ unless self.instance_methods.include? :to_ordinal
1018
+ def to_ordinal
1019
+ Nth::IntMethods.to_ordinal_string(self)
1020
+ end
1021
+ end
1022
+ unless self.instance_methods.include? :to_number
1023
+ def to_number
1024
+ Nth::IntMethods.to_number_string(self)
1025
+ end
1026
+ end
1027
+ unless self.instance_methods.include? :cents_to_dollars
1028
+ def cents_to_dollars
1029
+ Nth::IntMethods.penny_to_dollar(self)
1030
+ end
1031
+ end
1032
+ unless self.instance_methods.include? :pence_to_pounds
1033
+ def pence_to_pounds
1034
+ Nth::IntMethods.pence_to_pound(self)
1035
+ end
1036
+ end
1037
+ end
1038
+ # add to wrapper classes ...
1039
+ meths = [:nth, :to_ordinal, :to_number, :cents_to_dollars, :pence_to_pounds]
1040
+ Int.bestow_methods(*meths)
1041
+ Number.bestow_methods(*meths)
1042
+ Datum.bestow_methods(*meths)
1043
+ elsif (pn==:FloatMethods)
1044
+ Float.class_eval do
1045
+ unless self.instance_methods.include? :to_fractional_unit
1046
+ def to_fractional_unit(unit_name, mode = :descriptive, tar=0.01)
1047
+ Nth::FloatMethods.to_fractional_unit(self, unit_name, mode, tar)
1048
+ end
1049
+ end
1050
+ unless self.instance_methods.include? :inches_to_feet
1051
+ def inches_to_feet(mode = :descriptive, denom=16)
1052
+ Nth::FloatMethods.inches_to_feet(self, mode, denom)
1053
+ end
1054
+ end
1055
+ unless self.instance_methods.include? :inches_to_yards
1056
+ def inches_to_yards(mode = :descriptive, denom=16)
1057
+ Nth::FloatMethods.inches_to_yards(self, mode, denom)
1058
+ end
1059
+ end
1060
+ unless self.instance_methods.include? :to_dms
1061
+ def to_dms(sec_frac=2)
1062
+ Nth::FloatMethods.to_dms(self, sec_frac)
1063
+ end
1064
+ end
1065
+ end
1066
+ elsif (pn==:AccessAllOrdinals)
1067
+ plist[0].class_eval do
1068
+ @default_insert = plist[1]
1069
+ if @default_insert.nil?
1070
+ if self==String
1071
+ @default_insert = ' '
1072
+ end
1073
+ end
1074
+ def respond_to_missing?(method_name, include_private = false)
1075
+ begin
1076
+ meth = method_name.to_s
1077
+ str = meth[-1]=="=" ? meth[0..-2] : meth
1078
+ num = Nth::ordinal_string_to_number(str)
1079
+ return num.type_of? Integer
1080
+ rescue
1081
+ return false
1082
+ end
1083
+ return false
1084
+ end
1085
+ def method_missing(method_name, *prms, &block)
1086
+ meth = method_name.to_s
1087
+ num = nil
1088
+ assign = false
1089
+ str = ""
1090
+ begin
1091
+ if meth[-1]=="="
1092
+ str = meth[0..-2]
1093
+ assign = true
1094
+ else
1095
+ str = meth
1096
+ end
1097
+ num = Nth::ordinal_string_to_number(str)
1098
+ rescue
1099
+ super
1100
+ end
1101
+ super if num.nil?
1102
+ if assign
1103
+ if 1==prms.count
1104
+ if num >= size
1105
+ rpl = self.class.instance_variable_get :@default_insert
1106
+ (size..(num-1)).each { |t| self[t] = rpl }
1107
+ end
1108
+ self[num] = prms[0]
1109
+ return self
1110
+ else
1111
+ raise "*** ArgumentError Exception: wrong number of arguments (given #{prms.count}, expected 1)"
1112
+ end
1113
+ else
1114
+ raise "*** ArgumentError Exception: wrong number of arguments (given #{prms.count}, expected 0)" if prms.count > 0
1115
+ return self[num]
1116
+ end
1117
+ end
1118
+ end
1119
+ elsif (pn==:AccessOrdinals)
1120
+ plist[0].class_eval do
1121
+ @default_insert = plist[2]
1122
+ if @default_insert.nil?
1123
+ if self==String
1124
+ @default_insert = ' '
1125
+ end
1126
+ end
1127
+ unless self.instance_methods.include? :last
1128
+ def last
1129
+ self[-1]
1130
+ end
1131
+ end
1132
+ unless self.instance_methods.include? :last=
1133
+ def last=(val)
1134
+ self[-1]=val
1135
+ end
1136
+ end
1137
+ ran = (1..plist[1])
1138
+ ran.each do |num|
1139
+ meth = Nth::get_ordinal_name(num, :_)
1140
+ unless self.instance_methods.include? meth.to_sym
1141
+ define_method meth.to_sym do
1142
+ self[num-1]
1143
+ end
1144
+ end
1145
+ meth += '='
1146
+ unless self.instance_methods.include? meth.to_sym
1147
+ define_method meth.to_sym do |val|
1148
+ if num >= size
1149
+ rpl = self.class.instance_variable_get :@default_insert
1150
+ (size..(num-1)).each { |t| self[t] = rpl }
1151
+ end
1152
+ self[num-1] = val
1153
+ self
1154
+ end
1155
+ end
1156
+ end
1157
+ end
1158
+ else
1159
+ raise "unrecognized install parameter #{pn}"
23
1160
  end
24
1161
  end
25
1162
  end