flt 1.3.4 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -2
- data/History.txt +5 -0
- data/Rakefile +1 -1
- data/lib/flt.rb +4 -0
- data/lib/flt/bigdecimal.rb +14 -0
- data/lib/flt/float.rb +19 -0
- data/lib/flt/num.rb +22 -1
- data/lib/flt/support.rb +1 -1266
- data/lib/flt/support/flag_values.rb +341 -0
- data/lib/flt/support/formatter.rb +512 -0
- data/lib/flt/support/rationalizer.rb +312 -0
- data/lib/flt/support/rationalizer_extra.rb +168 -0
- data/lib/flt/support/reader.rb +427 -0
- data/lib/flt/tolerance.rb +1 -0
- data/lib/flt/version.rb +1 -1
- data/test/data/.gitignore +4 -0
- data/test/generate_trig_data.rb +2 -2
- data/test/helper.rb +35 -0
- data/test/test_base_digits.rb +4 -4
- data/test/test_dectest.rb +2 -2
- data/test/test_exact.rb +2 -2
- data/test/test_formatter.rb +2 -2
- data/test/test_rationalizer.rb +141 -0
- data/test/test_trig.rb +1 -1
- metadata +11 -2
@@ -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
|