flt 1.3.4 → 1.4.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.
@@ -0,0 +1,341 @@
1
+ module Flt
2
+ module Support
3
+
4
+ # This class assigns bit-values to a set of symbols
5
+ # so they can be used as flags and stored as an integer.
6
+ # fv = FlagValues.new(:flag1, :flag2, :flag3)
7
+ # puts fv[:flag3]
8
+ # fv.each{|f,v| puts "#{f} -> #{v}"}
9
+ class FlagValues
10
+
11
+ #include Enumerator
12
+
13
+ class InvalidFlagError < StandardError
14
+ end
15
+ class InvalidFlagTypeError < StandardError
16
+ end
17
+
18
+
19
+ # The flag symbols must be passed; values are assign in increasing order.
20
+ # fv = FlagValues.new(:flag1, :flag2, :flag3)
21
+ # puts fv[:flag3]
22
+ def initialize(*flags)
23
+ @flags = {}
24
+ value = 1
25
+ flags.each do |flag|
26
+ raise InvalidFlagType,"Flags must be defined as symbols or classes; invalid flag: #{flag.inspect}" unless flag.kind_of?(Symbol) || flag.instance_of?(Class)
27
+ @flags[flag] = value
28
+ value <<= 1
29
+ end
30
+ end
31
+
32
+ # Get the bit-value of a flag
33
+ def [](flag)
34
+ v = @flags[flag]
35
+ raise InvalidFlagError, "Invalid flag: #{flag}" unless v
36
+ v
37
+ end
38
+
39
+ # Return each flag and its bit-value
40
+ def each(&blk)
41
+ if blk.arity==2
42
+ @flags.to_a.sort_by{|f,v|v}.each(&blk)
43
+ else
44
+ @flags.to_a.sort_by{|f,v|v}.map{|f,v|f}.each(&blk)
45
+ end
46
+ end
47
+
48
+ def size
49
+ @flags.size
50
+ end
51
+
52
+ def all_flags_value
53
+ (1 << size) - 1
54
+ end
55
+
56
+ end
57
+
58
+ # This class stores a set of flags. It can be assign a FlagValues
59
+ # object (using values= or passing to the constructor) so that
60
+ # the flags can be store in an integer (bits).
61
+ class Flags
62
+
63
+ class Error < StandardError
64
+ end
65
+ class InvalidFlagError < Error
66
+ end
67
+ class InvalidFlagValueError < Error
68
+ end
69
+ class InvalidFlagTypeError < Error
70
+ end
71
+
72
+ # When a Flag object is created, the initial flags to be set can be passed,
73
+ # and also a FlagValues. If a FlagValues is passed an integer can be used
74
+ # to define the flags.
75
+ # Flags.new(:flag1, :flag3, FlagValues.new(:flag1,:flag2,:flag3))
76
+ # Flags.new(5, FlagValues.new(:flag1,:flag2,:flag3))
77
+ def initialize(*flags)
78
+ @values = nil
79
+ @flags = {}
80
+
81
+ v = 0
82
+
83
+ flags.flatten!
84
+
85
+ flags.each do |flag|
86
+ case flag
87
+ when FlagValues
88
+ @values = flag
89
+ when Symbol, Class
90
+ @flags[flag] = true
91
+ when Integer
92
+ v |= flag
93
+ when Flags
94
+ @values = flag.values
95
+ @flags = flag.to_h.dup
96
+ else
97
+ raise InvalidFlagTypeError, "Invalid flag type for: #{flag.inspect}"
98
+ end
99
+ end
100
+
101
+ if v!=0
102
+ raise InvalidFlagTypeError, "Integer flag values need flag bit values to be defined" if @values.nil?
103
+ self.bits = v
104
+ end
105
+
106
+ if @values
107
+ # check flags
108
+ @flags.each_key{|flag| check flag}
109
+ end
110
+
111
+ end
112
+
113
+ def dup
114
+ Flags.new(self)
115
+ end
116
+
117
+ # Clears all flags
118
+ def clear!
119
+ @flags = {}
120
+ end
121
+
122
+ # Sets all flags
123
+ def set!
124
+ if @values
125
+ self.bits = @values.all_flags_value
126
+ else
127
+ raise Error,"No flag values defined"
128
+ end
129
+ end
130
+
131
+ # Assign the flag bit values
132
+ def values=(fv)
133
+ @values = fv
134
+ end
135
+
136
+ # Retrieves the flag bit values
137
+ def values
138
+ @values
139
+ end
140
+
141
+ # Retrieves the flags as a bit-vector integer. Values must have been assigned.
142
+ def bits
143
+ if @values
144
+ i = 0
145
+ @flags.each do |f,v|
146
+ bit_val = @values[f]
147
+ i |= bit_val if v && bit_val
148
+ end
149
+ i
150
+ else
151
+ raise Error,"No flag values defined"
152
+ end
153
+ end
154
+
155
+ # Sets the flags as a bit-vector integer. Values must have been assigned.
156
+ def bits=(i)
157
+ if @values
158
+ raise Error, "Invalid bits value #{i}" if i<0 || i>@values.all_flags_value
159
+ clear!
160
+ @values.each do |f,v|
161
+ @flags[f]=true if (i & v)!=0
162
+ end
163
+ else
164
+ raise Error,"No flag values defined"
165
+ end
166
+ end
167
+
168
+ # Retrieves the flags as a hash.
169
+ def to_h
170
+ @flags
171
+ end
172
+
173
+ # Same as bits
174
+ def to_i
175
+ bits
176
+ end
177
+
178
+ # Retrieve the setting (true/false) of a flag
179
+ def [](flag)
180
+ check flag
181
+ @flags[flag]
182
+ end
183
+
184
+ # Modifies the setting (true/false) of a flag.
185
+ def []=(flag,value)
186
+ check flag
187
+ case value
188
+ when true,1
189
+ value = true
190
+ when false,0,nil
191
+ value = false
192
+ else
193
+ raise InvalidFlagValueError, "Invalid value: #{value.inspect}"
194
+ end
195
+ @flags[flag] = value
196
+ value
197
+ end
198
+
199
+ # Sets (makes true) one or more flags
200
+ def set(*flags)
201
+ flags = flags.first if flags.size==1 && flags.first.instance_of?(Array)
202
+ flags.each do |flag|
203
+ if flag.kind_of?(Flags)
204
+ #if @values && other.values && compatible_values(other_values)
205
+ # self.bits |= other.bits
206
+ #else
207
+ flags.concat other.to_a
208
+ #end
209
+ else
210
+ check flag
211
+ @flags[flag] = true
212
+ end
213
+ end
214
+ end
215
+
216
+ # Clears (makes false) one or more flags
217
+ def clear(*flags)
218
+ flags = flags.first if flags.size==1 && flags.first.instance_of?(Array)
219
+ flags.each do |flag|
220
+ if flag.kind_of?(Flags)
221
+ #if @values && other.values && compatible_values(other_values)
222
+ # self.bits &= ~other.bits
223
+ #else
224
+ flags.concat other.to_a
225
+ #end
226
+ else
227
+ check flag
228
+ @flags[flag] = false
229
+ end
230
+ end
231
+ end
232
+
233
+ # Sets (makes true) one or more flags (passes as an array)
234
+ def << (flags)
235
+ if flags.kind_of?(Array)
236
+ set(*flags)
237
+ else
238
+ set(flags)
239
+ end
240
+ end
241
+
242
+ # Iterate on each flag/setting pair.
243
+ def each(&blk)
244
+ if @values
245
+ @values.each do |f,v|
246
+ blk.call(f,@flags[f])
247
+ end
248
+ else
249
+ @flags.each(&blk)
250
+ end
251
+ end
252
+
253
+ # Iterate on each set flag
254
+ def each_set
255
+ each do |f,v|
256
+ yield f if v
257
+ end
258
+ end
259
+
260
+ # Iterate on each cleared flag
261
+ def each_clear
262
+ each do |f,v|
263
+ yield f if !v
264
+ end
265
+ end
266
+
267
+ # returns true if any flag is set
268
+ def any?
269
+ if @values
270
+ bits != 0
271
+ else
272
+ to_a.size>0
273
+ end
274
+ end
275
+
276
+ # Returns the true flags as an array
277
+ def to_a
278
+ a = []
279
+ each_set{|f| a << f}
280
+ a
281
+ end
282
+
283
+ def to_s
284
+ "[#{to_a.map{|f| f.to_s.split('::').last}.join(', ')}]"
285
+ end
286
+
287
+ def inspect
288
+ txt = "#{self.class.to_s}#{to_s}"
289
+ txt << " (0x#{bits.to_s(16)})" if @values
290
+ txt
291
+ end
292
+
293
+
294
+ def ==(other)
295
+ if @values && other.values && compatible_values?(other.values)
296
+ bits == other.bits
297
+ else
298
+ to_a.map{|s| s.to_s}.sort == other.to_a.map{|s| s.to_s}.sort
299
+ end
300
+ end
301
+
302
+
303
+
304
+ private
305
+ def check(flag)
306
+ raise InvalidFlagType,"Flags must be defined as symbols or classes; invalid flag: #{flag.inspect}" unless flag.kind_of?(Symbol) || flag.instance_of?(Class)
307
+
308
+ @values[flag] if @values # raises an invalid flag error if flag is invalid
309
+ true
310
+ end
311
+
312
+ def compatible_values?(v)
313
+ #@values.object_id==v.object_id
314
+ @values == v
315
+ end
316
+
317
+ end
318
+
319
+ module_function
320
+
321
+ # Constructor for FlagValues
322
+ def FlagValues(*params)
323
+ if params.size==1 && params.first.kind_of?(FlagValues)
324
+ params.first
325
+ else
326
+ FlagValues.new(*params)
327
+ end
328
+ end
329
+
330
+ # Constructor for Flags
331
+ def Flags(*params)
332
+ if params.size==1 && params.first.kind_of?(Flags)
333
+ params.first
334
+ else
335
+ Flags.new(*params)
336
+ end
337
+ end
338
+
339
+ end
340
+ end
341
+
@@ -0,0 +1,512 @@
1
+ module Flt
2
+ module Support
3
+
4
+ # Burger and Dybvig free formatting algorithm,
5
+ # from their paper: "Printing Floating-Point Numbers Quickly and Accurately"
6
+ # (Robert G. Burger, R. Kent Dybvig)
7
+ #
8
+ # This algorithm formats arbitrary base floating point numbers as decimal
9
+ # text literals. The floating-point (with fixed precision) is interpreted as an approximated
10
+ # value, representing any value in its 'rounding-range' (the interval where all values round
11
+ # to the floating-point value, with the given precision and rounding mode).
12
+ # An alternative approach which is not taken here would be to represent the exact floating-point
13
+ # value with some given precision and rounding mode requirements; that can be achieved with
14
+ # Clinger algorithm (which may fail for exact precision).
15
+ #
16
+ # The variables used by the algorithm are stored in instance variables:
17
+ # @v - The number to be formatted = @f*@b**@e
18
+ # @b - The numberic base of the input floating-point representation of @v
19
+ # @f - The significand or characteristic (fraction)
20
+ # @e - The exponent
21
+ #
22
+ # Quotients of integers will be used to hold the magnitudes:
23
+ # @s is the denominator of all fractions
24
+ # @r numerator of @v: @v = @r/@s
25
+ # @m_m numerator of the distance from the rounding-range lower limit, l, to @v: @m_m/@s = (@v - l)
26
+ # @m_p numerator of the distance from @v to the rounding-range upper limit, u: @m_p/@s = (u - @v)
27
+ # All numbers in the randound-range are rounded to @v (with the given precision p)
28
+ # @k scale factor that is applied to the quotients @r/@s, @m_m/@s and @m_p/@s to put the first
29
+ # significant digit right after the radix point. @b**@k is the first power of @b >= u
30
+ #
31
+ # The rounding range of @v is the interval of values that round to @v under the runding-mode.
32
+ # If the rounding mode is one of the round-to-nearest variants (even, up, down), then
33
+ # it is ((v+v-)/2 = (@v-@m_m)/@s, (v+v+)/2 = (@v+@m_)/2) whith the boundaries open or closed as explained below.
34
+ # In this case:
35
+ # @m_m/@s = (@v - (v + v-)/2) where v- = @v.next_minus is the lower adjacent to v floating point value
36
+ # @m_p/@s = ((v + v+)/2 - @v) where v+ = @v.next_plus is the upper adjacent to v floating point value
37
+ # If the rounding is directed, then the rounding interval is either (v-, @v] or [@v, v+]
38
+ # @roundl is true if the lower limit of the rounding range is closed (i.e., if l rounds to @v)
39
+ # @roundh is true if the upper limit of the rounding range is closed (i.e., if u rounds to @v)
40
+ # if @roundh, then @k is the minimum @k with (@r+@m_p)/@s <= @output_b**@k
41
+ # @k = ceil(logB((@r+@m_p)/2)) with lobB the @output_b base logarithm
42
+ # if @roundh, then @k is the minimum @k with (@r+@m_p)/@s < @output_b**@k
43
+ # @k = 1+floor(logB((@r+@m_p)/2))
44
+ #
45
+ # @output_b is the output base
46
+ # @output_min_e is the output minimum exponent
47
+ # p is the input floating point precision
48
+ class Formatter
49
+
50
+ # This Object-oriented implementation is slower than the original functional one for two reasons:
51
+ # * The overhead of object creation
52
+ # * The use of instance variables instead of local variables
53
+ # But if scale is optimized or local variables are used in the inner loops, then this implementation
54
+ # is on par with the functional one for Float and it is more efficient for Flt types, where the variables
55
+ # passed as parameters hold larger objects.
56
+
57
+ # A Formatted object is created to format floating point numbers given:
58
+ # * The input base in which numbers to be formatted are defined
59
+ # * The input minimum expeonent
60
+ # * The output base to which the input is converted.
61
+ # * The :raise_on_repeat option, true by default specifies that when
62
+ # an infinite sequence of repeating significant digits is found on the output
63
+ # (which may occur when using the all-digits options and using directed-rounding)
64
+ # an InfiniteLoopError exception is raised. If this option is false, then
65
+ # no exception occurs, and instead of generating an infinite sequence of digits,
66
+ # the formatter object will have a 'repeat' property which designs the first digit
67
+ # to be repeated (it is an index into digits). If this equals the size of digits,
68
+ # it is assumend, that the digit to be repeated is a zero which follows the last
69
+ # digit present in digits.
70
+ def initialize(input_b, input_min_e, output_b, options={})
71
+ @b = input_b
72
+ @min_e = input_min_e
73
+ @output_b = output_b
74
+ # result of last operation
75
+ @adjusted_digits = @digits = nil
76
+ # for "all-digits" mode results (which are truncated, rather than rounded),
77
+ # round_up contains information to round the result:
78
+ # * it is nil if the rest of digits are zero (the result is exact)
79
+ # * it is :lo if there exist non-zero digits beyond the significant ones (those returned), but
80
+ # the value is below the tie (the value must be rounded up only for :up rounding mode)
81
+ # * it is :tie if there exists exactly one nonzero digit after the significant and it is radix/2,
82
+ # for round-to-nearest it is atie.
83
+ # * it is :hi otherwise (the value should be rounded-up except for the :down mode)
84
+ @round_up = nil
85
+
86
+ options = { :raise_on_repeat => true }.merge(options)
87
+ # when significant repeating digits occur (+all+ parameter and directed rounding)
88
+ # @repeat is set to the index of the first repeating digit in @digits;
89
+ # (if equal to @digits.size, that would indicate an infinite sequence of significant zeros)
90
+ @repeat = nil
91
+ # the :raise_on_repeat options (by default true) causes exceptions when repeating is found
92
+ @raise_on_repeat = options[:raise_on_repeat]
93
+ end
94
+
95
+ # This method converts v = f*b**e into a sequence of +output_b+-base digits,
96
+ # so that if the digits are converted back to a floating-point value
97
+ # of precision p (correctly rounded), the result is exactly v.
98
+ #
99
+ # If +round_mode+ is not nil, then just enough digits to produce v using
100
+ # that rounding is used; otherwise enough digits to produce v with
101
+ # any rounding are delivered.
102
+ #
103
+ # If the +all+ parameter is true, all significant digits are generated without rounding,
104
+ # Significant digits here are all digits that, if used on input, cannot arbitrarily change
105
+ # while preserving the parsed value of the floating point number. Since the digits are not rounded
106
+ # more digits may be needed to assure round-trip value preservation.
107
+ #
108
+ # This is useful to reflect the precision of the floating point value in the output; in particular
109
+ # trailing significant zeros are shown. But note that, for directed rounding and base conversion
110
+ # this may need to produce an infinite number of digits, in which case an exception will be raised
111
+ # unless the :raise_on_repeat option has been set to false in the Formatter object. In that case
112
+ # the formatter objetct will have a +repeat+ property that specifies the point in the digit
113
+ # sequence where irepetition starts. The digits from that point to the end to the digits sequence
114
+ # repeat indefinitely.
115
+ #
116
+ # This digit-repetition is specially frequent for the :up rounding mode, in which any number
117
+ # with a finite numberof nonzero digits equal to or less than the precision will haver and infinite
118
+ # sequence of zero significant digits.
119
+ #
120
+ # The:down rounding (truncation) could be used to show the exact value of the floating
121
+ # point but beware: if the value has not an exact representation in the output base this will
122
+ # lead to an infinite loop or repeating squence.
123
+ #
124
+ # When the +all+ parameters is used the result is not rounded (is truncated), and the round_up flag
125
+ # is set to indicate that nonzero digits exists beyond the returned digits; the possible values
126
+ # of the round_up flag are:
127
+ # * nil : the rest of digits are zero or repeat (the result is exact)
128
+ # * :lo : there exist non-zero digits beyond the significant ones (those returned), but
129
+ # the value is below the tie (the value must be rounded up only for :up rounding mode)
130
+ # * :tie : there exists exactly one nonzero digit after the significant and it is radix/2,
131
+ # for round-to-nearest it is atie.
132
+ # * :hi : the value is closer to the rounded-up value (incrementing the last significative digit.)
133
+ #
134
+ # Note that the round_mode here is not the rounding mode applied to the output;
135
+ # it is the rounding mode that applied to *input* preserves the original floating-point
136
+ # value (with the same precision as input).
137
+ # should be rounded-up.
138
+ #
139
+ def format(v, f, e, round_mode, p=nil, all=false)
140
+ context = v.class.context
141
+ # TODO: consider removing parameters f,e and using v.split instead
142
+ @minus = (context.sign(v)==-1)
143
+ @v = context.copy_sign(v, +1) # don't use context.abs(v) because it rounds (and may overflow also)
144
+ @f = f.abs
145
+ @e = e
146
+ @round_mode = round_mode
147
+ @all_digits = all
148
+ p ||= context.precision
149
+
150
+ # adjust the rounding mode to work only with positive numbers
151
+ @round_mode = Support.simplified_round_mode(@round_mode, @minus)
152
+
153
+ # determine the high,low inclusion flags of the rounding limits
154
+ case @round_mode
155
+ when :half_even
156
+ # rounding rage is (v-m-,v+m+) if v is odd and [v+m-,v+m+] if even
157
+ @round_l = @round_h = ((@f%2)==0)
158
+ when :up
159
+ # rounding rage is (v-,v]
160
+ # ceiling is treated here assuming f>0
161
+ @round_l, @round_h = false, true
162
+ when :down
163
+ # rounding rage is [v,v+)
164
+ # floor is treated here assuming f>0
165
+ @round_l, @round_h = true, false
166
+ when :half_up
167
+ # rounding rage is [v+m-,v+m+)
168
+ @round_l, @round_h = true, false
169
+ when :half_down
170
+ # rounding rage is (v+m-,v+m+]
171
+ @round_l, @round_h = false, true
172
+ else # :nearest
173
+ # Here assume only that round-to-nearest will be used, but not which variant of it
174
+ # The result is valid for any rounding (to nearest) but may produce more digits
175
+ # than stricly necessary for specific rounding modes.
176
+ # That is, enough digits are generated so that when the result is
177
+ # converted to floating point with the specified precision and
178
+ # correct rounding (to nearest), the result is the original number.
179
+ # rounding range is (v+m-,v+m+)
180
+ @round_l = @round_h = false
181
+ end
182
+
183
+ # TODO: use context.next_minus, next_plus instead of direct computing, don't require min_e & ps
184
+ # Now compute the working quotients @r/@s, @m_p/@s = (v+ - @v), @m_m/@s = (@v - v-) and scale them.
185
+ if @e >= 0
186
+ if @f != b_power(p-1)
187
+ be = b_power(@e)
188
+ @r, @s, @m_p, @m_m = @f*be*2, 2, be, be
189
+ else
190
+ be = b_power(@e)
191
+ be1 = be*@b
192
+ @r, @s, @m_p, @m_m = @f*be1*2, @b*2, be1, be
193
+ end
194
+ else
195
+ if @e==@min_e or @f != b_power(p-1)
196
+ @r, @s, @m_p, @m_m = @f*2, b_power(-@e)*2, 1, 1
197
+ else
198
+ @r, @s, @m_p, @m_m = @f*@b*2, b_power(1-@e)*2, @b, 1
199
+ end
200
+ end
201
+ @k = 0
202
+ @context = context
203
+ scale_optimized!
204
+
205
+
206
+ # The value to be formatted is @v=@r/@s; m- = @m_m/@s = (@v - v-)/@s; m+ = @m_p/@s = (v+ - @v)/@s
207
+ # Now adjust @m_m, @m_p so that they define the rounding range
208
+ case @round_mode
209
+ when :up
210
+ # ceiling is treated here assuming @f>0
211
+ # rounding range is -v,@v
212
+ @m_m, @m_p = @m_m*2, 0
213
+ when :down
214
+ # floor is treated here assuming #f>0
215
+ # rounding range is @v,v+
216
+ @m_m, @m_p = 0, @m_p*2
217
+ else
218
+ # rounding range is v-,v+
219
+ # @m_m, @m_p = @m_m, @m_p
220
+ end
221
+
222
+ # Now m_m, m_p define the rounding range
223
+ all ? generate_max : generate
224
+
225
+ end
226
+
227
+ # Access result of format operation: scaling (position of radix point) and digits
228
+ def digits
229
+ return @k, @digits
230
+ end
231
+
232
+ attr_reader :round_up, :repeat
233
+
234
+ # Access rounded result of format operation: scaling (position of radix point) and digits
235
+ def adjusted_digits(round_mode)
236
+ if @adjusted_digits.nil? && !@digits.nil?
237
+ @adjusted_k, @adjusted_digits = Support.adjust_digits(@k, @digits,
238
+ :round_mode => round_mode,
239
+ :negative => @minus,
240
+ :round_up => @round_up,
241
+ :base => @output_b)
242
+ end
243
+ return @adjusted_k, @adjusted_digits
244
+ end
245
+
246
+ # Given r/s = v (number to convert to text), m_m/s = (v - v-)/s, m_p/s = (v+ - v)/s
247
+ # Scale the fractions so that the first significant digit is right after the radix point, i.e.
248
+ # find k = ceil(logB((r+m_p)/s)), the smallest integer such that (r+m_p)/s <= B^k
249
+ # if k>=0 return:
250
+ # r=r, s=s*B^k, m_p=m_p, m_m=m_m
251
+ # if k<0 return:
252
+ # r=r*B^k, s=s, m_p=m_p*B^k, m_m=m_m*B^k
253
+ #
254
+ # scale! is a general iterative method using only (multiprecision) integer arithmetic.
255
+ def scale_original!(really=false)
256
+ loop do
257
+ if (@round_h ? (@r+@m_p >= @s) : (@r+@m_p > @s)) # k is too low
258
+ @s *= @output_b
259
+ @k += 1
260
+ elsif (@round_h ? ((@r+@m_p)*@output_b<@s) : ((@r+@m_p)*@output_b<=@s)) # k is too high
261
+ @r *= @output_b
262
+ @m_p *= @output_b
263
+ @m_m *= @output_b
264
+ @k -= 1
265
+ else
266
+ break
267
+ end
268
+ end
269
+ end
270
+ # using local vars instead of instance vars: it makes a difference in performance
271
+ def scale!
272
+ r, s, m_p, m_m, k,output_b = @r, @s, @m_p, @m_m, @k,@output_b
273
+ loop do
274
+ if (@round_h ? (r+m_p >= s) : (r+m_p > s)) # k is too low
275
+ s *= output_b
276
+ k += 1
277
+ elsif (@round_h ? ((r+m_p)*output_b<s) : ((r+m_p)*output_b<=s)) # k is too high
278
+ r *= output_b
279
+ m_p *= output_b
280
+ m_m *= output_b
281
+ k -= 1
282
+ else
283
+ @s = s
284
+ @r = r
285
+ @m_p = m_p
286
+ @m_m = m_m
287
+ @k = k
288
+ break
289
+ end
290
+ end
291
+ end
292
+
293
+ def b_power(n)
294
+ @b**n
295
+ end
296
+
297
+ def output_b_power(n)
298
+ @output_b**n
299
+ end
300
+
301
+ def start_repetition_dectection
302
+ @may_repeat = (@m_p == 0 || @m_m == 0)
303
+ @n_iters = 0
304
+ @rs = []
305
+ end
306
+
307
+ ITERATIONS_BEFORE_KEEPING_TRACK_OF_REMAINDERS = 10000
308
+
309
+ # Detect indefinite repetitions in generate_max
310
+ # returns the number of digits that are being repeated
311
+ # (0 indicates the next digit would repeat and it would be a zero)
312
+ def detect_repetitions(r)
313
+ return nil unless @may_repeat
314
+ @n_iters += 1
315
+ if r == 0 && @m_p == 0
316
+ repeat_count = 0
317
+ elsif (@n_iters > ITERATIONS_BEFORE_KEEPING_TRACK_OF_REMAINDERS)
318
+ if @rs.include?(r)
319
+ repeat_count = @rs.index(r) - @rs.size
320
+ else
321
+ @rs << r
322
+ end
323
+ end
324
+ if repeat_count
325
+ raise InfiniteLoopError, "Infinite digit sequence." if @raise_on_repeat
326
+ repeat_count
327
+ else
328
+ nil
329
+ end
330
+ end
331
+
332
+ def remove_redundant_repetitions
333
+ if ITERATIONS_BEFORE_KEEPING_TRACK_OF_REMAINDERS > 0 && @repeat
334
+ if @repeat < @digits.size
335
+ repeating_digits = @digits[@repeat..-1]
336
+ l = repeating_digits.size
337
+ pos = @repeat - l
338
+ while pos >= 0 && @digits[pos, l] == repeating_digits
339
+ pos -= l
340
+ end
341
+ first_repeat = pos + l
342
+ if first_repeat < @repeat
343
+ @repeat = first_repeat
344
+ @digits = @digits[0, @repeat+l]
345
+ end
346
+ end
347
+ end
348
+ @digits
349
+ end
350
+
351
+ def generate_max
352
+ @round_up = false
353
+ list = []
354
+ r, s, m_p, m_m, = @r, @s, @m_p, @m_m
355
+
356
+ start_repetition_dectection
357
+
358
+ loop do
359
+ if repeat_count = detect_repetitions(r)
360
+ @repeat = list.size + repeat_count
361
+ break
362
+ end
363
+
364
+ d,r = (r*@output_b).divmod(s)
365
+
366
+ m_p *= @output_b
367
+ m_m *= @output_b
368
+
369
+ list << d
370
+
371
+ tc1 = @round_l ? (r<=m_m) : (r<m_m)
372
+ tc2 = @round_h ? (r+m_p >= s) : (r+m_p > s)
373
+
374
+ if tc1 && tc2
375
+ if r != 0
376
+ r *= 2
377
+ if r > s
378
+ @round_up = :hi
379
+ elsif r == s
380
+ @round_up = :tie
381
+ else
382
+ @rund_up = :lo
383
+ end
384
+ end
385
+ break
386
+ end
387
+ end
388
+ @digits = list
389
+ remove_redundant_repetitions
390
+ end
391
+
392
+ def generate
393
+ list = []
394
+ r, s, m_p, m_m, = @r, @s, @m_p, @m_m
395
+ loop do
396
+ d,r = (r*@output_b).divmod(s)
397
+ m_p *= @output_b
398
+ m_m *= @output_b
399
+ tc1 = @round_l ? (r<=m_m) : (r<m_m)
400
+ tc2 = @round_h ? (r+m_p >= s) : (r+m_p > s)
401
+
402
+ if not tc1
403
+ if not tc2
404
+ list << d
405
+ else
406
+ list << d+1
407
+ break
408
+ end
409
+ else
410
+ if not tc2
411
+ list << d
412
+ break
413
+ else
414
+ if r*2 < s
415
+ list << d
416
+ break
417
+ else
418
+ list << d+1
419
+ break
420
+ end
421
+ end
422
+ end
423
+
424
+ end
425
+ @digits = list
426
+ end
427
+
428
+ ESTIMATE_FLOAT_LOG_B = {2=>1/Math.log(2), 10=>1/Math.log(10), 16=>1/Math.log(16)}
429
+ # scale_o1! is an optimized version of scale!; it requires an additional parameters with the
430
+ # floating-point number v=r/s
431
+ #
432
+ # It uses a Float estimate of ceil(logB(v)) that may need to adjusted one unit up
433
+ # TODO: find easy to use estimate; determine max distance to correct value and use it for fixing,
434
+ # or use the general scale! for fixing (but remembar to multiply by exptt(...))
435
+ # (determine when Math.log is aplicable, etc.)
436
+ def scale_optimized!
437
+ context = @context # @v.class.context
438
+ return scale! if context.zero?(@v)
439
+
440
+ # 1. compute estimated_scale
441
+
442
+ # 1.1. try to use Float logarithms (Math.log)
443
+ v = @v
444
+ v_abs = context.copy_sign(v, +1) # don't use v.abs because it rounds (and may overflow also)
445
+ v_flt = v_abs.to_f
446
+ b = @output_b
447
+ log_b = ESTIMATE_FLOAT_LOG_B[b]
448
+ log_b = ESTIMATE_FLOAT_LOG_B[b] = 1.0/Math.log(b) if log_b.nil?
449
+ estimated_scale = nil
450
+ fixup = false
451
+ begin
452
+ l = ((b==10) ? Math.log10(v_flt) : Math.log(v_flt)*log_b)
453
+ estimated_scale =(l - 1E-10).ceil
454
+ fixup = true
455
+ rescue
456
+ # rescuing errors is more efficient than checking (v_abs < Float::MAX.to_i) && (v_flt > Float::MIN) when v is a Flt
457
+ else
458
+ # estimated_scale = nil
459
+ end
460
+
461
+ # 1.2. Use Flt::DecNum logarithm
462
+ if estimated_scale.nil?
463
+ v.to_decimal_exact(:precision=>12) if v.is_a?(BinNum)
464
+ if v.is_a?(DecNum)
465
+ l = nil
466
+ DecNum.context(:precision=>12) do
467
+ case b
468
+ when 10
469
+ l = v_abs.log10
470
+ else
471
+ l = v_abs.ln/Flt.DecNum(b).ln
472
+ end
473
+ end
474
+ l -= Flt.DecNum(+1,1,-10)
475
+ estimated_scale = l.ceil
476
+ fixup = true
477
+ end
478
+ end
479
+
480
+ # 1.3 more rough Float aproximation
481
+ # TODO: optimize denominator, correct numerator for more precision with first digit or part
482
+ # of the coefficient (like _log_10_lb)
483
+ estimated_scale ||= (v.adjusted_exponent.to_f * Math.log(v.class.context.radix) * log_b).ceil
484
+
485
+ if estimated_scale >= 0
486
+ @k = estimated_scale
487
+ @s *= output_b_power(estimated_scale)
488
+ else
489
+ sc = output_b_power(-estimated_scale)
490
+ @k = estimated_scale
491
+ @r *= sc
492
+ @m_p *= sc
493
+ @m_m *= sc
494
+ end
495
+ fixup ? scale_fixup! : scale!
496
+
497
+ end
498
+
499
+ # fix up scaling (final step): specialized version of scale!
500
+ # This performs a single up scaling step, i.e. behaves like scale2, but
501
+ # the input must be at most one step down from the final result
502
+ def scale_fixup!
503
+ if (@round_h ? (@r+@m_p >= @s) : (@r+@m_p > @s)) # too low?
504
+ @s *= @output_b
505
+ @k += 1
506
+ end
507
+ end
508
+
509
+ end
510
+
511
+ end
512
+ end