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.
- 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,427 @@
|
|
1
|
+
module Flt
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# Floating-point reading and printing (from/to text literals).
|
5
|
+
#
|
6
|
+
# Here are methods for floating-point reading, using algorithms by William D. Clinger, and
|
7
|
+
# printing, using algorithms by Robert G. Burger and R. Kent Dybvig.
|
8
|
+
#
|
9
|
+
# Reading and printing can also viewed as floating-point conversion between a fixed-precision
|
10
|
+
# floating-point format (the floating-point numbers) and and a free floating-point format (text),
|
11
|
+
# which may use different numerical bases.
|
12
|
+
#
|
13
|
+
# The Reader class, in the default :free mode, converts a free-form numeric value
|
14
|
+
# (as a text literal, i.e. a free floating-point format, usually in base 10) which is taken
|
15
|
+
# as an exact value, to a correctly-rounded floating-point of specified precision and with a
|
16
|
+
# specified rounding mode. It also has a :fixed mode that uses the Formatter class indirectly.
|
17
|
+
#
|
18
|
+
# The Formatter class implements the Burger-Dybvig printing algorithm which converts a
|
19
|
+
# fixed-precision floating point value and produces a text literal in some base, usually 10,
|
20
|
+
# (equivalently, it produces a floating-point free-format value) so that it rounds back to
|
21
|
+
# the original value (with some specified rounding-mode or any round-to-nearest mode) and with
|
22
|
+
# the same original precision (e.g. using the Clinger algorithm)
|
23
|
+
|
24
|
+
# Clinger algorithms to read floating point numbers from text literals with correct rounding.
|
25
|
+
# from his paper: "How to Read Floating Point Numbers Accurately"
|
26
|
+
# (William D. Clinger)
|
27
|
+
class Reader
|
28
|
+
|
29
|
+
# There are two different reading approaches, selected by the :mode parameter:
|
30
|
+
# * :fixed (the destination context defines the resulting precision) input is rounded as specified
|
31
|
+
# by the context; if the context precision is 'exact', the exact input value will be represented
|
32
|
+
# in the destination base, which can lead to a Inexact exception (or a NaN result and an Inexact flag)
|
33
|
+
# * :free The input precision is preserved, and the destination context precision is ignored;
|
34
|
+
# in this case the result can be converted back to the original number (with the same precision)
|
35
|
+
# a rounding mode for the back conversion may be passed; otherwise any round-to-nearest is assumed.
|
36
|
+
# (to increase the precision of the result the input precision must be increased --adding trailing zeros)
|
37
|
+
# * :short is like :free, but the minumum number of digits that preserve the original value
|
38
|
+
# are generated (with :free, all significant digits are generated)
|
39
|
+
#
|
40
|
+
# For the fixed mode there are three conversion algorithms available that can be selected with the
|
41
|
+
# :algorithm parameter:
|
42
|
+
# * :A Arithmetic algorithm, using correctly rounded Flt::Num arithmetic.
|
43
|
+
# * :M The Clinger Algorithm M is the slowest method, but it was the first implemented and testes and
|
44
|
+
# is kept as a reference for testing.
|
45
|
+
# * :R The Clinger Algorithm R, which requires an initial approximation is currently only implemented
|
46
|
+
# for Float and is the fastest by far.
|
47
|
+
def initialize(options={})
|
48
|
+
@exact = nil
|
49
|
+
@algorithm = options[:algorithm]
|
50
|
+
@mode = options[:mode] || :fixed
|
51
|
+
end
|
52
|
+
|
53
|
+
def exact?
|
54
|
+
@exact
|
55
|
+
end
|
56
|
+
|
57
|
+
# Given exact integers f and e, with f nonnegative, returns the floating-point number
|
58
|
+
# closest to f * eb**e
|
59
|
+
# (eb is the input radix)
|
60
|
+
#
|
61
|
+
# If the context precision is exact an Inexact exception may occur (an NaN be returned)
|
62
|
+
# if an exact conversion is not possible.
|
63
|
+
#
|
64
|
+
# round_mode: in :fixed mode it specifies how to round the result (to the context precision); it
|
65
|
+
# is passed separate from context for flexibility.
|
66
|
+
# in :free mode it specifies what rounding would be used to convert back the output to the
|
67
|
+
# input base eb (using the same precision that f has).
|
68
|
+
def read(context, round_mode, sign, f, e, eb=10)
|
69
|
+
@exact = true
|
70
|
+
|
71
|
+
case @mode
|
72
|
+
when :free, :short
|
73
|
+
all_digits = (@mode == :free)
|
74
|
+
# for free mode, (any) :nearest rounding is used by default
|
75
|
+
Num.convert(Num[eb].Num(sign, f, e), context.num_class, :rounding=>round_mode||:nearest, :all_digits=>all_digits)
|
76
|
+
when :fixed
|
77
|
+
if exact_mode = context.exact?
|
78
|
+
a,b = [eb, context.radix].sort
|
79
|
+
m = (Math.log(b)/Math.log(a)).round
|
80
|
+
if b == a**m
|
81
|
+
# conmensurable bases
|
82
|
+
if eb > context.radix
|
83
|
+
n = AuxiliarFunctions._ndigits(f, eb)*m
|
84
|
+
else
|
85
|
+
n = (AuxiliarFunctions._ndigits(f, eb)+m-1)/m
|
86
|
+
end
|
87
|
+
else
|
88
|
+
# inconmesurable bases; exact result may not be possible
|
89
|
+
x = Num[eb].Num(sign, f, e)
|
90
|
+
x = Num.convert_exact(x, context.num_class, context)
|
91
|
+
@exact = !x.nan?
|
92
|
+
return x
|
93
|
+
end
|
94
|
+
else
|
95
|
+
n = context.precision
|
96
|
+
end
|
97
|
+
if round_mode == :nearest
|
98
|
+
# :nearest is not meaningful here in :fixed mode; replace it
|
99
|
+
if [:half_even, :half_up, :half_down].include?(context.rounding)
|
100
|
+
round_mode = context.rounding
|
101
|
+
else
|
102
|
+
round_mode = :half_even
|
103
|
+
end
|
104
|
+
end
|
105
|
+
# for fixed mode, use the context rounding by default
|
106
|
+
round_mode ||= context.rounding
|
107
|
+
alg = @algorithm
|
108
|
+
if (context.radix == 2 && alg.nil?) || alg==:R
|
109
|
+
z0 = _alg_r_approx(context, round_mode, sign, f, e, eb, n)
|
110
|
+
alg = z0 && :R
|
111
|
+
end
|
112
|
+
alg ||= :A
|
113
|
+
case alg
|
114
|
+
when :M, :R
|
115
|
+
round_mode = Support.simplified_round_mode(round_mode, sign == -1)
|
116
|
+
case alg
|
117
|
+
when :M
|
118
|
+
_alg_m(context, round_mode, sign, f, e, eb, n)
|
119
|
+
when :R
|
120
|
+
_alg_r(z0, context, round_mode, sign, f, e, eb, n)
|
121
|
+
end
|
122
|
+
else # :A
|
123
|
+
# direct arithmetic conversion
|
124
|
+
if round_mode == context.rounding
|
125
|
+
x = Num.convert_exact(Num[eb].Num(sign, f, e), context.num_class, context)
|
126
|
+
x = context.normalize(x) unless !context.respond_to?(:normalize) || context.exact?
|
127
|
+
x
|
128
|
+
else
|
129
|
+
if context.num_class == Float
|
130
|
+
float = true
|
131
|
+
context = BinNum::FloatContext
|
132
|
+
end
|
133
|
+
x = context.num_class.context(context) do |local_context|
|
134
|
+
local_context.rounding = round_mode
|
135
|
+
Num.convert_exact(Num[eb].Num(sign, f, e), local_context.num_class, local_context)
|
136
|
+
end
|
137
|
+
if float
|
138
|
+
x = x.to_f
|
139
|
+
else
|
140
|
+
x = context.normalize(x) unless context.exact?
|
141
|
+
end
|
142
|
+
x
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def _alg_r_approx(context, round_mode, sign, f, e, eb, n)
|
149
|
+
|
150
|
+
return nil if context.radix != Float::RADIX || context.exact? || context.precision > Float::MANT_DIG
|
151
|
+
|
152
|
+
# Compute initial approximation; if Float uses IEEE-754 binary arithmetic, the approximation
|
153
|
+
# is good enough to be adjusted in just one step.
|
154
|
+
@good_approx = true
|
155
|
+
|
156
|
+
ndigits = Support::AuxiliarFunctions._ndigits(f, eb)
|
157
|
+
adj_exp = e + ndigits - 1
|
158
|
+
min_exp, max_exp = Reader.float_min_max_adj_exp(eb)
|
159
|
+
|
160
|
+
if adj_exp >= min_exp && adj_exp <= max_exp
|
161
|
+
if eb==2
|
162
|
+
z0 = Math.ldexp(f,e)
|
163
|
+
elsif eb==10
|
164
|
+
unless Flt.float_correctly_rounded?
|
165
|
+
min_exp_norm, max_exp_norm = Reader.float_min_max_adj_exp(eb, true)
|
166
|
+
@good_approx = false
|
167
|
+
return nil if e <= min_exp_norm
|
168
|
+
end
|
169
|
+
z0 = Float("#{f}E#{e}")
|
170
|
+
else
|
171
|
+
ff = f
|
172
|
+
ee = e
|
173
|
+
min_exp_norm, max_exp_norm = Reader.float_min_max_adj_exp(eb, true)
|
174
|
+
if e <= min_exp_norm
|
175
|
+
# avoid loss of precision due to gradual underflow
|
176
|
+
return nil if e <= min_exp
|
177
|
+
@good_approx = false
|
178
|
+
ff = Float(f)*Float(eb)**(e-min_exp_norm-1)
|
179
|
+
ee = min_exp_norm + 1
|
180
|
+
end
|
181
|
+
# if ee < 0
|
182
|
+
# z0 = Float(ff)/Float(eb**(-ee))
|
183
|
+
# else
|
184
|
+
# z0 = Float(ff)*Float(eb**ee)
|
185
|
+
# end
|
186
|
+
z0 = Float(ff)*Float(eb)**ee
|
187
|
+
end
|
188
|
+
|
189
|
+
if z0 && context.num_class != Float
|
190
|
+
@good_approx = false
|
191
|
+
z0 = context.Num(z0).plus(context) # context.plus(z0) ?
|
192
|
+
else
|
193
|
+
z0 = context.Num(z0)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
def _alg_r(z0, context, round_mode, sign, f, e, eb, n) # Fast for Float
|
200
|
+
#raise InvalidArgument, "Reader Algorithm R only supports base 2" if context.radix != 2
|
201
|
+
|
202
|
+
@z = z0
|
203
|
+
@r = context.radix
|
204
|
+
@rp_n_1 = context.int_radix_power(n-1)
|
205
|
+
@round_mode = round_mode
|
206
|
+
|
207
|
+
ret = nil
|
208
|
+
loop do
|
209
|
+
m, k = context.to_int_scale(@z)
|
210
|
+
# TODO: replace call to compare by setting the parameters in local variables,
|
211
|
+
# then insert the body of compare here;
|
212
|
+
# then eliminate innecesary instance variables
|
213
|
+
if e >= 0 && k >= 0
|
214
|
+
ret = compare m, f*eb**e, m*@r**k, context
|
215
|
+
elsif e >= 0 && k < 0
|
216
|
+
ret = compare m, f*eb**e*@r**(-k), m, context
|
217
|
+
elsif e < 0 && k >= 0
|
218
|
+
ret = compare m, f, m*@r**k*eb**(-e), context
|
219
|
+
else # e < 0 && k < 0
|
220
|
+
ret = compare m, f*@r**(-k), m*eb**(-e), context
|
221
|
+
end
|
222
|
+
break if ret
|
223
|
+
end
|
224
|
+
ret && context.copy_sign(ret, sign) # TODO: normalize?
|
225
|
+
end
|
226
|
+
|
227
|
+
@float_min_max_exp_values = {
|
228
|
+
10 => [Float::MIN_10_EXP, Float::MAX_10_EXP],
|
229
|
+
Float::RADIX => [Float::MIN_EXP, Float::MAX_EXP],
|
230
|
+
-Float::RADIX => [Float::MIN_EXP-Float::MANT_DIG, Float::MAX_EXP-Float::MANT_DIG]
|
231
|
+
}
|
232
|
+
class <<self
|
233
|
+
# Minimum & maximum adjusted exponent for numbers in base to be in the range of Floats
|
234
|
+
def float_min_max_adj_exp(base, normalized=false)
|
235
|
+
k = normalized ? base : -base
|
236
|
+
unless min_max = @float_min_max_exp_values[k]
|
237
|
+
max_exp = (Math.log(Float::MAX)/Math.log(base)).floor
|
238
|
+
e = Float::MIN_EXP
|
239
|
+
e -= Float::MANT_DIG unless normalized
|
240
|
+
min_exp = (e*Math.log(Float::RADIX)/Math.log(base)).ceil
|
241
|
+
@float_min_max_exp_values[k] = min_max = [min_exp, max_exp]
|
242
|
+
end
|
243
|
+
min_max.map{|exp| exp - 1} # adjust
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def compare(m, x, y, context)
|
248
|
+
ret = nil
|
249
|
+
d = x-y
|
250
|
+
d2 = 2*m*d.abs
|
251
|
+
|
252
|
+
# v = f*eb**e is the number to be approximated
|
253
|
+
# z = m*@r**k is the current aproximation
|
254
|
+
# the error of @z is eps = abs(v-z) = 1/2 * d2 / y
|
255
|
+
# we have x, y integers such that x/y = v/z
|
256
|
+
# so eps < 1/2 <=> d2 < y
|
257
|
+
# d < 0 <=> x < y <=> v < z
|
258
|
+
|
259
|
+
directed_rounding = [:up, :down].include?(@round_mode)
|
260
|
+
|
261
|
+
if directed_rounding
|
262
|
+
if @round_mode==:up ? (d <= 0) : (d < 0)
|
263
|
+
# v <(=) z
|
264
|
+
chk = (m == @rp_n_1) ? d2*@r : d2
|
265
|
+
if (@round_mode == :up) && (chk < 2*y)
|
266
|
+
# eps < 1
|
267
|
+
ret = @z
|
268
|
+
else
|
269
|
+
@z = context.next_minus(@z)
|
270
|
+
end
|
271
|
+
else # @round_mode==:up ? (d > 0) : (d >= 0)
|
272
|
+
# v >(=) z
|
273
|
+
if (@round_mode == :down) && (d2 < 2*y)
|
274
|
+
# eps < 1
|
275
|
+
ret = @z
|
276
|
+
else
|
277
|
+
@z = context.next_plus(@z)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
else
|
281
|
+
if d2 < y # eps < 1/2
|
282
|
+
if (m == @rp_n_1) && (d < 0) && (y < @r*d2)
|
283
|
+
# z has the minimum normalized significand, i.e. is a power of @r
|
284
|
+
# and v < z
|
285
|
+
# and @r*eps > 1/2
|
286
|
+
# On the left of z the ulp is 1/@r than the ulp on the right; if v < z we
|
287
|
+
# must require an error @r times smaller.
|
288
|
+
@z = context.next_minus(@z)
|
289
|
+
else
|
290
|
+
# unambiguous nearest
|
291
|
+
ret = @z
|
292
|
+
end
|
293
|
+
elsif d2 == y # eps == 1/2
|
294
|
+
# round-to-nearest tie
|
295
|
+
if @round_mode == :half_even
|
296
|
+
if (m%2) == 0
|
297
|
+
# m is even
|
298
|
+
if (m == @rp_n_1) && (d < 0)
|
299
|
+
# z is power of @r and v < z; this wasn't really a tie because
|
300
|
+
# there are closer values on the left
|
301
|
+
@z = context.next_minus(@z)
|
302
|
+
else
|
303
|
+
# m is even => round tie to z
|
304
|
+
ret = @z
|
305
|
+
end
|
306
|
+
elsif d < 0
|
307
|
+
# m is odd, v < z => round tie to prev
|
308
|
+
ret = context.next_minus(@z)
|
309
|
+
elsif d > 0
|
310
|
+
# m is odd, v > z => round tie to next
|
311
|
+
ret = context.next_plus(@z)
|
312
|
+
end
|
313
|
+
elsif @round_mode == :half_up
|
314
|
+
if d < 0
|
315
|
+
# v < z
|
316
|
+
if (m == @rp_n_1)
|
317
|
+
# this was not really a tie
|
318
|
+
@z = context.next_minus(@z)
|
319
|
+
else
|
320
|
+
ret = @z
|
321
|
+
end
|
322
|
+
else # d > 0
|
323
|
+
# v >= z
|
324
|
+
ret = context.next_plus(@z)
|
325
|
+
end
|
326
|
+
else # @round_mode == :half_down
|
327
|
+
if d < 0
|
328
|
+
# v < z
|
329
|
+
if (m == @rp_n_1)
|
330
|
+
# this was not really a tie
|
331
|
+
@z = context.next_minus(@z)
|
332
|
+
else
|
333
|
+
ret = context.next_minus(@z)
|
334
|
+
end
|
335
|
+
else # d < 0
|
336
|
+
# v > z
|
337
|
+
ret = @z
|
338
|
+
end
|
339
|
+
end
|
340
|
+
elsif d < 0 # eps > 1/2 and v < z
|
341
|
+
@z = context.next_minus(@z)
|
342
|
+
elsif d > 0 # eps > 1/2 and v > z
|
343
|
+
@z = context.next_plus(@z)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Assume the initial approx is good enough (uses IEEE-754 arithmetic with round-to-nearest),
|
348
|
+
# so we can avoid further iteration, except for directed rounding
|
349
|
+
ret ||= @z unless directed_rounding || !@good_approx
|
350
|
+
|
351
|
+
return ret
|
352
|
+
end
|
353
|
+
|
354
|
+
# Algorithm M to read floating point numbers from text literals with correct rounding
|
355
|
+
# from his paper: "How to Read Floating Point Numbers Accurately" (William D. Clinger)
|
356
|
+
def _alg_m(context, round_mode, sign, f, e, eb, n)
|
357
|
+
if e<0
|
358
|
+
u,v,k = f,eb**(-e),0
|
359
|
+
else
|
360
|
+
u,v,k = f*(eb**e),1,0
|
361
|
+
end
|
362
|
+
min_e = context.etiny
|
363
|
+
max_e = context.etop
|
364
|
+
rp_n = context.int_radix_power(n)
|
365
|
+
rp_n_1 = context.int_radix_power(n-1)
|
366
|
+
r = context.radix
|
367
|
+
loop do
|
368
|
+
x = u.div(v) # bottleneck
|
369
|
+
if (x>=rp_n_1 && x<rp_n) || k==min_e || k==max_e
|
370
|
+
z, exact = Reader.ratio_float(context,u,v,k,round_mode)
|
371
|
+
@exact = exact
|
372
|
+
if context.respond_to?(:exception)
|
373
|
+
if k==min_e
|
374
|
+
context.exception(Num::Subnormal) if z.subnormal?
|
375
|
+
context.exception(Num::Underflow,"Input literal out of range") if z.zero? && f!=0
|
376
|
+
elsif k==max_e
|
377
|
+
if !context.exact? && z.coefficient > context.maximum_coefficient
|
378
|
+
context.exception(Num::Overflow,"Input literal out of range")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
context.exception Num::Inexact if !exact
|
382
|
+
end
|
383
|
+
return z.copy_sign(sign)
|
384
|
+
elsif x<rp_n_1
|
385
|
+
u *= r
|
386
|
+
k -= 1
|
387
|
+
elsif x>=rp_n
|
388
|
+
v *= r
|
389
|
+
k += 1
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# Given exact positive integers u and v with beta**(n-1) <= u/v < beta**n
|
395
|
+
# and exact integer k, returns the floating point number closest to u/v * beta**n
|
396
|
+
# (beta is the floating-point radix)
|
397
|
+
def self.ratio_float(context, u, v, k, round_mode)
|
398
|
+
# since this handles only positive numbers and ceiling and floor
|
399
|
+
# are not symmetrical, they should have been swapped before calling this.
|
400
|
+
q = u.div v
|
401
|
+
r = u-q*v
|
402
|
+
v_r = v-r
|
403
|
+
z = context.Num(+1,q,k)
|
404
|
+
exact = (r==0)
|
405
|
+
if round_mode == :down
|
406
|
+
# z = z
|
407
|
+
elsif (round_mode == :up) && r>0
|
408
|
+
z = context.next_plus(z)
|
409
|
+
elsif r<v_r
|
410
|
+
# z = z
|
411
|
+
elsif r>v_r
|
412
|
+
z = context.next_plus(z)
|
413
|
+
else
|
414
|
+
# tie
|
415
|
+
if (round_mode == :half_down) || (round_mode == :half_even && ((q%2)==0)) || (round_mode == :down)
|
416
|
+
# z = z
|
417
|
+
else
|
418
|
+
z = context.next_plus(z)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
return z, exact
|
422
|
+
end
|
423
|
+
|
424
|
+
end # Reader
|
425
|
+
|
426
|
+
end
|
427
|
+
end
|
data/lib/flt/tolerance.rb
CHANGED
@@ -549,6 +549,7 @@ module Flt
|
|
549
549
|
#
|
550
550
|
# Finally any additional parameters admitted by the class constructor can be passed.
|
551
551
|
def Tolerance(*args)
|
552
|
+
return args.first if args.size == 1 && Tolerance === args.first
|
552
553
|
if args.first.is_a?(Symbol)
|
553
554
|
value = nil
|
554
555
|
else
|
data/lib/flt/version.rb
CHANGED
data/test/generate_trig_data.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Generate test data for trigonometry tests (test_trig.
|
1
|
+
# Generate test data for trigonometry tests (test_trig.rb)
|
2
2
|
|
3
3
|
require File.expand_path(File.join(File.dirname(__FILE__),'helper.rb'))
|
4
4
|
require File.dirname(__FILE__) + '/../lib/flt/math'
|
@@ -83,7 +83,7 @@ end
|
|
83
83
|
|
84
84
|
|
85
85
|
def gen_test_data(num_class, prec, angle_units=:rad)
|
86
|
-
dir = File.dirname(__FILE__)+'/trigtest'
|
86
|
+
dir = File.dirname(__FILE__)+'/data/trigtest'
|
87
87
|
num_class.context(:precision=>prec) do
|
88
88
|
num_class.context.angle = angle_units
|
89
89
|
DecNum.context.traps[DecNum::DivisionByZero] = false
|