castwide_numbers_in_words 1.0.2
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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +14 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +58 -0
- data/.travis.yml +17 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +24 -0
- data/NUMBERS_IN_WORDS.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +3 -0
- data/bin/spec +2 -0
- data/castwide_numbers_in_words.gemspec +24 -0
- data/lib/numbers_in_words/duck_punch.rb +23 -0
- data/lib/numbers_in_words/exceptional_numbers.rb +115 -0
- data/lib/numbers_in_words/fraction.rb +136 -0
- data/lib/numbers_in_words/number_group.rb +64 -0
- data/lib/numbers_in_words/parsing/fraction_parsing.rb +31 -0
- data/lib/numbers_in_words/parsing/number_parser.rb +98 -0
- data/lib/numbers_in_words/parsing/pair_parsing.rb +64 -0
- data/lib/numbers_in_words/parsing/parse_fractions.rb +45 -0
- data/lib/numbers_in_words/parsing/parse_individual_number.rb +68 -0
- data/lib/numbers_in_words/parsing/parse_status.rb +17 -0
- data/lib/numbers_in_words/parsing/special.rb +67 -0
- data/lib/numbers_in_words/parsing/to_number.rb +77 -0
- data/lib/numbers_in_words/powers_of_ten.rb +49 -0
- data/lib/numbers_in_words/to_word.rb +84 -0
- data/lib/numbers_in_words/version.rb +5 -0
- data/lib/numbers_in_words/writer.rb +69 -0
- data/lib/numbers_in_words.rb +58 -0
- data/spec/exceptional_numbers_spec.rb +26 -0
- data/spec/fraction_spec.rb +152 -0
- data/spec/fractions_spec.rb +31 -0
- data/spec/non_monkey_patch_spec.rb +51 -0
- data/spec/number_group_spec.rb +17 -0
- data/spec/number_parser_spec.rb +31 -0
- data/spec/numbers_in_words_spec.rb +99 -0
- data/spec/numerical_strings_spec.rb +35 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/to_word_spec.rb +18 -0
- data/spec/words_in_numbers_spec.rb +152 -0
- data/spec/writer_spec.rb +26 -0
- data/spec/years_spec.rb +27 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7d91888df88e8fc7610f529b7cbe3871f2d655094becfaf76ce0f20a520d1c22
|
4
|
+
data.tar.gz: 2071d799552a79eaa52c891e0508d0835832b9bea52d870391cd78bd0b17e535
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 286ad2a50878696c6841879653f2ee86bc0e0f3b1a7e5dc5e46f0e962cbc29fbf26385fadfdab81088336e435aec658e7d3884d1bf1bab8c6e9e3434b7ab9bbf
|
7
|
+
data.tar.gz: 3e94b737d38930ab1fb5559c455d24207d7b6226133155bf9558252e2695018cb414018c38fe3ed866c2b0e648fcc8433e2d6c261ad385609bd112ff768ed644
|
data/.codeclimate.yml
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# The behavior of RuboCop can be controlled via the .rubocop.yml
|
2
|
+
# configuration file. It makes it possible to enable/disable
|
3
|
+
# certain cops (checks) and to alter their behavior if they accept
|
4
|
+
# any parameters. The file can be placed either in your home
|
5
|
+
# directory or in some project directory.
|
6
|
+
#
|
7
|
+
# RuboCop will start looking for the configuration file in the directory
|
8
|
+
# where the inspected file is and continue its way up to the root directory.
|
9
|
+
#
|
10
|
+
# See https://docs.rubocop.org/rubocop/configuration
|
11
|
+
Layout/EmptyLinesAroundAttributeAccessor:
|
12
|
+
Enabled: true
|
13
|
+
Layout/SpaceAroundMethodCallOperator:
|
14
|
+
Enabled: true
|
15
|
+
Lint/DeprecatedOpenSSLConstant:
|
16
|
+
Enabled: true
|
17
|
+
Lint/MixedRegexpCaptureTypes:
|
18
|
+
Enabled: true
|
19
|
+
Lint/RaiseException:
|
20
|
+
Enabled: true
|
21
|
+
Lint/StructNewOverride:
|
22
|
+
Enabled: true
|
23
|
+
Style/ExponentialNotation:
|
24
|
+
Enabled: true
|
25
|
+
Style/HashEachMethods:
|
26
|
+
Enabled: true
|
27
|
+
Style/HashTransformKeys:
|
28
|
+
Enabled: true
|
29
|
+
Style/HashTransformValues:
|
30
|
+
Enabled: true
|
31
|
+
Style/RedundantFetchBlock:
|
32
|
+
Enabled: true
|
33
|
+
Style/RedundantRegexpCharacterClass:
|
34
|
+
Enabled: true
|
35
|
+
Style/RedundantRegexpEscape:
|
36
|
+
Enabled: true
|
37
|
+
Style/SlicingWithRange:
|
38
|
+
Enabled: true
|
39
|
+
Lint/DuplicateElsifCondition:
|
40
|
+
Enabled: true
|
41
|
+
Style/AccessorGrouping:
|
42
|
+
Enabled: true
|
43
|
+
Style/ArrayCoercion:
|
44
|
+
Enabled: true
|
45
|
+
Style/BisectedAttrAccessor:
|
46
|
+
Enabled: true
|
47
|
+
Style/CaseLikeIf:
|
48
|
+
Enabled: true
|
49
|
+
Style/Documentation:
|
50
|
+
Enabled: false
|
51
|
+
Style/HashAsLastArrayItem:
|
52
|
+
Enabled: true
|
53
|
+
Style/HashLikeCase:
|
54
|
+
Enabled: true
|
55
|
+
Style/RedundantAssignment:
|
56
|
+
Enabled: true
|
57
|
+
Style/RedundantFileExtensionInRequire:
|
58
|
+
Enabled: true
|
data/.travis.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
env:
|
2
|
+
global:
|
3
|
+
- CC_TEST_REPORTER_ID=9a548b48c1c0804d8fb9083ae8e9693a5d5197eb86e8cf1b63a89d18f33e0464
|
4
|
+
sudo: false
|
5
|
+
language: ruby
|
6
|
+
rvm:
|
7
|
+
- "2.4.10"
|
8
|
+
- "2.5.8"
|
9
|
+
- "2.6.6"
|
10
|
+
- "2.7.1"
|
11
|
+
before_script:
|
12
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
13
|
+
- chmod +x ./cc-test-reporter
|
14
|
+
- ./cc-test-reporter before-build
|
15
|
+
script: ./bin/spec
|
16
|
+
after_script:
|
17
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
castwide_numbers_in_words (1.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.3)
|
10
|
+
byebug (12.0.0)
|
11
|
+
diff-lcs (1.6.2)
|
12
|
+
docile (1.4.1)
|
13
|
+
json (2.13.2)
|
14
|
+
language_server-protocol (3.17.0.5)
|
15
|
+
lint_roller (1.1.0)
|
16
|
+
parallel (1.27.0)
|
17
|
+
parser (3.3.9.0)
|
18
|
+
ast (~> 2.4.1)
|
19
|
+
racc
|
20
|
+
prism (1.4.0)
|
21
|
+
racc (1.8.1)
|
22
|
+
rainbow (3.1.1)
|
23
|
+
regexp_parser (2.10.0)
|
24
|
+
rspec (3.4.0)
|
25
|
+
rspec-core (~> 3.4.0)
|
26
|
+
rspec-expectations (~> 3.4.0)
|
27
|
+
rspec-mocks (~> 3.4.0)
|
28
|
+
rspec-core (3.4.4)
|
29
|
+
rspec-support (~> 3.4.0)
|
30
|
+
rspec-expectations (3.4.0)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.4.0)
|
33
|
+
rspec-mocks (3.4.1)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.4.0)
|
36
|
+
rspec-support (3.4.1)
|
37
|
+
rubocop (1.79.0)
|
38
|
+
json (~> 2.3)
|
39
|
+
language_server-protocol (~> 3.17.0.2)
|
40
|
+
lint_roller (~> 1.1.0)
|
41
|
+
parallel (~> 1.10)
|
42
|
+
parser (>= 3.3.0.2)
|
43
|
+
rainbow (>= 2.2.2, < 4.0)
|
44
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
45
|
+
rubocop-ast (>= 1.46.0, < 2.0)
|
46
|
+
ruby-progressbar (~> 1.7)
|
47
|
+
tsort (>= 0.2.0)
|
48
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
49
|
+
rubocop-ast (1.46.0)
|
50
|
+
parser (>= 3.3.7.2)
|
51
|
+
prism (~> 1.4)
|
52
|
+
ruby-progressbar (1.13.0)
|
53
|
+
simplecov (0.17.1)
|
54
|
+
docile (~> 1.1)
|
55
|
+
json (>= 1.8, < 3)
|
56
|
+
simplecov-html (~> 0.10.0)
|
57
|
+
simplecov-html (0.10.2)
|
58
|
+
tsort (0.2.0)
|
59
|
+
unicode-display_width (3.1.4)
|
60
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
61
|
+
unicode-emoji (4.0.4)
|
62
|
+
|
63
|
+
PLATFORMS
|
64
|
+
x64-mingw-ucrt
|
65
|
+
|
66
|
+
DEPENDENCIES
|
67
|
+
byebug
|
68
|
+
castwide_numbers_in_words!
|
69
|
+
rspec (~> 3.4.0)
|
70
|
+
rubocop
|
71
|
+
simplecov (< 0.18.0)
|
72
|
+
|
73
|
+
BUNDLED WITH
|
74
|
+
2.6.9
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Adapted from numbers_in_words by Mark Burns (see NUMBERS_IN_WORDS.txt)
|
2
|
+
|
3
|
+
Copyright (c) 2025 by Fred Snyder
|
4
|
+
|
5
|
+
MIT License
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -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,47 @@
|
|
1
|
+
**This is a fork of the [numbers_in_words gem by Mark Burns](https://github.com/markburns/numbers_in_words).** The original license is reproduced in [NUMBERS_IN_WORDS.txt](NUMBERS_IN_WORDS.txt).
|
2
|
+
|
3
|
+
Changes in this fork:
|
4
|
+
* Compatibility with [Opal](https://github.com/opal/opal)
|
5
|
+
* Updates for Ruby 3
|
6
|
+
|
7
|
+
Installation
|
8
|
+
============
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'castwide_numbers_in_words'
|
12
|
+
```
|
13
|
+
|
14
|
+
Usage
|
15
|
+
=========
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'numbers_in_words'
|
19
|
+
|
20
|
+
NumbersInWords.in_words(112)
|
21
|
+
#=> one hundred and twelve
|
22
|
+
|
23
|
+
NumbersInWords.in_numbers("one googol")
|
24
|
+
#=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
25
|
+
|
26
|
+
NumbersInWords.in_numbers("Seventy million, five-hundred and fifty six thousand point eight nine three")
|
27
|
+
#=> 70556000.893
|
28
|
+
|
29
|
+
NumbersInWords.in_numbers("nineteen sixty five")
|
30
|
+
#=> 1965
|
31
|
+
```
|
32
|
+
|
33
|
+
|
34
|
+
Monkey patch version
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'numbers_in_words'
|
38
|
+
require 'numbers_in_words/duck_punch'
|
39
|
+
112.in_words
|
40
|
+
#=> one hundred and twelve
|
41
|
+
|
42
|
+
"one googol".in_numbers
|
43
|
+
#=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
44
|
+
|
45
|
+
"Seventy million, five-hundred and fifty six thousand point eight nine three".in_numbers
|
46
|
+
#=> 70556000.893
|
47
|
+
```
|
data/Rakefile
ADDED
data/bin/spec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'numbers_in_words/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = 'castwide_numbers_in_words'
|
9
|
+
gem.description = 'convert written numbers into Integers and vice-versa'
|
10
|
+
gem.summary = 'Example: NumbersInWords.in_words(123) # => ' \
|
11
|
+
'"one hundred and twenty three", NumbersInWords.in_numbers("seventy-five point eight") # = > 75.8'
|
12
|
+
|
13
|
+
gem.version = NumbersInWords::VERSION
|
14
|
+
gem.authors = ['Fred Snyder']
|
15
|
+
gem.email = ['castwide@gmail.com']
|
16
|
+
gem.homepage = 'http://github.com/castwide/castwide_numbers_in_words'
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec', '~> 3.4.0'
|
19
|
+
gem.add_development_dependency 'rubocop'
|
20
|
+
|
21
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
22
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
23
|
+
gem.require_paths = ['lib']
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NumbersInWords
|
4
|
+
module NumericExtension
|
5
|
+
def in_words(fraction: false)
|
6
|
+
NumbersInWords::ToWord.new(self).in_words(fraction: fraction)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module StringExtension
|
11
|
+
def in_numbers(only_compress: false)
|
12
|
+
NumbersInWords::ToNumber.new(self, only_compress).call
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class String
|
18
|
+
include NumbersInWords::StringExtension
|
19
|
+
end
|
20
|
+
|
21
|
+
class Numeric
|
22
|
+
include NumbersInWords::NumericExtension
|
23
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require_relative 'fraction'
|
6
|
+
require_relative 'powers_of_ten'
|
7
|
+
|
8
|
+
module NumbersInWords
|
9
|
+
class ExceptionalNumbers
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
DEFINITIONS = {
|
13
|
+
0 => { number: 'zero', ordinal: 'zeroth' },
|
14
|
+
1 => { number: 'one', ordinal: 'first' },
|
15
|
+
2 => { number: 'two', ordinal: 'second', fraction: { singular: 'half', plural: 'halves' } },
|
16
|
+
3 => { number: 'three', ordinal: 'third' },
|
17
|
+
4 => { number: 'four', ordinal: 'fourth', fraction: { singular: 'quarter', plural: 'quarters' } },
|
18
|
+
5 => { number: 'five', ordinal: 'fifth' },
|
19
|
+
6 => { number: 'six' },
|
20
|
+
7 => { number: 'seven' },
|
21
|
+
8 => { number: 'eight', ordinal: 'eighth' },
|
22
|
+
9 => { number: 'nine', ordinal: 'ninth' },
|
23
|
+
10 => { number: 'ten' },
|
24
|
+
11 => { number: 'eleven' },
|
25
|
+
12 => { number: 'twelve', ordinal: 'twelfth' },
|
26
|
+
13 => { number: 'thirteen' },
|
27
|
+
14 => { number: 'fourteen' },
|
28
|
+
15 => { number: 'fifteen' },
|
29
|
+
16 => { number: 'sixteen' },
|
30
|
+
17 => { number: 'seventeen' },
|
31
|
+
18 => { number: 'eighteen' },
|
32
|
+
19 => { number: 'nineteen' },
|
33
|
+
20 => { number: 'twenty', ordinal: 'twentieth' },
|
34
|
+
30 => { number: 'thirty', ordinal: 'thirtieth' },
|
35
|
+
40 => { number: 'forty', ordinal: 'fortieth' },
|
36
|
+
50 => { number: 'fifty', ordinal: 'fiftieth' },
|
37
|
+
60 => { number: 'sixty', ordinal: 'sixtieth' },
|
38
|
+
70 => { number: 'seventy', ordinal: 'seventieth' },
|
39
|
+
80 => { number: 'eighty', ordinal: 'eightieth' },
|
40
|
+
90 => { number: 'ninety', ordinal: 'ninetieth' }
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
def fraction_names
|
44
|
+
@fraction_names ||= determine_fraction_names
|
45
|
+
end
|
46
|
+
|
47
|
+
def lookup_fraction(words)
|
48
|
+
fraction_lookup[words]
|
49
|
+
end
|
50
|
+
|
51
|
+
def fraction_lookup
|
52
|
+
@fraction_lookup ||= generate_fraction_lookup
|
53
|
+
end
|
54
|
+
|
55
|
+
def lookup(number)
|
56
|
+
to_h[number]
|
57
|
+
end
|
58
|
+
|
59
|
+
def fraction(denominator: nil, numerator: nil, word: nil)
|
60
|
+
raise unless denominator || word
|
61
|
+
|
62
|
+
numerator ||= 1
|
63
|
+
|
64
|
+
denominator ||= NumbersInWords.in_numbers(word)
|
65
|
+
|
66
|
+
Fraction.new(denominator: denominator, numerator: numerator, attributes: DEFINITIONS[denominator])
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_h
|
70
|
+
@to_h ||= DEFINITIONS.transform_values do |h|
|
71
|
+
h[:number]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def generate_fraction_lookup
|
78
|
+
named_fractions.each_with_object({}) do |f, result|
|
79
|
+
f.lookup_keys.each do |k|
|
80
|
+
key = k.split(' ').last
|
81
|
+
result[key] = 1.0 / f.denominator.to_f
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def named_fractions
|
87
|
+
@named_fractions ||= numbers.flat_map do |n|
|
88
|
+
[
|
89
|
+
Fraction.new(denominator: n, numerator: 1),
|
90
|
+
Fraction.new(denominator: n, numerator: 2)
|
91
|
+
]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def numbers
|
96
|
+
(2..100).to_a + powers_of_ten_skipping_googolplex.map { |p| 10**p }
|
97
|
+
end
|
98
|
+
|
99
|
+
def powers_of_ten_skipping_googolplex
|
100
|
+
POWERS_OF_TEN.keys[0..-2]
|
101
|
+
end
|
102
|
+
|
103
|
+
def determine_fraction_names
|
104
|
+
names = named_fractions.map(&:in_words)
|
105
|
+
|
106
|
+
words = names.map(&:split).map(&:last)
|
107
|
+
words += strip_punctuation(words)
|
108
|
+
words.uniq
|
109
|
+
end
|
110
|
+
|
111
|
+
def strip_punctuation(words)
|
112
|
+
words.map { |w| w.gsub(/^a-z/, ' ') }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NumbersInWords
|
4
|
+
class Fraction
|
5
|
+
attr_reader :denominator, :numerator, :attributes
|
6
|
+
|
7
|
+
def self.in_words(that)
|
8
|
+
r = that.rationalize(EPSILON)
|
9
|
+
|
10
|
+
NumbersInWords
|
11
|
+
.fraction(denominator: r.denominator, numerator: r.numerator)
|
12
|
+
.in_words
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(denominator:, numerator: 1, attributes: nil)
|
16
|
+
@denominator = denominator
|
17
|
+
@numerator = numerator
|
18
|
+
@attributes = attributes || NumbersInWords::ExceptionalNumbers::DEFINITIONS[denominator] || {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def lookup_keys
|
22
|
+
key = in_words
|
23
|
+
key2 = strip_punctuation(key.split(' ')).join(' ')
|
24
|
+
|
25
|
+
key3 = "a #{key}"
|
26
|
+
key4 = "an #{key}"
|
27
|
+
key5 = "a #{key2}"
|
28
|
+
key6 = "an #{key2}"
|
29
|
+
[key, key2, key3, key4, key5, key6].uniq
|
30
|
+
end
|
31
|
+
|
32
|
+
def in_words
|
33
|
+
NumbersInWords.in_words(numerator) + ' ' + fraction
|
34
|
+
end
|
35
|
+
|
36
|
+
def ordinal
|
37
|
+
pluralize? ? pluralized_ordinal_in_words : singular_ordinal_in_words
|
38
|
+
end
|
39
|
+
|
40
|
+
def fraction
|
41
|
+
if denominator == Float::INFINITY
|
42
|
+
# We've reached the limits of ruby's number system
|
43
|
+
# by the time we get to a googolplex (10 ** (10 ** 100))
|
44
|
+
return pluralize? ? 'infinitieths' : 'infinitieth'
|
45
|
+
end
|
46
|
+
|
47
|
+
pluralize? ? pluralized_fraction : singular_fraction
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def strip_punctuation(words)
|
53
|
+
words.map { |w| w.gsub(/^a-z/, ' ') }
|
54
|
+
end
|
55
|
+
|
56
|
+
def pluralized_fraction
|
57
|
+
fraction_plural || pluralized_ordinal_in_words
|
58
|
+
end
|
59
|
+
|
60
|
+
def singular_fraction
|
61
|
+
fraction_singular || singular_ordinal_in_words
|
62
|
+
end
|
63
|
+
|
64
|
+
def pluralized_ordinal_in_words
|
65
|
+
pluralized_ordinal || denominator_ordinal_in_words
|
66
|
+
end
|
67
|
+
|
68
|
+
def singular_ordinal_in_words
|
69
|
+
singular_ordinal || denominator_ordinal_in_words
|
70
|
+
end
|
71
|
+
|
72
|
+
def singular_ordinal
|
73
|
+
attributes[:ordinal]
|
74
|
+
end
|
75
|
+
|
76
|
+
def pluralized_ordinal
|
77
|
+
singular_ordinal && singular_ordinal + 's'
|
78
|
+
end
|
79
|
+
|
80
|
+
def pluralize?
|
81
|
+
numerator > 1
|
82
|
+
end
|
83
|
+
|
84
|
+
def denominator_ordinal_in_words
|
85
|
+
if denominator > 100
|
86
|
+
# one hundred and second
|
87
|
+
with_remainder(100, ' and ')
|
88
|
+
elsif denominator > 19
|
89
|
+
# two thirty-fifths
|
90
|
+
with_remainder(10, '-')
|
91
|
+
else
|
92
|
+
# one seventh
|
93
|
+
singular = NumbersInWords.in_words(denominator) + 'th'
|
94
|
+
pluralize? ? singular + 's' : singular
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def with_remainder(mod, join_word)
|
99
|
+
rest = denominator % mod
|
100
|
+
main = denominator - rest
|
101
|
+
main = NumbersInWords.in_words(main)
|
102
|
+
|
103
|
+
main = main.gsub(/^one /, '') if pluralize?
|
104
|
+
|
105
|
+
rest_zero(rest, main) || joined(main, rest, join_word)
|
106
|
+
end
|
107
|
+
|
108
|
+
def joined(main, rest, join_word)
|
109
|
+
main +
|
110
|
+
join_word +
|
111
|
+
self.class.new(numerator: numerator, denominator: rest).ordinal
|
112
|
+
end
|
113
|
+
|
114
|
+
def rest_zero(rest, main)
|
115
|
+
return unless rest.zero?
|
116
|
+
|
117
|
+
if pluralize?
|
118
|
+
main + 'ths'
|
119
|
+
else
|
120
|
+
main + 'th'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def exception
|
125
|
+
attributes[:fraction]
|
126
|
+
end
|
127
|
+
|
128
|
+
def fraction_singular
|
129
|
+
exception && exception[:singular]
|
130
|
+
end
|
131
|
+
|
132
|
+
def fraction_plural
|
133
|
+
exception && exception[:plural]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NumbersInWords
|
4
|
+
class NumberGroup
|
5
|
+
include Enumerable
|
6
|
+
attr_accessor :number
|
7
|
+
|
8
|
+
def self.groups_of(number, size)
|
9
|
+
new(number).groups(size)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(number)
|
13
|
+
@number = number
|
14
|
+
end
|
15
|
+
|
16
|
+
# split into groups this gives us 1234567 => 123 456 7
|
17
|
+
# so we need to reverse first
|
18
|
+
# in stages
|
19
|
+
def groups(size)
|
20
|
+
# 1234567 => %w(765 432 1)
|
21
|
+
@array = in_groups_of(@number.to_s.reverse.split(''), size)
|
22
|
+
# %w(765 432 1) => %w(1 432 765)
|
23
|
+
@array.reverse!
|
24
|
+
|
25
|
+
# %w(1 432 765) => [1, 234, 567]
|
26
|
+
@array.map! { |group| group.reverse.join('').to_i }
|
27
|
+
@array.reverse! # put in ascending order of power of ten
|
28
|
+
|
29
|
+
power = 0
|
30
|
+
|
31
|
+
# [1, 234, 567] => {6 => 1, 3 => 234, 0 => 567}
|
32
|
+
@array.each_with_object({}) do |digits, o|
|
33
|
+
o[power] = digits
|
34
|
+
power += size
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def split_decimals
|
39
|
+
return unless @number.to_s.include?('.')
|
40
|
+
|
41
|
+
int, decimal = @number.to_s.split '.'
|
42
|
+
|
43
|
+
[int.to_i, decimal.split(//).map(&:to_i)]
|
44
|
+
end
|
45
|
+
|
46
|
+
def split_googols
|
47
|
+
googols = @number.to_s[0..-LENGTH_OF_GOOGOL].to_i
|
48
|
+
remainder = @number.to_s[(1 - LENGTH_OF_GOOGOL)..-1].to_i
|
49
|
+
[googols, remainder]
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def in_groups_of(array, number, fill_with = nil)
|
55
|
+
# size % number gives how many extra we have;
|
56
|
+
# subtracting from number gives how many to add;
|
57
|
+
# modulo number ensures we don't add group of just fill.
|
58
|
+
padding = (number - array.size % number) % number
|
59
|
+
collection = array.dup.concat(Array.new(padding, fill_with))
|
60
|
+
|
61
|
+
collection.each_slice(number).to_a
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|