flt 1.3.4 → 1.4.0

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