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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 20e32ed4a377f88140d75db5dce2070dceca3b30
4
+ data.tar.gz: 835f3416de372cf7fa26f2607b74176b666d88b5
5
+ SHA512:
6
+ metadata.gz: a97323fa538d4e1fe2cc9c57b5fc9bc2e09dcf67260b8a74828be88415d7672efb6f314eefa6e23f41542d739bc291cf0c3f36c8b446eaf82c359b8beb0a7e7a
7
+ data.tar.gz: e894386209dbfbc2e3471a00cf866a39e320a99a00e8f6c467dfa4c3947a87eac6797409036efe4c6446041dbb9d8ad4a1dfeb83a7d8a3ae5522932d5a81d40d
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Javier Goizueta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ numerals
2
+ ========
3
+
4
+ Number representation as text.
5
+
6
+ This will be a successor gem to Nio.
7
+
8
+ Roadmap
9
+ =======
10
+
11
+ Done:
12
+
13
+ * Numeral handles (repeating) numerals in any base with bidirectional
14
+ quotient conversion.
15
+
16
+ * Numerical conversions (numbers to/from Numerals)
17
+
18
+ * Rounding can be applied to Numerals (with rounding options)
19
+
20
+ Pending:
21
+
22
+ * Numerals can be written into text form using Formatting options
23
+
24
+ * Numerals con be read from text form using Formatting options
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ require 'rdoc/task'
11
+ Rake::RDocTask.new do |rdoc|
12
+ version = Numerals::VERSION
13
+
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = "Numerals #{version}"
16
+ rdoc.main = "README.md"
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
@@ -0,0 +1,30 @@
1
+ require 'numerals/conversions'
2
+
3
+ class Numerals::BigDecimalConversion
4
+
5
+ def order_of_magnitude(value, options={})
6
+ base = options[:base] || 10
7
+ if base == 10
8
+ value.exponent
9
+ else
10
+ Conversions.order_of_magnitude(Flt::DecNum(value.to_s), options)
11
+ end
12
+ end
13
+
14
+ def number_to_numeral(number, options={})
15
+ mode = options[:mode] || :fixed
16
+ base = options[:base] || 10
17
+ rounding = options[:rounding] || Rounding[:exact]
18
+
19
+ end
20
+
21
+ def numeral_to_number(numeral, options={})
22
+ mode = options[:mode] || :fixed
23
+
24
+ end
25
+
26
+ end
27
+
28
+ def BigDecimal.numerals_conversion
29
+ Numerals::BigDecimalConversion.new
30
+ end
@@ -0,0 +1,226 @@
1
+ require 'numerals/conversions'
2
+ require 'flt/float'
3
+
4
+ class Numerals::FloatConversion
5
+
6
+ def initialize(options={})
7
+ @type = Float
8
+ @context = @type.context
9
+ options = { use_native_float: true }.merge(options)
10
+ @use_native_float = options[:use_native_float]
11
+ # @rounding_mode if used for :free numeral to number conversion
12
+ # and should be the implied rounding mode of the invers conversion
13
+ # (number to numeral);
14
+ # TODO: it should be possible to assign it for higher level
15
+ # formatting handling.
16
+ @rounding_mode = @context.rounding
17
+ @honor_rounding = true
18
+ end
19
+
20
+ def order_of_magnitude(value, options={})
21
+ base = options[:base] || 10 # @contex.radix
22
+ if base == 10
23
+ Math.log10(value.abs).floor + 1
24
+ else
25
+ (Math.log(value.abs)/Math.log(base)).floor + 1
26
+ end
27
+ end
28
+
29
+ # mode is either :exact or :approximate
30
+ def number_to_numeral(number, mode, rounding)
31
+ if @context.special?(number)
32
+ special_float_to_numeral(number)
33
+ else
34
+ if mode == :exact
35
+ exact_float_to_numeral number, rounding
36
+ else # mode == :approximate
37
+ approximate_float_to_numeral(number, rounding)
38
+ end
39
+ end
40
+ end
41
+
42
+ def numeral_to_number(numeral, mode)
43
+ if numeral.special?
44
+ special_numeral_to_float numeral
45
+ elsif mode == :fixed
46
+ fixed_numeral_to_float numeral
47
+ else # mode == :free
48
+ free_numeral_to_float numeral
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def special_float_to_numeral(x)
55
+ if x.nan?
56
+ Numeral.nan
57
+ elsif x.infinite?
58
+ Numeral.infinity @context.sign(x)
59
+ end
60
+ end
61
+
62
+ def exact_float_to_numeral(number, rounding)
63
+ quotient = number.to_r
64
+ numeral = Numeral.from_quotient(quotient, base: rounding.base)
65
+ unless rounding.exact?
66
+ numeral = rounding.round(numeral)
67
+ end
68
+ numeral
69
+ end
70
+
71
+ def approximate_float_to_numeral(number, rounding)
72
+ all_digits = !rounding.exact?
73
+ general_float_to_numeral(number, rounding, all_digits)
74
+ end
75
+
76
+ # def fixed_float_to_numeral(number, rounding)
77
+ # # adjust to rounding.precision
78
+ # if rounding.exact?
79
+ # # if simplify
80
+ # # number = @context.rationalize(simplify)
81
+ # # end
82
+ # exact_float_to_numeral number, rounding.base
83
+ # else
84
+ # if rounding.base == 10 && @use_native_float
85
+ # native_float_to_numeral number, rounding
86
+ # else
87
+ # general_float_to_numeral number, rounding, true
88
+ # end
89
+ # end
90
+ # end
91
+
92
+ # def free_float_to_numeral(number, rounding)
93
+ # # free mode ignores output precision (rounding) and
94
+ # # produces the result based only on the number precision
95
+ # rounding = Rounding[:exact, base: rounding.base]
96
+ # general_float_to_numeral number, rounding, false
97
+ # end
98
+
99
+ def native_float_to_numeral(number, rounding)
100
+ need_to_round = (rounding.mode != @context.rounding)
101
+ n = need_to_round ? Float::DECIMAL_DIG : rounding.precision
102
+ txt = format("%.*e", n - 1, number)
103
+ numeral = text_to_numeral(txt, normalize: :approximate) # C-Locale text to numeral...
104
+ numeral = rounding.round(numeral) if need_to_round
105
+ numeral
106
+ end
107
+
108
+ def general_float_to_numeral(x, rounding, all_digits)
109
+ sign, coefficient, exponent = x.split
110
+ precision = x.number_of_digits
111
+ output_base = rounding.base
112
+
113
+ # here rounding_mode should be not the output rounding mode, but the rounding mode used for input
114
+ # we'll assume rounding.mode will be used for input unless it is exact
115
+ rounding_mode = rounding.exact? ? @context.rounding : rounding.mode
116
+ formatter = Flt::Support::Formatter.new(
117
+ @context.radix, @context.etiny, output_base, raise_on_repeat: false
118
+ )
119
+ formatter.format(
120
+ x, coefficient, exponent, rounding_mode, precision, all_digits
121
+ )
122
+
123
+ dec_pos, digits = formatter.digits
124
+ rep_pos = formatter.repeat
125
+ numeral = Numeral[digits, sign: sign, point: dec_pos, rep_pos: rep_pos, base: output_base]
126
+ if all_digits
127
+ numeral = rounding.round(numeral, formatter.round_up)
128
+ end
129
+ numeral
130
+ end
131
+
132
+ def special_numeral_to_float(numeral)
133
+ case numeral.special
134
+ when :nan
135
+ @context.nan
136
+ when :inf
137
+ @context.infinity numeral.sign
138
+ end
139
+ end
140
+
141
+ def fixed_numeral_to_float(numeral)
142
+ # consider:
143
+ # return exact_numeral_to_float(numeral) if numeral.exact?
144
+ if numeral.base == @context.radix
145
+ same_base_numeral_to_float numeral
146
+ else
147
+ # representable_digits: number of numeral.base digits that can always be converted to Float and back
148
+ # to a numeral preserving its value.
149
+ representable_digits = @context.representable_digits(numeral.base)
150
+ k = numeral.scale
151
+ if !@honor_rounding && numeral.digits.size <= representable_digits && k.abs <= representable_digits
152
+ representable_numeral_to_float numeral
153
+ elsif !@honor_rounding && (k > 0 && numeral.point < 2*representable_digits)
154
+ near_representable_numeral_to_float numeral
155
+ elsif numeral.base.modulo(@context.radix) == 0
156
+ conmensurable_base_numeral_to_float numeral
157
+ else
158
+ general_numeral_to_float numeral, :fixed
159
+ end
160
+ end
161
+ end
162
+
163
+ def exact_numeral_to_float(numeral)
164
+ Rational(*numeral.to_quotient).to_f
165
+ end
166
+
167
+ def free_numeral_to_float(numeral)
168
+ # raise "Invalid Conversion" # Float does not support free (arbitrary precision)
169
+ # fixed_numeral_to_float numeral
170
+ # consider:
171
+ # return general_numeral_to_float(numeral, :short) if numeral.exact?
172
+ general_numeral_to_float numeral, :free
173
+ end
174
+
175
+ def same_base_numeral_to_float(numeral)
176
+ sign, coefficient, scale = numeral.split
177
+ @context.Num(sign, coefficient, scale)
178
+ end
179
+
180
+ def representable_numeral_to_float(numeral)
181
+ value, scale = numeral.to_value_scale
182
+ x = value.to_f
183
+ if scale < 0
184
+ x /= Float(numeral.base**-scale)
185
+ else
186
+ x *= Float(numeral.base**scale)
187
+ end
188
+ x
189
+ end
190
+
191
+ def near_representable_numeral_to_float(numeral)
192
+ value, scale = numeral.to_value_scale
193
+ j = scale - numeral.digits.size
194
+ x = value.to_f * Float(numeral.base**(j))
195
+ x *= Float(numeral.base**(scale - j))
196
+ x
197
+ end
198
+
199
+ def conmensurable_base_numeral_to_float(numeral)
200
+ general_numeral_to_float numeral, :fixed
201
+ end
202
+
203
+ def general_numeral_to_float(numeral, mode)
204
+ sign, coefficient, scale = numeral.split
205
+ reader = Flt::Support::Reader.new(mode: mode)
206
+ if mode == :fixed
207
+ rounding_mode = @context.rounding
208
+ else
209
+ rounding_mode = @rounding_mode
210
+ end
211
+ reader.read(@context, rounding_mode, sign, coefficient, scale, numeral.base).tap do
212
+ # @exact = reader.exact?
213
+ end
214
+ end
215
+
216
+ end
217
+
218
+ def Float.numerals_conversion
219
+ Numerals::FloatConversion.new
220
+ end
221
+
222
+ class <<Float.context
223
+ def numerals_conversion
224
+ Numerals::FloatConversion.new
225
+ end
226
+ end
@@ -0,0 +1,162 @@
1
+ require 'numerals/conversions'
2
+ require 'flt'
3
+
4
+ class Numerals::FltConversion
5
+
6
+ def initialize(context_or_type)
7
+ if Class === context_or_type && context_or_type < Flt::Num
8
+ @type = context_or_type
9
+ @context = @type.context
10
+ elsif Flt::Num::ContextBase === context_or_type
11
+ @context = context_or_type
12
+ @type = @context.num_class
13
+ else
14
+ raise "Invalid FltConversion definition"
15
+ end
16
+ # @rounding_mode if used for :free numeral to number conversion
17
+ # and should be the implied rounding mode of the invers conversion
18
+ # (number to numeral);
19
+ # TODO: it should be possible to assign it for higher level
20
+ # formatting handling.
21
+ @rounding_mode = @context.rounding
22
+ end
23
+
24
+ attr_reader :context, :type
25
+
26
+ def order_of_magnitude(value, options={})
27
+ base = options[:base] || 10 # value.num_class.radix
28
+ if value.class.radix == base
29
+ value.adjusted_exponent + 1
30
+ else
31
+ value.abs.log(base).floor + 1
32
+ end
33
+ end
34
+
35
+ # mode is either :exact or :approximate
36
+ def number_to_numeral(number, mode, rounding)
37
+ if number.special? # @context.special?(number)
38
+ special_num_to_numeral(number)
39
+ else
40
+ if mode == :exact
41
+ exact_num_to_numeral number, rounding
42
+ else # mode == :approximate
43
+ approximate_num_to_numeral(number, rounding)
44
+ end
45
+ end
46
+ end
47
+
48
+ def numeral_to_number(numeral, mode)
49
+ if numeral.special?
50
+ special_numeral_to_num numeral
51
+ elsif mode == :fixed
52
+ fixed_numeral_to_num numeral
53
+ else # mode == :free
54
+ free_numeral_to_num numeral
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def special_num_to_numeral(x)
61
+ if x.nan?
62
+ Numeral.nan
63
+ elsif x.infinite?
64
+ Numeral.infinity @context.sign(x)
65
+ end
66
+ end
67
+
68
+ def exact_num_to_numeral(number, rounding)
69
+ quotient = number.to_r
70
+ numeral = Numeral.from_quotient(quotient, base: rounding.base)
71
+ unless rounding.exact?
72
+ numeral = rounding.round(numeral)
73
+ end
74
+ numeral
75
+ end
76
+
77
+ def approximate_num_to_numeral(number, rounding)
78
+ all_digits = !rounding.exact?
79
+ general_num_to_numeral(number, rounding, all_digits)
80
+ end
81
+
82
+ def general_num_to_numeral(x, rounding, all_digits)
83
+ sign, coefficient, exponent = x.split
84
+ precision = x.number_of_digits
85
+ output_base = rounding.base
86
+
87
+ # here rounding_mode should be not the output rounding mode, but the rounding mode used for input
88
+ # we'll assume rounding.mode will be used for input unless it is exact
89
+ rounding_mode = rounding.exact? ? @context.rounding : rounding.mode
90
+ formatter = Flt::Support::Formatter.new(
91
+ @context.radix, @context.etiny, output_base, raise_on_repeat: false
92
+ )
93
+ formatter.format(
94
+ x, coefficient, exponent, rounding_mode, precision, all_digits
95
+ )
96
+
97
+ dec_pos, digits = formatter.digits
98
+ rep_pos = formatter.repeat
99
+ numeral = Numeral[digits, sign: sign, point: dec_pos, rep_pos: formatter.repeat, base: output_base]
100
+ if all_digits
101
+ numeral = rounding.round(numeral, formatter.round_up)
102
+ end
103
+ numeral
104
+ end
105
+
106
+ def special_numeral_to_num(numeral)
107
+ case numeral.special
108
+ when :nan
109
+ @context.nan
110
+ when :inf
111
+ @context.infinity numeral.sign
112
+ end
113
+ end
114
+
115
+ def fixed_numeral_to_num(numeral)
116
+ # consider:
117
+ # return exact_numeral_to_num(numeral) if numeral.exact?
118
+ if numeral.base == @context.radix
119
+ numeral = numeral.approximate(@context.precision) unless @context.exact?
120
+ same_base_numeral_to_num numeral
121
+ else
122
+ general_numeral_to_num numeral, :fixed
123
+ end
124
+ end
125
+
126
+ def same_base_numeral_to_num(numeral)
127
+ sign, coefficient, scale = numeral.split
128
+ @context.Num sign, coefficient, scale
129
+ end
130
+
131
+ def exact_numeral_to_num(numeral)
132
+ @context.Num Rational(*numeral.to_quotient), :fixed
133
+ end
134
+
135
+ def free_numeral_to_num(numeral)
136
+ general_numeral_to_num numeral, :free
137
+ end
138
+
139
+ def general_numeral_to_num(numeral, mode)
140
+ sign, coefficient, scale = numeral.split
141
+ reader = Flt::Support::Reader.new(mode: mode)
142
+ if mode == :fixed
143
+ rounding_mode = @context.rounding
144
+ else
145
+ rounding_mode = @rounding_mode
146
+ end
147
+ reader.read(@context, rounding_mode, sign, coefficient, scale, numeral.base).tap do
148
+ # @exact = reader.exact?
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ def (Flt::Num).numerals_conversion
155
+ Numerals::FltConversion.new(self)
156
+ end
157
+
158
+ class Flt::Num::ContextBase
159
+ def numerals_conversion
160
+ Numerals::FltConversion.new(self)
161
+ end
162
+ end
@@ -0,0 +1,39 @@
1
+ require 'numerals/conversions'
2
+ require 'singleton'
3
+
4
+ class Numerals::IntegerConversion
5
+
6
+ include Singleton
7
+
8
+ class InvalidConversion < RuntimeError
9
+ end
10
+
11
+ def order_of_magnitude(value, options={})
12
+ base = options[:base] || 10
13
+ if base == 2 && value.respond_to?(:bit_length)
14
+ value.bit_length
15
+ else
16
+ value.to_s(base).size
17
+ end
18
+ end
19
+
20
+ def number_to_numeral(number, mode, rounding)
21
+ # Rational.numerals_conversion Rational(number), mode, rounding
22
+ numeral = Numeral.from_quotient(number, 1)
23
+ numeral = rounding.round(numeral) unless rounding.exact?
24
+ numeral
25
+ end
26
+
27
+ def numeral_to_number(numeral, mode)
28
+ rational = Rational.numerals_conversion.numeral_to_number numeral, mode
29
+ if rational.denominator != 1
30
+ raise InvalidConversion, "Invalid numeral to rational conversion"
31
+ end
32
+ rational.numerator
33
+ end
34
+
35
+ end
36
+
37
+ def Integer.numerals_conversion
38
+ Numerals::IntegerConversion.instance
39
+ end
@@ -0,0 +1,32 @@
1
+ require 'numerals/conversions'
2
+ require 'singleton'
3
+
4
+ class Numerals::RationalConversion
5
+
6
+ include Singleton
7
+
8
+ def order_of_magnitude(value, options={})
9
+ base = options[:base] || 10
10
+ if base == 10
11
+ Math.log10(value.abs).floor + 1
12
+ else
13
+ (Math.log(value.abs)/Math.log(base)).floor + 1
14
+ end
15
+ end
16
+
17
+ def number_to_numeral(number, mode, rounding)
18
+ q = [number.numerator, number.denominator]
19
+ numeral = Numeral.from_quotient(q)
20
+ numeral = rounding.round(numeral) unless rounding.exact?
21
+ numeral
22
+ end
23
+
24
+ def numeral_to_number(numeral, mode)
25
+ Rational(*numeral.to_quotient)
26
+ end
27
+
28
+ end
29
+
30
+ def Rational.numerals_conversion
31
+ Numerals::RationalConversion.instance
32
+ end
@@ -0,0 +1,57 @@
1
+ module Numerals::Conversions
2
+
3
+ class <<self
4
+ def [](type)
5
+ if type.respond_to?(:numerals_conversion)
6
+ type.numerals_conversion
7
+ end
8
+ end
9
+
10
+ def order_of_magnitude(number, options={})
11
+ self[number.class].order_of_magnitude(number, options)
12
+ end
13
+
14
+ # explicit: number_to_numeral x, mode, rounding
15
+ # but for :free mode rounding is ignored except for the base
16
+ # so if no rounding/rounding options are passed except for the base, the default mode should be :free
17
+ # otherwise, the default mode should be :fixed...
18
+
19
+ # number_to_numeral(x, precision: 3) = number_to_numeral(x, :fixed, Rounding[precision: 3])
20
+ # number_to_numeral(x, precision: 3, base: 2) = number_to_numeral(x, :fixed, Rounding[precision: 3, base: 2])
21
+ # number_to_numeral(x, :exact, base: 2) = number_to_numeral(x, :free, Rounding[:exact, base: 2])
22
+
23
+ # Two ways of defining the conversion mode:
24
+ # 1. fixed or free:
25
+ # * :fixed means: adjust the number precision to the output rounding
26
+ # * :free means: forget about the rounding, preserve the input number precision
27
+ # But :free cheats: if rounding is not exact it really honors it by
28
+ # converting the number to an exact numeral an then rounding it.
29
+ # 2. exact or approximate
30
+ # * :exact means: consider the number an exact quantity
31
+ # * :approximate means: consider the number approximate; show only non-spurious digits.
32
+ def number_to_numeral(number, *args)
33
+ mode = extract_mode_from_args!(args)
34
+ rounding = Rounding[*args]
35
+ # mode ||= rounding.exact? ? :free : :fixed
36
+ mode ||= :approximate
37
+ if [:fixed, :free].include?(mode)
38
+ mode = (mode == :fixed) == (rounding.exact?) ? :exact : :approximate
39
+ end
40
+ self[number.class].number_to_numeral(number, mode, rounding)
41
+ end
42
+
43
+ def numeral_to_number(numeral, type, *args)
44
+ mode = extract_mode_from_args!(args) || :fixed
45
+ self[type].numeral_to_number(numeral, mode, *args)
46
+ end
47
+
48
+ private
49
+
50
+ def extract_mode_from_args!(args)
51
+ if [:fixed, :free, :exact, :approximate].include?(args.first)
52
+ args.shift
53
+ end
54
+ end
55
+ end
56
+
57
+ end