numerals 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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