numbers_in_words 0.1.0 → 0.1.1
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.
- data/Manifest +0 -1
- data/README +16 -2
- data/Rakefile +33 -3
- data/lib/numbers.rb +215 -192
- data/lib/numbers_in_words.rb +0 -1
- data/lib/words.rb +192 -197
- data/numbers_in_words.gemspec +12 -13
- data/spec/numbers_in_words_spec.rb +85 -81
- data/spec/words_in_numbers_spec.rb +108 -101
- metadata +38 -53
data/Manifest
CHANGED
data/README
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
Installation
|
2
2
|
============
|
3
3
|
|
4
|
-
|
5
4
|
gem install numbers_in_words
|
6
5
|
|
7
6
|
or
|
@@ -10,13 +9,21 @@ sudo gem install numbers_in_words
|
|
10
9
|
|
11
10
|
|
12
11
|
|
12
|
+
|
13
13
|
The file numbers_to_words defines a module NumbersToWords which is included in Fixnum and Bignum.
|
14
14
|
The in_words method can then be used on any Fixnum or Bignum object.
|
15
15
|
|
16
16
|
E.g.
|
17
17
|
>require 'numbers_in_words'
|
18
18
|
>112.in_words
|
19
|
-
|
19
|
+
=> one hundred and twelve
|
20
|
+
>"one googol".in_numbers
|
21
|
+
=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
22
|
+
>"Seventy million, five-hundred and fifty six thousand point eight nine three".in_numbers
|
23
|
+
=> 70556000.893
|
24
|
+
|
25
|
+
---------------
|
26
|
+
Example program
|
20
27
|
|
21
28
|
To try it out the program display_numbers_to_words.rb expects two integer parameters to define a
|
22
29
|
range. It then prints out all numbers in words included in this range.
|
@@ -68,3 +75,10 @@ Example of usage:
|
|
68
75
|
2*10^2= 2 hundreds
|
69
76
|
45*10^0 = 45 ones
|
70
77
|
|
78
|
+
Future plans
|
79
|
+
============
|
80
|
+
|
81
|
+
* Handle complex numbers
|
82
|
+
* Option for outputting punctuation
|
83
|
+
* Reject invalid numbers
|
84
|
+
* Support for other languages
|
data/Rakefile
CHANGED
@@ -1,14 +1,44 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
require 'echoe'
|
4
|
-
|
4
|
+
require 'metric_fu'
|
5
|
+
|
6
|
+
Echoe.new('numbers_in_words','0.1.1') do |e|
|
5
7
|
e.description = "#in_words method for integers and #in_numbers for strings"
|
6
8
|
e.summary = "Example: 123.in_words #=> \"one hundred and twenty three\", \"seventy-five point eight\".in_numbers #=> 75.8"
|
7
9
|
e.url = "http://rubygems.org/gems/numbers_in_words"
|
8
10
|
e.author = "Mark Burns"
|
9
|
-
e.email = "markthedeveloper@
|
11
|
+
e.email = "markthedeveloper@gmail.com"
|
10
12
|
e.ignore_pattern = ["tmp/*",".git/*"]
|
11
|
-
e.
|
13
|
+
e.dependencies = ['active_support']
|
12
14
|
end
|
13
15
|
|
14
16
|
|
17
|
+
MetricFu::Configuration.run do |config|
|
18
|
+
#define which metrics you want to use
|
19
|
+
config.metrics = [:churn, :saikuro, :stats, :flog, :flay, :reek, :roodi, :rcov]
|
20
|
+
config.graphs = [:flog, :flay, :reek, :roodi, :rcov]
|
21
|
+
config.flay = { :dirs_to_flay => ['lib'], :minimum_score => 100 }
|
22
|
+
config.flog = { :dirs_to_flog => [ 'lib'] }
|
23
|
+
config.reek = { :dirs_to_reek => ['lib'] }
|
24
|
+
config.roodi = { :dirs_to_roodi => ['lib'] }
|
25
|
+
config.saikuro = { :output_directory => 'scratch_directory/saikuro',
|
26
|
+
:input_directory => ['lib'],
|
27
|
+
:cyclo => "",
|
28
|
+
:filter_cyclo => "0",
|
29
|
+
:warn_cyclo => "5",
|
30
|
+
:error_cyclo => "7",
|
31
|
+
:formater => "text"} #this needs to be set to "text"
|
32
|
+
config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10}
|
33
|
+
config.rcov = { :environment => 'test',
|
34
|
+
:test_files => ['spec/**/*_spec.rb'],
|
35
|
+
:rcov_opts => ["--sort coverage",
|
36
|
+
"--no-html",
|
37
|
+
"--text-coverage",
|
38
|
+
"--no-color",
|
39
|
+
"--profile",
|
40
|
+
"--rails",
|
41
|
+
"--exclude /gems/,/Library/,spec"]}
|
42
|
+
config.graph_engine = :bluff
|
43
|
+
end
|
44
|
+
|
data/lib/numbers.rb
CHANGED
@@ -1,237 +1,260 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'active_support'
|
2
|
+
require 'active_support/core_ext/array'
|
3
3
|
module NumbersInWords
|
4
|
+
EXCEPTIONS = {
|
5
|
+
10 => "ten",
|
6
|
+
11 => "eleven",
|
7
|
+
12 => "twelve",
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
24=>"septillion",
|
22
|
-
27=>"octillion",
|
23
|
-
30=>"nonillion",
|
24
|
-
33=>"decillion",
|
25
|
-
36=>"undecillion",
|
26
|
-
39=>"duodecillion",
|
27
|
-
42=>"tredecillion",
|
28
|
-
45=>"quattuordecillion",
|
29
|
-
48=>"quindecillion",
|
30
|
-
51=>"sexdecillion",
|
31
|
-
54=>"septendecillion",
|
32
|
-
57=>"octodecillion",
|
33
|
-
60=>"novemdecillion",
|
34
|
-
63=>"vigintillion",
|
35
|
-
66=>"unvigintillion",
|
36
|
-
69=>"duovigintillion",
|
37
|
-
72=>"trevigintillion",
|
38
|
-
75=>"quattuorvigintillion",
|
39
|
-
78=>"quinvigintillion",
|
40
|
-
81=>"sexvigintillion",
|
41
|
-
84=>"septenvigintillion",
|
42
|
-
87=>"octovigintillion",
|
43
|
-
90=>"novemvigintillion",
|
44
|
-
93=>"trigintillion",
|
45
|
-
96=>"untrigintillion",
|
46
|
-
99=>"duotrigintillion",
|
47
|
-
100 => "googol"
|
9
|
+
13 => "thirteen",
|
10
|
+
14 => "fourteen",
|
11
|
+
15 => "fifteen",
|
12
|
+
16 => "sixteen" ,
|
13
|
+
17 => "seventeen",
|
14
|
+
18 => "eighteen",
|
15
|
+
19 => "nineteen",
|
16
|
+
|
17
|
+
20 => "twenty",
|
18
|
+
30 => "thirty",
|
19
|
+
40 => "forty",
|
20
|
+
50 => "fifty",
|
21
|
+
60 => "sixty",
|
22
|
+
70 => "seventy",
|
23
|
+
80 => "eighty",
|
24
|
+
90 => "ninety"
|
48
25
|
}
|
26
|
+
|
27
|
+
DIGITS = %w[zero one two three four five six seven eight nine]
|
28
|
+
|
29
|
+
POWERS_OF_TEN ={
|
30
|
+
0 => "one",
|
31
|
+
1 => "ten",
|
32
|
+
2 => "hundred",
|
33
|
+
3 => "thousand",
|
34
|
+
6 => "million",
|
35
|
+
9 => "billion",
|
36
|
+
12 => "trillion",
|
37
|
+
15 => "quadrillion",
|
38
|
+
18 => "quintillion",
|
39
|
+
21 => "sextillion",
|
40
|
+
24 => "septillion",
|
41
|
+
27 => "octillion",
|
42
|
+
30 => "nonillion",
|
43
|
+
33 => "decillion",
|
44
|
+
36 => "undecillion",
|
45
|
+
39 => "duodecillion",
|
46
|
+
42 => "tredecillion",
|
47
|
+
45 => "quattuordecillion",
|
48
|
+
48 => "quindecillion",
|
49
|
+
51 => "sexdecillion",
|
50
|
+
54 => "septendecillion",
|
51
|
+
57 => "octodecillion",
|
52
|
+
60 => "novemdecillion",
|
53
|
+
63 => "vigintillion",
|
54
|
+
66 => "unvigintillion",
|
55
|
+
69 => "duovigintillion",
|
56
|
+
72 => "trevigintillion",
|
57
|
+
75 => "quattuorvigintillion",
|
58
|
+
78 => "quinvigintillion",
|
59
|
+
81 => "sexvigintillion",
|
60
|
+
84 => "septenvigintillion",
|
61
|
+
87 => "octovigintillion",
|
62
|
+
90 => "novemvigintillion",
|
63
|
+
93 => "trigintillion",
|
64
|
+
96 => "untrigintillion",
|
65
|
+
99 => "duotrigintillion",
|
66
|
+
100 => "googol"
|
67
|
+
}
|
68
|
+
|
49
69
|
LENGTH_OF_GOOGOL = 101 #length of the string i.e. one with 100 zeros
|
50
70
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
power += 3
|
71
|
+
def in_words language="English"
|
72
|
+
case language
|
73
|
+
when "English" #allow for I18n
|
74
|
+
in_english
|
56
75
|
end
|
57
76
|
end
|
58
77
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
digits.map! { |x| x.to_i}
|
65
|
-
|
66
|
-
digits.reverse!
|
67
|
-
#create a hash where the key is the
|
68
|
-
#power of ten and the value is the multipler
|
69
|
-
power = 0
|
70
|
-
return digits.inject({}) do |result,digit|
|
71
|
-
result[power]=digit unless digit==0
|
72
|
-
power+=1
|
73
|
-
result
|
74
|
-
end
|
75
|
-
end
|
78
|
+
def in_english
|
79
|
+
#handle 0-9
|
80
|
+
return DIGITS[self] if self.is_a?(Integer) and (0..9).to_a.include? self
|
81
|
+
#teens etc
|
82
|
+
return EXCEPTIONS[self] if self.is_a?(Integer) and EXCEPTIONS[self]
|
76
83
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
output[power]=digits
|
100
|
-
power+=size
|
101
|
-
output
|
84
|
+
writer = LanguageWriterEnglish.new(self)
|
85
|
+
|
86
|
+
in_decimals = writer.decimals
|
87
|
+
return in_decimals unless in_decimals.nil?
|
88
|
+
|
89
|
+
number = to_i
|
90
|
+
|
91
|
+
return writer.negative() if number < 0
|
92
|
+
|
93
|
+
length = number.to_s.length
|
94
|
+
output = ""
|
95
|
+
|
96
|
+
if length == 2 #20-99
|
97
|
+
tens = (number/10).round*10 #write the tens
|
98
|
+
|
99
|
+
output << EXCEPTIONS[tens] # e.g. eighty
|
100
|
+
|
101
|
+
digit = number - tens #write the digits
|
102
|
+
|
103
|
+
output << " " + digit.in_english unless digit==0
|
104
|
+
else
|
105
|
+
output << writer.write() #longer numbers
|
102
106
|
end
|
103
107
|
|
108
|
+
output.strip
|
104
109
|
end
|
105
110
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
digits = groups[power]
|
114
|
-
yield power, name, digits
|
111
|
+
protected
|
112
|
+
|
113
|
+
class LanguageWriter
|
114
|
+
attr_accessor :number
|
115
|
+
|
116
|
+
def initialize number
|
117
|
+
@number = number
|
115
118
|
end
|
116
119
|
|
117
|
-
|
120
|
+
def group_words size
|
121
|
+
#1000 and over Numbers are split into groups of three
|
122
|
+
groups = NumberGroup.groups_of @number, size
|
123
|
+
powers = groups.keys.sort.reverse #put in descending order
|
118
124
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
if digits > 0
|
124
|
-
prefix = " "
|
125
|
-
#no 'and' between thousands and hundreds
|
126
|
-
prefix << "and " if power == 0 and digits < 100
|
127
|
-
output << prefix + digits.in_english
|
128
|
-
output << prefix + name unless power == 0
|
125
|
+
powers.each do |power|
|
126
|
+
name = POWERS_OF_TEN[power]
|
127
|
+
digits = groups[power]
|
128
|
+
yield power, name, digits
|
129
129
|
end
|
130
130
|
end
|
131
|
-
return output
|
132
131
|
end
|
133
132
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
googols = number.to_s[0..(-LENGTH_OF_GOOGOL)].to_i
|
138
|
-
remainder = number.to_s[1-LENGTH_OF_GOOGOL .. -1].to_i
|
139
|
-
output << " " + googols.in_words + " googol"
|
140
|
-
if remainder > 0
|
141
|
-
prefix = " "
|
142
|
-
prefix << "and " if remainder < 100
|
143
|
-
output << prefix + remainder.in_english
|
133
|
+
class LanguageWriterEnglish < LanguageWriter
|
134
|
+
def negative
|
135
|
+
"minus " + (-@number).in_english
|
144
136
|
end
|
145
|
-
return output
|
146
|
-
end
|
147
137
|
|
148
|
-
|
138
|
+
def write
|
139
|
+
length = @number.to_s.length
|
140
|
+
output = if length == 3
|
141
|
+
#e.g. 113 splits into "one hundred" and "thirteen"
|
142
|
+
write_groups(2)
|
149
143
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
remainder = number.to_s[1..-1].to_i
|
154
|
-
prefix = " "
|
155
|
-
prefix << "and " if remainder < 100
|
156
|
-
output << prefix + remainder.in_english if remainder > 0
|
144
|
+
#more than one hundred less than one googol
|
145
|
+
elsif length < LENGTH_OF_GOOGOL
|
146
|
+
write_groups(3)
|
157
147
|
|
158
|
-
|
159
|
-
|
148
|
+
elsif length >= LENGTH_OF_GOOGOL
|
149
|
+
write_googols
|
160
150
|
|
161
|
-
|
162
|
-
|
163
|
-
int = value.to_i
|
164
|
-
decimal = value - int
|
165
|
-
return int.in_english + " point " + decimal_portion(decimal)
|
151
|
+
end
|
152
|
+
output.strip
|
166
153
|
end
|
167
|
-
return nil
|
168
|
-
end
|
169
154
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
155
|
+
def decimals
|
156
|
+
int, decimals = NumberGroup.new(@number).split_decimals
|
157
|
+
if int
|
158
|
+
out = int.in_english + " point "
|
159
|
+
decimals.each do |decimal|
|
160
|
+
out << decimal.to_i.in_english + " "
|
161
|
+
end
|
162
|
+
out.strip
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
def write_googols
|
168
|
+
googols, remainder = NumberGroup.new(@number).split_googols
|
169
|
+
output = ""
|
170
|
+
output << " " + googols.in_words + " googol"
|
171
|
+
if remainder > 0
|
172
|
+
prefix = " "
|
173
|
+
prefix << "and " if remainder < 100
|
174
|
+
output << prefix + remainder.in_english
|
175
|
+
end
|
176
|
+
output
|
177
|
+
end
|
178
178
|
|
179
|
+
def write_groups group
|
180
|
+
#e.g. 113 splits into "one hundred" and "thirteen"
|
181
|
+
output = ""
|
182
|
+
group_words(group) do |power, name, digits|
|
183
|
+
if digits > 0
|
184
|
+
prefix = " "
|
185
|
+
#no and between thousands and hundreds
|
186
|
+
prefix << "and " if power == 0 and digits < 100
|
187
|
+
output << prefix + digits.in_english
|
188
|
+
output << prefix + name unless power == 0
|
189
|
+
end
|
190
|
+
end
|
191
|
+
output
|
192
|
+
end
|
179
193
|
end
|
180
194
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
return d unless d.nil?
|
195
|
+
class NumberGroup
|
196
|
+
include Enumerable
|
197
|
+
attr_accessor :number
|
185
198
|
|
199
|
+
def each
|
200
|
+
@array.each { |item| yield item}
|
201
|
+
end
|
186
202
|
|
187
|
-
|
188
|
-
#
|
189
|
-
|
203
|
+
#split into groups this gives us 1234567 => 123 456 7
|
204
|
+
#so we need to reverse first
|
205
|
+
#in stages
|
206
|
+
def initialize number
|
207
|
+
@number = number
|
208
|
+
end
|
190
209
|
|
191
|
-
|
192
|
-
|
193
|
-
|
210
|
+
def groups size
|
211
|
+
#i.e. 1234567 => 7654321
|
212
|
+
groups = @number.to_s.reverse
|
213
|
+
#7654321 => 765 432 1
|
214
|
+
@array = groups.split("").in_groups_of(size)
|
215
|
+
#765 432 1 => 1 432 765
|
216
|
+
@array.reverse!
|
217
|
+
#1 432 765 => 1 234 567
|
218
|
+
#and turn back into integers
|
219
|
+
@array.map! {|group| group.reverse.join("").to_i}
|
220
|
+
@array.reverse! # put in ascending order of power of ten
|
221
|
+
power = 0
|
222
|
+
output = @array.inject({}) do |output, digits|
|
223
|
+
output[power] = digits
|
224
|
+
power += size
|
225
|
+
output
|
226
|
+
end
|
227
|
+
output
|
228
|
+
end
|
194
229
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
output << number.english_group(2)
|
208
|
-
elsif length < LENGTH_OF_GOOGOL #more than one hundred less than one googol
|
209
|
-
output << number.english_group(3)
|
210
|
-
elsif length == LENGTH_OF_GOOGOL
|
211
|
-
output << number.in_googols
|
212
|
-
elsif length > LENGTH_OF_GOOGOL #one googol and larger
|
213
|
-
output << number.split_googols
|
230
|
+
def split_decimals
|
231
|
+
if @number.is_a? Float
|
232
|
+
int = @number.to_i
|
233
|
+
decimal = @number - int
|
234
|
+
decimal = decimal.to_s.split(".")[1]
|
235
|
+
digits = decimal.split //
|
236
|
+
#convert to integers array
|
237
|
+
digits.inject([]) {|out, digit|
|
238
|
+
out<< digit.to_i
|
239
|
+
}
|
240
|
+
return int, digits
|
241
|
+
end
|
214
242
|
end
|
215
243
|
|
216
|
-
|
217
|
-
|
244
|
+
def self.groups_of number, size
|
245
|
+
new(number).groups(size)
|
246
|
+
end
|
218
247
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
248
|
+
def split_googols
|
249
|
+
output = ""
|
250
|
+
googols = @number.to_s[0 .. (-LENGTH_OF_GOOGOL)].to_i
|
251
|
+
remainder = @number.to_s[(1-LENGTH_OF_GOOGOL) .. -1].to_i
|
252
|
+
return googols, remainder
|
223
253
|
end
|
224
254
|
end
|
225
255
|
end
|
226
256
|
|
227
|
-
class Fixnum
|
228
|
-
include NumbersInWords
|
229
|
-
end
|
230
|
-
|
231
|
-
class Bignum
|
232
|
-
include NumbersInWords
|
233
|
-
end
|
234
257
|
|
235
|
-
class
|
258
|
+
class Numeric
|
236
259
|
include NumbersInWords
|
237
260
|
end
|
data/lib/numbers_in_words.rb
CHANGED