numbers_in_words 0.1.1 → 0.2.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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +22 -0
- data/README.md +97 -0
- data/Rakefile +1 -44
- data/lib/numbers_in_words.rb +21 -4
- data/lib/numbers_in_words/duck_punch.rb +19 -0
- data/lib/numbers_in_words/english/constants.rb +93 -0
- data/lib/numbers_in_words/english/language_writer_english.rb +107 -0
- data/lib/numbers_in_words/language_writer.rb +31 -0
- data/lib/numbers_in_words/number_group.rb +55 -0
- data/lib/numbers_in_words/number_parser.rb +81 -0
- data/lib/numbers_in_words/to_number.rb +82 -0
- data/lib/numbers_in_words/to_word.rb +19 -0
- data/lib/numbers_in_words/version.rb +3 -0
- data/numbers_in_words.gemspec +18 -26
- data/spec/language_writer_spec.rb +23 -0
- data/spec/number_group_spec.rb +17 -0
- data/spec/numbers_in_words_spec.rb +1 -35
- data/spec/spec_helper.rb +2 -0
- data/spec/words_in_numbers_spec.rb +1 -1
- metadata +83 -35
- data/CHANGELOG +0 -1
- data/Manifest +0 -11
- data/README +0 -84
- data/examples/display_numbers_in_words.rb +0 -22
- data/init.rb +0 -8
- data/lib/numbers.rb +0 -260
- data/lib/words.rb +0 -221
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
numbers_in_words (0.2.0)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (3.2.10)
|
11
|
+
i18n (~> 0.6)
|
12
|
+
multi_json (~> 1.0)
|
13
|
+
columnize (0.3.6)
|
14
|
+
debugger (1.2.3)
|
15
|
+
columnize (>= 0.3.1)
|
16
|
+
debugger-linecache (~> 1.1.1)
|
17
|
+
debugger-ruby_core_source (~> 1.1.5)
|
18
|
+
debugger-linecache (1.1.2)
|
19
|
+
debugger-ruby_core_source (>= 1.1.1)
|
20
|
+
debugger-ruby_core_source (1.1.6)
|
21
|
+
diff-lcs (1.1.3)
|
22
|
+
i18n (0.6.1)
|
23
|
+
multi_json (1.5.0)
|
24
|
+
rspec (2.12.0)
|
25
|
+
rspec-core (~> 2.12.0)
|
26
|
+
rspec-expectations (~> 2.12.0)
|
27
|
+
rspec-mocks (~> 2.12.0)
|
28
|
+
rspec-core (2.12.2)
|
29
|
+
rspec-expectations (2.12.1)
|
30
|
+
diff-lcs (~> 1.1.3)
|
31
|
+
rspec-mocks (2.12.1)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
debugger
|
38
|
+
numbers_in_words!
|
39
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Mark Burns
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
Installation
|
2
|
+
============
|
3
|
+
|
4
|
+
```
|
5
|
+
gem 'numbers_in_words'
|
6
|
+
|
7
|
+
require 'numbers_in_words'
|
8
|
+
require 'numbers_in_words/duck_punch' #see why later
|
9
|
+
```
|
10
|
+
|
11
|
+
This project was created for a test for a job interview. I haven't really used
|
12
|
+
it myself, but I saw it mentioned somewhere so I thought I'd tidy it up a bit.
|
13
|
+
|
14
|
+
I'm going to hopefully preempt some support queries by predicting this will happen:
|
15
|
+
|
16
|
+
You've got one of:
|
17
|
+
|
18
|
+
```
|
19
|
+
NoMethodError: undefined method `in_words' for 123:Fixnum
|
20
|
+
NoMethodError: undefined method `in_numbers' for "123":String
|
21
|
+
```
|
22
|
+
|
23
|
+
Here's why
|
24
|
+
==========
|
25
|
+
|
26
|
+
Previous versions of this gem duckpunched Fixnum and String with a whole bunch
|
27
|
+
of methods. This gem will now only add methods if you specifically tell it to
|
28
|
+
with:
|
29
|
+
|
30
|
+
```
|
31
|
+
require 'numbers_in_words'
|
32
|
+
require 'numbers_in_words/duck_punch'
|
33
|
+
```
|
34
|
+
|
35
|
+
Plus it now only adds a single `#in_words` method to `Numeric` and an `#in_numbers`
|
36
|
+
method to `String` instead of a whole bunch of them.
|
37
|
+
|
38
|
+
Usage
|
39
|
+
=========
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'numbers_in_words'
|
43
|
+
require 'numbers_in_words/duck_punch'
|
44
|
+
112.in_words
|
45
|
+
#=> one hundred and twelve
|
46
|
+
|
47
|
+
"one googol".in_numbers
|
48
|
+
#=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
49
|
+
|
50
|
+
"Seventy million, five-hundred and fifty six thousand point eight nine three".in_numbers
|
51
|
+
#=> 70556000.893
|
52
|
+
```
|
53
|
+
|
54
|
+
---------------
|
55
|
+
|
56
|
+
Whilst creating this project I realized that in English:
|
57
|
+
|
58
|
+
* Numbers are grouped in groups of threes
|
59
|
+
* Numbers less than 1,000 are grouped by hundreds and then by tens
|
60
|
+
* There are specific rules for when we put an "and" in between numbers
|
61
|
+
|
62
|
+
It makes sense to manage the numbers by these groups, so
|
63
|
+
I created a method groups_of which will split any integer into
|
64
|
+
groups of a certain size. It returns a hash with the power of ten
|
65
|
+
as the key and the multiplier as the value. E.g:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
31245.groups_of(3)
|
69
|
+
#=> {0=>245,3=>31} #i.e. 31 thousands, and 245 ones
|
70
|
+
|
71
|
+
245.group_of(2)
|
72
|
+
#=> {0=>45,2=>2} #i.e. 2 hundreds, and 45 ones
|
73
|
+
```
|
74
|
+
|
75
|
+
(In Japanese numbers are grouped in groups of 4, so it makes sense to try and
|
76
|
+
separate the language related stuff from the number grouping related stuff)
|
77
|
+
|
78
|
+
Example of usage:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
245.group_words(2,"English") do |power, name, digits|
|
82
|
+
puts "#{digits}*10^#{power} #{digits} #{name}s"
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
2 * 10^2 = 2 hundreds
|
87
|
+
45 * 10^0 = 45 ones
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
Future plans
|
92
|
+
============
|
93
|
+
|
94
|
+
* Handle complex numbers
|
95
|
+
* Option for outputting punctuation
|
96
|
+
* Reject invalid numbers
|
97
|
+
* Support for other languages
|
data/Rakefile
CHANGED
@@ -1,44 +1 @@
|
|
1
|
-
require
|
2
|
-
require 'rake'
|
3
|
-
require 'echoe'
|
4
|
-
require 'metric_fu'
|
5
|
-
|
6
|
-
Echoe.new('numbers_in_words','0.1.1') do |e|
|
7
|
-
e.description = "#in_words method for integers and #in_numbers for strings"
|
8
|
-
e.summary = "Example: 123.in_words #=> \"one hundred and twenty three\", \"seventy-five point eight\".in_numbers #=> 75.8"
|
9
|
-
e.url = "http://rubygems.org/gems/numbers_in_words"
|
10
|
-
e.author = "Mark Burns"
|
11
|
-
e.email = "markthedeveloper@gmail.com"
|
12
|
-
e.ignore_pattern = ["tmp/*",".git/*"]
|
13
|
-
e.dependencies = ['active_support']
|
14
|
-
end
|
15
|
-
|
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
|
-
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/numbers_in_words.rb
CHANGED
@@ -1,7 +1,24 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_support/core_ext/array'
|
2
2
|
|
3
|
-
|
3
|
+
require "numbers_in_words/version"
|
4
|
+
require 'numbers_in_words/language_writer'
|
4
5
|
|
5
|
-
require '
|
6
|
-
require '
|
6
|
+
require 'numbers_in_words/english/constants'
|
7
|
+
require 'numbers_in_words/english/language_writer_english'
|
8
|
+
|
9
|
+
require 'numbers_in_words/number_group'
|
10
|
+
require 'numbers_in_words/number_parser'
|
11
|
+
require 'numbers_in_words/to_number'
|
12
|
+
require 'numbers_in_words/to_word'
|
13
|
+
|
14
|
+
module NumbersInWords
|
15
|
+
LENGTH_OF_GOOGOL = 101 #length of the string i.e. one with 100 zeros
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_writer :language
|
19
|
+
def language
|
20
|
+
@language ||= "English"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
7
24
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module NumbersInWords
|
2
|
+
def in_words language=NumbersInWords.language
|
3
|
+
NumbersInWords::ToWord.new(self, language).in_words
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module WordsInNumbers
|
8
|
+
def in_numbers language=NumbersInWords.language
|
9
|
+
NumbersInWords::ToNumber.new(self, language).in_numbers
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
include WordsInNumbers
|
15
|
+
end
|
16
|
+
|
17
|
+
class Numeric
|
18
|
+
include NumbersInWords
|
19
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module NumbersInWords
|
2
|
+
module English
|
3
|
+
def self.exceptions
|
4
|
+
{
|
5
|
+
0 => "zero",
|
6
|
+
1 => "one",
|
7
|
+
2 => "two",
|
8
|
+
3 => "three",
|
9
|
+
4 => "four",
|
10
|
+
5 => "five",
|
11
|
+
6 => "six",
|
12
|
+
7 => "seven",
|
13
|
+
8 => "eight",
|
14
|
+
9 => "nine",
|
15
|
+
|
16
|
+
10 => "ten",
|
17
|
+
11 => "eleven",
|
18
|
+
12 => "twelve",
|
19
|
+
|
20
|
+
13 => "thirteen",
|
21
|
+
14 => "fourteen",
|
22
|
+
15 => "fifteen",
|
23
|
+
16 => "sixteen" ,
|
24
|
+
17 => "seventeen",
|
25
|
+
18 => "eighteen",
|
26
|
+
19 => "nineteen",
|
27
|
+
|
28
|
+
20 => "twenty",
|
29
|
+
30 => "thirty",
|
30
|
+
40 => "forty",
|
31
|
+
50 => "fifty",
|
32
|
+
60 => "sixty",
|
33
|
+
70 => "seventy",
|
34
|
+
80 => "eighty",
|
35
|
+
90 => "ninety"
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.swap_keys hsh
|
40
|
+
hsh.inject({}){|h,(k,v)| h[v]=k; h}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.powers_of_ten
|
44
|
+
{
|
45
|
+
0 => "one",
|
46
|
+
1 => "ten",
|
47
|
+
2 => "hundred",
|
48
|
+
3 => "thousand",
|
49
|
+
6 => "million",
|
50
|
+
9 => "billion",
|
51
|
+
12 => "trillion",
|
52
|
+
15 => "quadrillion",
|
53
|
+
18 => "quintillion",
|
54
|
+
21 => "sextillion",
|
55
|
+
24 => "septillion",
|
56
|
+
27 => "octillion",
|
57
|
+
30 => "nonillion",
|
58
|
+
33 => "decillion",
|
59
|
+
36 => "undecillion",
|
60
|
+
39 => "duodecillion",
|
61
|
+
42 => "tredecillion",
|
62
|
+
45 => "quattuordecillion",
|
63
|
+
48 => "quindecillion",
|
64
|
+
51 => "sexdecillion",
|
65
|
+
54 => "septendecillion",
|
66
|
+
57 => "octodecillion",
|
67
|
+
60 => "novemdecillion",
|
68
|
+
63 => "vigintillion",
|
69
|
+
66 => "unvigintillion",
|
70
|
+
69 => "duovigintillion",
|
71
|
+
72 => "trevigintillion",
|
72
|
+
75 => "quattuorvigintillion",
|
73
|
+
78 => "quinvigintillion",
|
74
|
+
81 => "sexvigintillion",
|
75
|
+
84 => "septenvigintillion",
|
76
|
+
87 => "octovigintillion",
|
77
|
+
90 => "novemvigintillion",
|
78
|
+
93 => "trigintillion",
|
79
|
+
96 => "untrigintillion",
|
80
|
+
99 => "duotrigintillion",
|
81
|
+
100 => "googol"
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.exceptions_to_i
|
86
|
+
swap_keys exceptions
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.powers_of_ten_to_i
|
90
|
+
swap_keys powers_of_ten
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module NumbersInWords
|
2
|
+
module English
|
3
|
+
class LanguageWriterEnglish < LanguageWriter
|
4
|
+
delegate :to_i, to: :that
|
5
|
+
|
6
|
+
def initialize that
|
7
|
+
super that
|
8
|
+
@language = "English"
|
9
|
+
end
|
10
|
+
|
11
|
+
def negative
|
12
|
+
"minus " + (-@that).in_words
|
13
|
+
end
|
14
|
+
|
15
|
+
def in_words
|
16
|
+
v = handle_exception
|
17
|
+
return v if v
|
18
|
+
|
19
|
+
in_decimals = decimals
|
20
|
+
return in_decimals if in_decimals
|
21
|
+
|
22
|
+
number = to_i
|
23
|
+
|
24
|
+
return negative() if number < 0
|
25
|
+
|
26
|
+
length = number.to_s.length
|
27
|
+
output = ""
|
28
|
+
|
29
|
+
if length == 2 #20-99
|
30
|
+
tens = (number/10).round*10 #write the tens
|
31
|
+
|
32
|
+
output << exceptions[tens] # e.g. eighty
|
33
|
+
|
34
|
+
digit = number - tens #write the digits
|
35
|
+
|
36
|
+
output << " " + digit.in_words unless digit==0
|
37
|
+
else
|
38
|
+
output << write() #longer numbers
|
39
|
+
end
|
40
|
+
|
41
|
+
output.strip
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_exception
|
46
|
+
exceptions[@that] if @that.is_a?(Integer) and exceptions[@that]
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def write
|
51
|
+
length = @that.to_s.length
|
52
|
+
output =
|
53
|
+
if length == 3
|
54
|
+
#e.g. 113 splits into "one hundred" and "thirteen"
|
55
|
+
write_groups(2)
|
56
|
+
|
57
|
+
#more than one hundred less than one googol
|
58
|
+
elsif length < LENGTH_OF_GOOGOL
|
59
|
+
write_groups(3)
|
60
|
+
|
61
|
+
elsif length >= LENGTH_OF_GOOGOL
|
62
|
+
write_googols
|
63
|
+
end
|
64
|
+
output.strip
|
65
|
+
end
|
66
|
+
|
67
|
+
def decimals
|
68
|
+
int, decimals = NumberGroup.new(@that).split_decimals
|
69
|
+
if int
|
70
|
+
out = int.in_words + " point "
|
71
|
+
decimals.each do |decimal|
|
72
|
+
out << decimal.to_i.in_words + " "
|
73
|
+
end
|
74
|
+
out.strip
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def write_googols
|
80
|
+
googols, remainder = NumberGroup.new(@that).split_googols
|
81
|
+
output = ""
|
82
|
+
output << " " + googols.in_words + " googol"
|
83
|
+
if remainder > 0
|
84
|
+
prefix = " "
|
85
|
+
prefix << "and " if remainder < 100
|
86
|
+
output << prefix + remainder.in_words
|
87
|
+
end
|
88
|
+
output
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_groups group
|
92
|
+
#e.g. 113 splits into "one hundred" and "thirteen"
|
93
|
+
output = ""
|
94
|
+
group_words(group) do |power, name, digits|
|
95
|
+
if digits > 0
|
96
|
+
prefix = " "
|
97
|
+
#no and between thousands and hundreds
|
98
|
+
prefix << "and " if power == 0 and digits < 100
|
99
|
+
output << prefix + digits.in_words
|
100
|
+
output << prefix + name unless power == 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
output
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|