numbers_in_words 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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