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 CHANGED
@@ -7,6 +7,5 @@ init.rb
7
7
  lib/numbers.rb
8
8
  lib/numbers_in_words.rb
9
9
  lib/words.rb
10
- numbers_in_words.gemspec
11
10
  spec/numbers_in_words_spec.rb
12
11
  spec/words_in_numbers_spec.rb
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
- #=> one hundred and twelve
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
- Echoe.new('numbers_in_words','0.1.0') do |e|
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@googlemail.com"
11
+ e.email = "markthedeveloper@gmail.com"
10
12
  e.ignore_pattern = ["tmp/*",".git/*"]
11
- e.development_dependencies = ['active_support']
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
- #handle exceptions to normal numbers
6
- EXCEPTIONS = {10=> "ten", 11=>"eleven", 12 => "twelve", 13 => "thirteen",
7
- 14=>"fourteen", 15=>"fifteen", 16=>"sixteen" ,
8
- 17=> "seventeen", 18=> "eighteen", 19=> "nineteen",
9
- 20 => "twenty", 30=>"thirty",
10
- 40=>"forty", 50=>"fifty", 60 => "sixty", 70=> "seventy", 80=>"eighty",
11
- 90 => "ninety"}
12
-
13
- DIGITS= %w[zero one two three four five six seven eight nine]
14
- POWERS_OF_TEN ={0=>"one", 1 => "ten", 2=> "hundred",
15
- 3 => "thousand", 6=>"million",
16
- 9=>"billion",
17
- 12=>"trillion",
18
- 15=>"quadrillion",
19
- 18=>"quintillion",
20
- 21=>"sextillion",
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 initialize
52
- power = 9
53
- POWERS_OF_TEN_NAMES.each do |name|
54
- POWERS_OF_TEN[power]=name
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
- #returns a hash with powers of ten and their multipliers
60
- def powers_of_ten
61
- i = self.to_i
62
- digits=i.to_s.split ""
63
- #turn back into integers
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
- def groups_of size
78
- i=self.to_i
79
- #split into groups this gives us 1234567 => 123 456 7
80
- #so we need to reverse first
81
- #in stages
82
- #i.e. 1234567 => 7654321
83
- groups = i.to_s.reverse
84
- #7654321 => 765 432 1
85
- groups = groups.split("").in_groups_of(size)
86
- #765 432 1 => 1 432 765
87
- groups.reverse!
88
- #1 432 765 => 1 234 567
89
- groups.map! {|group| group.reverse}
90
-
91
- #turn back into integers
92
- groups.map! {|group| group.join("").to_i }
93
- groups.reverse! # put in ascending order of power of ten
94
-
95
- #output hash where key is the power of ten
96
- #and value if the multiplier
97
- power = 0
98
- return groups.inject({}) do |output, digits|
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
- def group_words size, language="English"
107
- #1000 and over Numbers are split into groups of three
108
- number = self.to_i
109
- groups = number.groups_of(size)
110
- powers = groups.keys.sort.reverse #put in descending order
111
- powers.each do |power|
112
- name = POWERS_OF_TEN[power] if language=="English"
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
- end
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
- def english_group group_size
120
- number = self.to_i
121
- output = ""
122
- number.group_words(group_size) do |power, name, digits|
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
- def split_googols
135
- number = self.to_i
136
- output = ""
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
- def in_googols
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
- number = self.to_i
151
- output = ""
152
- output << " " + number.to_s[0..0].to_i.in_english + " googol"
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
- return output
159
- end
148
+ elsif length >= LENGTH_OF_GOOGOL
149
+ write_googols
160
150
 
161
- def handle_decimal value
162
- if value.is_a? Float
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
- def decimal_portion decimal
171
- decimal = decimal.to_s.split(".")[1]
172
- digits = decimal.to_s.split //
173
- out= digits.inject([]) {|out, digit|
174
- out<< digit.to_i.in_english
175
- out
176
- }
177
- out.join " "
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
- def in_english
182
- #decimals
183
- d= handle_decimal(self)
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
- number = self.to_i # make a copy
188
- #negative numbers
189
- return "minus " + (-number).in_english if number < 0
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
- #handle 0-10
192
- return DIGITS[number] if number < 10
193
- return EXCEPTIONS[number] if EXCEPTIONS[number]
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
- #longer numbers
196
- output = ""
197
- length = number.to_s.length
198
- if length == 2 #20-99
199
- tens = (number/10).round*10 #write the tens
200
- # e.g. eighty
201
- output << EXCEPTIONS[tens]
202
- #write the digits
203
- digit= number - tens
204
- output << " " + digit.in_english unless digit==0
205
- elsif length == 3
206
- #e.g. 113 splits into "one hundred" and "thirteen"
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
- return output.strip
217
- end
244
+ def self.groups_of number, size
245
+ new(number).groups(size)
246
+ end
218
247
 
219
- def in_words language="English"
220
- case language
221
- when "English" #allow for I18n
222
- in_english
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 Float
258
+ class Numeric
236
259
  include NumbersInWords
237
260
  end
@@ -5,4 +5,3 @@ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__)))
5
5
  require 'numbers'
6
6
  require 'words'
7
7
 
8
-