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,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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Flt
2
- VERSION = "1.3.4"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -0,0 +1,4 @@
1
+ # Ignore everything in this directory
2
+ *
3
+ # Except this file
4
+ !.gitignore
@@ -1,4 +1,4 @@
1
- # Generate test data for trigonometry tests (test_trig.rbG)
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