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 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
-