numerals 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +24 -0
- data/Rakefile +19 -0
- data/lib/numerals/conversions/bigdecimal.rb +30 -0
- data/lib/numerals/conversions/float.rb +226 -0
- data/lib/numerals/conversions/flt.rb +162 -0
- data/lib/numerals/conversions/integer.rb +39 -0
- data/lib/numerals/conversions/rational.rb +32 -0
- data/lib/numerals/conversions.rb +57 -0
- data/lib/numerals/digits.rb +99 -0
- data/lib/numerals/formatting/digits_definition.rb +75 -0
- data/lib/numerals/formatting/options.rb +84 -0
- data/lib/numerals/numeral.rb +650 -0
- data/lib/numerals/rounding.rb +229 -0
- data/lib/numerals/support.rb +10 -0
- data/lib/numerals/version.rb +3 -0
- data/lib/numerals.rb +12 -0
- data/numerals.gemspec +26 -0
- data/test/data.yaml +101 -0
- data/test/helper.rb +40 -0
- data/test/test_digits_definition.rb +110 -0
- data/test/test_float_conversions.rb +58 -0
- data/test/test_flt_conversions.rb +277 -0
- data/test/test_integer_conversions.rb +50 -0
- data/test/test_numeral.rb +366 -0
- data/test/test_rational_conversions.rb +75 -0
- data/test/test_rounding.rb +77 -0
- metadata +138 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Numerals
|
4
|
+
|
5
|
+
# Sequence of digit values type, with an Array-compatible interface
|
6
|
+
# Having this encapsulated here allow to change the implementation
|
7
|
+
# e.g. to an Integer or packed in a String, ...
|
8
|
+
class Digits
|
9
|
+
def initialize(*args)
|
10
|
+
if Hash === args.last
|
11
|
+
options = args.pop
|
12
|
+
else
|
13
|
+
options = {}
|
14
|
+
end
|
15
|
+
@radix = options[:base] || options[:radix] || 10
|
16
|
+
if args.size == 1 && Array === args.first
|
17
|
+
@digits_array = args.first
|
18
|
+
else
|
19
|
+
@digits_array = args
|
20
|
+
end
|
21
|
+
if options[:value]
|
22
|
+
self.value = options[:value]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
include ModalSupport::BracketConstructor
|
27
|
+
|
28
|
+
attr_reader :digits_array, :radix
|
29
|
+
|
30
|
+
extend Forwardable
|
31
|
+
def_delegators :@digits_array,
|
32
|
+
:size, :map, :pop, :push, :shift, :unshift,
|
33
|
+
:empty?, :first, :last, :any?, :all?, :[]=
|
34
|
+
|
35
|
+
# The [] with a Range argument or two arguments (index, length)
|
36
|
+
# returns a Regular Array.
|
37
|
+
def_delegators :@digits_array, :[], :replace
|
38
|
+
include ModalSupport::StateEquivalent # maybe == with Arrays too?
|
39
|
+
|
40
|
+
# This could be changed to have [] return a Digits object (except when a single index is passed).
|
41
|
+
# In that case we would have to define replace, ==, != to accept either Array or Digits arguments
|
42
|
+
# (and also possibly the constructor)
|
43
|
+
|
44
|
+
# Integral coefficient
|
45
|
+
def value
|
46
|
+
if @radix == 10
|
47
|
+
@digits_array.join.to_i
|
48
|
+
else
|
49
|
+
@digits_array.inject(0){|x,y| x*@radix + y}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def value=(v)
|
54
|
+
raise "Invalid digits value" if v < 0
|
55
|
+
if @radix < 37
|
56
|
+
replace v.to_s(@radix).each_char.map{|c| c.to_i(@radix)}
|
57
|
+
else
|
58
|
+
if v == 0
|
59
|
+
replace [0]
|
60
|
+
else
|
61
|
+
while v > 0
|
62
|
+
v, r = v.divmod(@radix)
|
63
|
+
unshift r
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def zero?
|
70
|
+
# value == 0
|
71
|
+
@digits && @digits.all?{|d| d==0 }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Deep copy
|
75
|
+
def dup
|
76
|
+
Digits[@digits_array.dup, base: @radix]
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
args = ""
|
81
|
+
if @digits_array.size > 0
|
82
|
+
args << @digits_array.to_s.unwrap('[]')
|
83
|
+
args << ', '
|
84
|
+
end
|
85
|
+
args << "base: #{radix}"
|
86
|
+
"Digits[#{args}]"
|
87
|
+
end
|
88
|
+
|
89
|
+
def inspect
|
90
|
+
to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def truncate!(n)
|
94
|
+
@digits_array.slice! n..-1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Numerals
|
2
|
+
|
3
|
+
# Digits definition (symbols used as digits)
|
4
|
+
class DigitsDefinition
|
5
|
+
include ModalSupport::StateEquivalent
|
6
|
+
include ModalSupport::BracketConstructor
|
7
|
+
|
8
|
+
DEFAULT_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
9
|
+
DEFAULT_BASE = 10
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
if String === args.first
|
13
|
+
digits = args.shift
|
14
|
+
end
|
15
|
+
options = args.shift || {}
|
16
|
+
raise NumeralError, "Invalid DigitsDefinition" unless args.empty? && Hash === options
|
17
|
+
digits ||= options[:digits]
|
18
|
+
base = options[:base]
|
19
|
+
if base
|
20
|
+
if digits
|
21
|
+
raise NumeralError, "Inconsistent DigitsDefinition" unless digits.size == base
|
22
|
+
end
|
23
|
+
elsif digits
|
24
|
+
base = digits.size
|
25
|
+
else
|
26
|
+
base = DEFAULT_BASE
|
27
|
+
end
|
28
|
+
digits ||= DEFAULT_DIGITS[0, base]
|
29
|
+
|
30
|
+
@radix = base
|
31
|
+
@digits = digits
|
32
|
+
@case_sensitive = options[:case_sensitive]
|
33
|
+
@downcase = options[:downcase] || (@digits.downcase == @digits)
|
34
|
+
@digits = @digits.downcase if @downcase
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_digit?(digit)
|
38
|
+
digit = set_case(digit)
|
39
|
+
@digits.include?(digit)
|
40
|
+
end
|
41
|
+
|
42
|
+
def digit_value(digit)
|
43
|
+
digit = set_case(digit)
|
44
|
+
@digits.index(digit)
|
45
|
+
end
|
46
|
+
|
47
|
+
def digit_char(v)
|
48
|
+
v >= 0 && v < @radix ? @digits[v] : nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def radix
|
52
|
+
@radix
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid?
|
56
|
+
@digits.none?{|x| x.nil? || x<0 || x>=@radix}
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def set_case(digit_char)
|
62
|
+
if digit_char
|
63
|
+
unless @case_sensitive
|
64
|
+
if @downcase
|
65
|
+
digit_char = digit_char.downcase
|
66
|
+
else
|
67
|
+
digit_char = digit_char.upcase
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
digit_char
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
module Numerals
|
3
|
+
|
4
|
+
# Repeating decimal configuration options
|
5
|
+
class Options
|
6
|
+
include ModalSupport::StateEquivalent
|
7
|
+
include ModalSupport::BracketConstructor
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
options = {
|
11
|
+
# default options
|
12
|
+
delim: ['<', '>'],
|
13
|
+
suffix: '...',
|
14
|
+
sep: '.',
|
15
|
+
grouping: [',', []], # [...,[3]] for thousands separators
|
16
|
+
special: ['NaN', 'Infinity'],
|
17
|
+
digits: nil,
|
18
|
+
signs: ['+', '-'],
|
19
|
+
maximum_number_of_digits: Numeral.maximum_number_of_digits
|
20
|
+
}.merge(options)
|
21
|
+
|
22
|
+
set_delim *Array(options[:delim])
|
23
|
+
set_suffix options[:suffix]
|
24
|
+
set_sep options[:sep]
|
25
|
+
set_grouping *Array(options[:grouping])
|
26
|
+
set_special *Array(options[:special])
|
27
|
+
set_digits *Array(options[:digits])
|
28
|
+
set_signs *Array(options[:signs])
|
29
|
+
@maximum_number_of_digits = options[:maximum_number_of_digits]
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_accessor :begin_rep, :end_rep, :auto_rep, :dec_sep, :grp_sep, :grp, :maximum_number_of_digits
|
33
|
+
attr_accessor :nan_txt, :inf_txt, :plus_sign, :minus_sign
|
34
|
+
|
35
|
+
def set_delim(begin_d, end_d='')
|
36
|
+
@begin_rep = begin_d
|
37
|
+
@end_rep = end_d
|
38
|
+
return self
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_suffix(a)
|
42
|
+
@auto_rep = a
|
43
|
+
return self
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_sep(d)
|
47
|
+
@dec_sep = d
|
48
|
+
return self
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_grouping(sep, g=[])
|
52
|
+
@grp_sep = sep
|
53
|
+
@grp = g
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_special(nan_txt, inf_txt)
|
58
|
+
@nan_txt = nan_txt
|
59
|
+
@inf_txt = inf_txt
|
60
|
+
return self
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_digits(*args)
|
64
|
+
@digits_defined = !args.empty?
|
65
|
+
@digits = DigitsDefinition[*args]
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_signs(plus, minus)
|
70
|
+
@plus_sign = plus
|
71
|
+
@minus_sign = minus
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_accessor :digits
|
75
|
+
|
76
|
+
def digits_defined?
|
77
|
+
@digits_defined
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
DEFAULT_OPTIONS = Options[]
|
83
|
+
|
84
|
+
end
|