flt 1.0.0 → 1.1.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.
@@ -1,4 +1,23 @@
1
- == 1.0.0 2009-07-22
1
+ == 1.1.0 2009-11-30
2
+
3
+ * New Features:
4
+ - Access Math functions through context
5
+ - Enhanced compatibility with Float
6
+ - Enhanced DecNum trigonometry
7
+ - Context eval method
8
+ - New methods integer_part(), fraction_part()
9
+
10
+ * Maintenance changes:
11
+ - Minor internal refactoring.
12
+ - Avoid some warnings on Ruby 1.9
13
+ - Better documentation
14
+
15
+ * Bugfixes:
16
+ - local context blocks didn't restore previous context on return or exceptions,
17
+ - Tolerance fix for near zero numbers
18
+ - Other internal problems
19
+
20
+ == 1.0.0 2009-08-05
2
21
 
3
22
  * First release of the new Flt project (derived from the former Ruby-Decimal.)
4
23
  - New gem name: flt
data/lib/flt.rb CHANGED
@@ -4,6 +4,7 @@ require 'flt/version'
4
4
  require 'flt/support'
5
5
  require 'flt/float'
6
6
  require 'flt/bigdecimal'
7
+ require 'flt/num'
7
8
  require 'flt/dec_num'
8
9
  require 'flt/bin_num'
9
10
  require 'flt/tolerance'
@@ -1,9 +1,9 @@
1
1
  # Support classes for homogeneous treatment of BigDecimal and Num values by defining BigDecimal.context
2
2
 
3
- require 'flt'
3
+ require 'flt/num'
4
+
4
5
  require 'bigdecimal'
5
6
  require 'bigdecimal/math'
6
-
7
7
  require 'singleton'
8
8
 
9
9
  # Context class with some of the Flt::Num context functionality, to allow the use of BigDecimal numbers
@@ -14,6 +14,10 @@ class Flt::BigDecimalContext
14
14
  include Singleton
15
15
  # TODO: Context class with precision, rounding, etc. (no singleton)
16
16
 
17
+ def eval
18
+ yield self
19
+ end
20
+
17
21
  def num_class
18
22
  BigDecimal
19
23
  end
@@ -19,13 +19,13 @@ class BinNum < Num
19
19
  # Multiply by an integral power of the base: x*(radix**n) for x,n integer;
20
20
  # returns an integer.
21
21
  def int_mult_radix_power(x,n)
22
- x * ((n < 0) ? (2**n) : (1<<n))
22
+ n < 0 ? (x / (1<<(-n))) : (x * (1<<n))
23
23
  end
24
24
 
25
25
  # Divide by an integral power of the base: x/(radix**n) for x,n integer;
26
26
  # returns an integer.
27
27
  def int_div_radix_power(x,n)
28
- x / ((n < 0) ? (2**n) : (1<<n))
28
+ n < 0 ? (x * (1<<(-n))) : (x / (1<<n))
29
29
  end
30
30
  end
31
31
 
@@ -1,4 +1,5 @@
1
1
  require 'flt/num'
2
+ require 'flt/bigdecimal'
2
3
 
3
4
  module Flt
4
5
 
@@ -21,13 +22,13 @@ class DecNum < Num
21
22
  # Multiply by an integral power of the base: x*(radix**n) for x,n integer;
22
23
  # returns an integer.
23
24
  def int_mult_radix_power(x,n)
24
- x * (10**n)
25
+ n < 0 ? (x / (10**(-n))) : (x * (10**n))
25
26
  end
26
27
 
27
28
  # Divide by an integral power of the base: x/(radix**n) for x,n integer;
28
29
  # returns an integer.
29
30
  def int_div_radix_power(x,n)
30
- x / (10**n)
31
+ n < 0 ? (x * (10**(-n))) : (x / (10**n))
31
32
  end
32
33
  end
33
34
 
@@ -2,7 +2,9 @@
2
2
  #
3
3
  # The set of constants with Float metadata is also augmented.
4
4
 
5
- require 'flt'
5
+ require 'flt/num'
6
+ require 'singleton'
7
+
6
8
 
7
9
  # Float constants.
8
10
  #
@@ -79,8 +81,6 @@ class Float
79
81
 
80
82
  end
81
83
 
82
- require 'singleton'
83
-
84
84
  # Context class with some of the Flt::Num context functionality, to allow the use of Float numbers
85
85
  # similarly to other Num values; this eases the implementation of functions compatible with either
86
86
  # Num or Float values.
@@ -425,9 +425,14 @@ class Flt::FloatContext
425
425
 
426
426
  def math_function(*methods) #:nodoc:
427
427
  methods.each do |method|
428
- define_method(method) do |x|
429
- Math.send(method, x.to_f)
428
+ define_method(method) do |*args|
429
+ x = args.shift.to_f
430
+ Math.send(method, x, *args)
430
431
  end
432
+ # TODO: consider injecting the math methods into Float
433
+ # Float.send(:define_method, method) do |*args|
434
+ # Math.send(method, self, *args)
435
+ # end
431
436
  end
432
437
  end
433
438
 
@@ -441,8 +446,31 @@ class Flt::FloatContext
441
446
  float_binary_operator :power, :**
442
447
 
443
448
  math_function :log, :log10, :exp, :sqrt,
444
- :sin, :cos, :tan, :asin, :acos, :atan,
449
+ :sin, :cos, :tan, :asin, :acos, :atan, :atan2,
445
450
  :sinh, :cosh, :tanh, :asinh, :acosh, :atanh
451
+
452
+ def ln(x)
453
+ log(x)
454
+ end
455
+
456
+ def pi
457
+ Float::PI
458
+ end
459
+
460
+ def eval
461
+ yield self
462
+ end
463
+
464
+ def math(*parameters, &blk)
465
+ if parameters.empty?
466
+ self.instance_eval &blk
467
+ else
468
+ # needs instance_exe (available in Ruby 1.9, ActiveRecord; TODO: include implementation here)
469
+ self.instance_exec *parameters, &blk
470
+ end
471
+ end
472
+
473
+
446
474
  end
447
475
 
448
476
  # Return a (limited) context object for Float.
@@ -1,5 +1,10 @@
1
1
  require 'flt/dec_num'
2
2
 
3
+ # TODO:
4
+ # * convert arguments as Context#_convert does, to accept non DecNum arguments
5
+ # * Better precision (currently is within about 2 ulps of the correct result)
6
+ # * Speed optimization
7
+
3
8
  module Flt
4
9
  class DecNum
5
10
  module Math
@@ -8,59 +13,360 @@ module Flt
8
13
 
9
14
  module_function
10
15
 
11
- # Trinogometry
16
+ # Trigonometry
17
+
18
+ HALF = DecNum('0.5')
12
19
 
13
20
  # Pi
14
- def pi(decimals=nil)
15
- three = DecNum(3)
16
- lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24
17
- Flt::DecNum.context(:precision=>decimals) do |local_context|
18
- local_context.precision += 2 # extra digits for intermediate steps
19
- while s != lasts
20
- lasts = s
21
- n, na = n+na, na+8
22
- d, da = d+da, da+32
23
- t = (t * n) / d
24
- s += t
21
+ $no_cache = false
22
+ @@pi_cache = nil # truncated pi digits as a string
23
+ @@pi_cache_digits = 0
24
+ PI_MARGIN = 10
25
+ def pi(round_digits=nil)
26
+
27
+ round_digits ||= DecNum.context.precision
28
+ digits = round_digits
29
+ if @@pi_cache_digits <= digits # we need at least one more truncated digit
30
+ continue = true
31
+ while continue
32
+ margin = PI_MARGIN # margin to reduce recomputing with more digits to avoid ending in 0 or 5
33
+ digits += margin + 1
34
+ fudge = 10
35
+ unity = 10**(digits+fudge)
36
+ v = 4*(4*iarccot(5, unity) - iarccot(239, unity))
37
+ v = v.to_s[0,digits]
38
+ # if the last digit is 0 or 5 the truncated value may not be good for rounding
39
+ loop do
40
+ #last_digit = v%10
41
+ last_digit = v[-1,1].to_i
42
+ continue = (last_digit==5 || last_digit==0)
43
+ if continue && margin>0
44
+ # if we have margin we back-up one digit
45
+ margin -= 1
46
+ v = v[0...-1]
47
+ else
48
+ break
49
+ end
50
+ end
51
+ end
52
+ @@pi_cache_digits = digits + margin - PI_MARGIN # @pi_cache.size
53
+ @@pi_cache = v # DecNum(+1, v, 1-digits) # cache truncated value
25
54
  end
55
+ # Now we avoid rounding too much because it is slow
56
+ l = round_digits + 1
57
+ while (l<@@pi_cache_digits) && [0,5].include?(@@pi_cache[l-1,1].to_i)
58
+ l += 1
59
+ end
60
+ v = @@pi_cache[0,l]
61
+ DecNum.context(:precision=>round_digits){+DecNum(+1,v.to_i,1-l)}
62
+ end
63
+
64
+ def e(decimals=nil)
65
+ DecNum.context do |local_context|
66
+ local_context.precision = decimals if decimals
67
+ DecNum(1).exp
26
68
  end
27
- return +s
28
69
  end
29
70
 
30
71
  # Cosine of angle in radians
31
72
  def cos(x)
32
- i, lasts, s, fact, num, sign = 0, 0, 1, 1, 1, 1
73
+ x = x.abs
74
+ rev_sign = false
75
+ s = nil
33
76
  DecNum.context do |local_context|
34
- local_context.precision += 2 # extra digits for intermediate steps
77
+ local_context.precision += 3 # extra digits for intermediate steps
78
+ x,k,pi_2 = reduce_angle2(x,2)
79
+ rev_sign = true if k>1
80
+ if k % 2 == 0
81
+ x = pi_2 - x
82
+ else
83
+ rev_sign = !rev_sign
84
+ end
85
+ i, lasts, fact, num = 1, 0, 1, DecNum(x)
86
+ s = num
87
+ x2 = -x*x
35
88
  while s != lasts
36
89
  lasts = s
37
90
  i += 2
38
91
  fact *= i * (i-1)
39
- num *= x * x
40
- sign *= -1
41
- s += num / fact * sign
92
+ num *= x2
93
+ s += num / fact
42
94
  end
43
95
  end
44
- return +s
96
+ return rev_sign ? (-s) : (+s)
45
97
  end
46
98
 
47
99
  # Sine of angle in radians
48
100
  def sin(x)
49
- i, lasts, s, fact, num, sign = 1, 0, x, 1, x, 1
101
+ sign = x.sign
102
+ s = nil
50
103
  DecNum.context do |local_context|
51
- local_context.precision += 2 # extra digits for intermediate steps
104
+ local_context.precision += 3 # extra digits for intermediate steps
105
+ x = x.abs if sign<0
106
+ x,k,pi_2 = reduce_angle2(x,2)
107
+ sign = -sign if k>1
108
+ x = pi_2 - x if k % 2 == 1
109
+ x = +x
110
+ i, lasts, fact, num = 1, 0, 1, DecNum(x)
111
+ s = num
112
+ x2 = -x*x
52
113
  while s != lasts
53
114
  lasts = s
54
115
  i += 2
55
116
  fact *= i * (i-1)
56
- num *= x * x
57
- sign *= -1
58
- s += num / fact * sign
117
+ num *= x2
118
+ s += num / fact
119
+ end
120
+ end
121
+ return (+s).copy_sign(sign)
122
+ end
123
+
124
+ def tan(x)
125
+ +DecNum.context do |local_context|
126
+ local_context.precision += 2 # extra digits for intermediate steps
127
+ s,c = sin(x), cos(x)
128
+ s/c
129
+ end
130
+ end
131
+
132
+ # Arc-tangent.
133
+ def atan(x)
134
+ s = nil
135
+ DecNum.context do |local_context|
136
+ local_context.precision += 2
137
+ if x == 0
138
+ return DecNum.zero
139
+ elsif x.abs > 1
140
+ if x.infinite?
141
+ s = (pi*HALF).copy_sign(x)
142
+ break
143
+ else
144
+ c = (pi*HALF).copy_sign(x)
145
+ x = 1 / x
146
+ end
147
+ end
148
+ local_context.precision += 2
149
+ x_squared = x ** 2
150
+ y = x_squared / (1 + x_squared)
151
+ y_over_x = y / x
152
+ i = DecNum.zero; lasts = 0; s = y_over_x; coeff = 1; num = y_over_x
153
+ while s != lasts
154
+ lasts = s
155
+ i += 2
156
+ coeff *= i / (i + 1)
157
+ num *= y
158
+ s += coeff * num
159
+ end
160
+ if c && c!= 0
161
+ s = c - s
59
162
  end
60
163
  end
61
164
  return +s
62
165
  end
63
166
 
167
+ def atan2(y, x)
168
+ abs_y = y.abs
169
+ abs_x = x.abs
170
+ y_is_real = !x.infinite?
171
+
172
+ if x != 0
173
+ if y_is_real
174
+ a = y!=0 ? atan(y / x) : DecNum.zero
175
+ a += pi.copy_sign(y) if x < 0
176
+ return a
177
+ elsif abs_y == abs_x
178
+ x = DecNum(1).copy_sign(x)
179
+ y = DecNum(1).copy_sign(y)
180
+ return pi * (2 - x) / (4 * y)
181
+ end
182
+ end
183
+
184
+ if y != 0
185
+ return atan(DecNum.infinity(y.sign))
186
+ elsif x < 0
187
+ return pi.copy_sign(x)
188
+ else
189
+ return DecNum.zero
190
+ end
191
+ end
192
+
193
+ def asin(x)
194
+ x = +x
195
+ return DecNum.context.exception(Num::InvalidOperation, 'asin needs -1 <= x <= 1') if x.abs > 1
196
+
197
+ if x == -1
198
+ return -pi*HALF
199
+ elsif x == 0
200
+ return DecNum.zero
201
+ elsif x == 1
202
+ return pi*HALF
203
+ end
204
+
205
+ DecNum.context do |local_context|
206
+ local_context.precision += 3
207
+ x = x/(1-x*x).sqrt
208
+ x = atan(x)
209
+ end
210
+ +x
211
+ end
212
+
213
+ def acos(x)
214
+
215
+ # We can compute acos(x) = pi/2 - asin(x)
216
+ # but we must take care with x near 1, where that formula would cause loss of precision
217
+
218
+ return DecNum.context.exception(Num::InvalidOperation, 'acos needs -1 <= x <= 2') if x.abs > 1
219
+
220
+ if x == -1
221
+ return pi
222
+ elsif x == 0
223
+ return +DecNum.context(:precision=>DecNum.context.precision+3){pi*HALF}
224
+ elsif x == 1
225
+ return DecNum.zero
226
+ end
227
+
228
+ # some identities:
229
+ # acos(x) = pi/2 - asin(x) # (but this losses accuracy near x=+1)
230
+ # acos(x) = pi/2 - atan(x/(1-x*x).sqrt) # this too
231
+ # acos(x) = asin((1-x*x).sqrt) for x>=0; for x<=0 acos(x) = pi/2 - asin((1-x*x).sqrt)
232
+
233
+ if x < HALF
234
+ DecNum.context do |local_context|
235
+ local_context.precision += 3
236
+ x = x/(1-x*x).sqrt
237
+ x = pi*HALF - atan(x)
238
+ end
239
+ else
240
+ # valid for x>=0
241
+ DecNum.context do |local_context|
242
+ local_context.precision += 3
243
+ x = (1-x*x).sqrt
244
+ x = asin(x)
245
+ end
246
+ end
247
+ +x
248
+
249
+ end
250
+
251
+ # TODO: degrees mode or radians/degrees conversion
252
+
253
+ def pi2(decimals=nil)
254
+ decimals ||= DecNum.context.precision
255
+ DecNum.context(:precision=>decimals) do
256
+ pi(decimals)*2
257
+ end
258
+ end
259
+
260
+ def invpi(decimals=nil)
261
+ decimals ||= DecNum.context.precision
262
+ DecNum.context(:precision=>decimals) do
263
+ DecNum(1)/pi(decimals)
264
+ end
265
+ end
266
+
267
+ def inv2pi(decimals=nil)
268
+ decimals ||= DecNum.context.precision
269
+ DecNum.context(:precision=>decimals) do
270
+ DecNum(1)/pi2(decimals)
271
+ end
272
+ end
273
+
274
+ # class <<self
275
+ # private
276
+
277
+ def iarccot(x, unity)
278
+ xpow = unity / x
279
+ n = 1
280
+ sign = 1
281
+ sum = 0
282
+ loop do
283
+ term = xpow / n
284
+ break if term == 0
285
+ sum += sign * (xpow/n)
286
+ xpow /= x*x
287
+ n += 2
288
+ sign = -sign
289
+ end
290
+ sum
291
+ end
292
+
293
+ def modtwopi(x)
294
+ return +DecNum.context(:precision=>DecNum.context.precision*3){x.modulo(pi2)}
295
+ # This seems to be slower and less accurate:
296
+ # prec = DecNum.context.precision
297
+ # pi_2 = pi2(prec*2)
298
+ # return x if x < pi_2
299
+ # ex = x.fractional_exponent
300
+ # DecNum.context do |local_context|
301
+ # # x.modulo(pi_2)
302
+ # local_context.precision *= 2
303
+ # if ex > prec
304
+ # # consider exponent separately
305
+ # fd = nil
306
+ # excess = ex - prec
307
+ # x = x.scaleb(prec-ex)
308
+ # # now obtain 2*prec digits from inv2pi after the initial excess digits
309
+ # digits = nil
310
+ # inv_2pi = inv2pi(local_context.precision+excess)
311
+ # DecNum.context do |extended_context|
312
+ # extended_context.precision += excess
313
+ # digits = (inv2pi.scaleb(excess)).fraction_part
314
+ # end
315
+ # x *= digits*pi_2
316
+ # end
317
+ # # compute the fractional part of the division by 2pi
318
+ # inv_2pi ||= inv2pi
319
+ # x = pi_2*((x*inv2pi).fraction_part)
320
+ # end
321
+ # +x
322
+ end
323
+
324
+ # Reduce angle to [0,2Pi)
325
+ def reduce_angle(a)
326
+ modtwopi(a)
327
+ end
328
+
329
+ # Reduce angle to [0,Pi/k0) (result is not rounded to precision)
330
+ def reduce_angle2(a,k0=nil) # divisor of pi or nil for pi*2
331
+ # we could reduce first to pi*2 to avoid the mod k0 operation
332
+ k,r,divisor = DecNum.context do
333
+ DecNum.context.precision *= 3
334
+ m = k0.nil? ? pi2 : pi/k0
335
+ a.divmod(m)+[m]
336
+ end
337
+ [r, k.modulo(k0*2).to_i, divisor]
338
+ end
339
+
340
+ #end
341
+
64
342
  end # Math
343
+
344
+ class <<self
345
+ private
346
+ # declare math functions and inject them into the context class
347
+ def math_function(*functions)
348
+ functions.each do |f|
349
+ # TODO: consider injecting the math methods into the numeric class
350
+ # define_method(f) do |*args|
351
+ # Math.send(f, self, *args)
352
+ # end
353
+ Num::ContextBase.send :define_method,f do |*args|
354
+ x = Num(args.shift)
355
+ Math.send(f, x, *args)
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+ math_function :sin, :cos, :tan, :atan, :asin, :acos
362
+
363
+ def pi
364
+ Math.pi
365
+ end
366
+
367
+ def e
368
+ Math.e
369
+ end
370
+
65
371
  end # DecNum
66
372
  end # Flt
@@ -413,6 +413,38 @@ class Num < Numeric
413
413
 
414
414
  end
415
415
 
416
+ # Evaluate a block under a context (set up the context as a local context)
417
+ #
418
+ # When we have a context object we can use this instead of using the context method of the
419
+ # numeric class, e.g.:
420
+ # DecNum.context(context) { ... }
421
+ # This saves verbosity, specially when numeric class is not fixed, in which case
422
+ # we would have to write:
423
+ # context.num_class.context(context) { ... }
424
+ # With this method, we simply write:
425
+ # context.eval { ... }
426
+ def eval(&blk)
427
+ # TODO: consider other names for this method; use ? apply ? local ? with ?
428
+ num_class.context(self, &blk)
429
+ end
430
+
431
+ # Evalute a block under a context (set up the context as a local context) and inject the
432
+ # context methods (math and otherwise) into the block scope.
433
+ #
434
+ # This allows the use of regular algebraic notations for math functions,
435
+ # e.g. exp(x) instead of x.exp
436
+ def math(*parameters, &blk)
437
+ # TODO: consider renaming this to eval
438
+ num_class.context(self) do
439
+ if parameters.empty?
440
+ num_class.context.instance_eval &blk
441
+ else
442
+ # needs instance_exe (available in Ruby 1.9, ActiveRecord; TODO: include implementation here)
443
+ num_class.context.instance_exec *parameters, &blk
444
+ end
445
+ end
446
+ end
447
+
416
448
  # This gives access to the numeric class (Flt::Num-derived) this context is for.
417
449
  def num_class
418
450
  @num_class
@@ -1172,18 +1204,21 @@ class Num < Numeric
1172
1204
  # options to apply to the local scope.
1173
1205
  # Changes done to the current context are reversed when the scope is exited.
1174
1206
  def self.local_context(*args)
1175
- keep = self.context # use this so _context is initialized if necessary
1176
- self.context = define_context(*args) # this dups the assigned context
1177
- result = yield _context
1178
- # TODO: consider the convenience of copying the flags from DecNum.context to keep
1179
- # This way a local context does not affect the settings of the previous context,
1180
- # but flags are transferred.
1181
- # (this could be done always or be controlled by some option)
1182
- # keep.flags = DecNum.context.flags
1183
- # Another alternative to consider: logically or the flags:
1184
- # keep.flags ||= DecNum.context.flags # (this requires implementing || in Flags)
1185
- self._context = keep
1186
- result
1207
+ begin
1208
+ keep = self.context # use this so _context is initialized if necessary
1209
+ self.context = define_context(*args) # this dups the assigned context
1210
+ result = yield _context
1211
+ ensure
1212
+ # TODO: consider the convenience of copying the flags from DecNum.context to keep
1213
+ # This way a local context does not affect the settings of the previous context,
1214
+ # but flags are transferred.
1215
+ # (this could be done always or be controlled by some option)
1216
+ # keep.flags = DecNum.context.flags
1217
+ # Another alternative to consider: logically or the flags:
1218
+ # keep.flags ||= DecNum.context.flags # (this requires implementing || in Flags)
1219
+ self._context = keep
1220
+ result
1221
+ end
1187
1222
  end
1188
1223
 
1189
1224
  class <<self
@@ -1232,11 +1267,11 @@ class Num < Numeric
1232
1267
  end
1233
1268
 
1234
1269
  def int_mult_radix_power(x,n)
1235
- x * self.radix**n
1270
+ n < 0 ? (x / self.radix**(-n)) : (x * self.radix**n)
1236
1271
  end
1237
1272
 
1238
1273
  def int_div_radix_power(x,n)
1239
- x / self.radix**n
1274
+ n < 0 ? (x * self.radix**(-n) ) : (x / self.radix**n)
1240
1275
  end
1241
1276
  end
1242
1277
 
@@ -2482,6 +2517,21 @@ class Num < Numeric
2482
2517
  @exp
2483
2518
  end
2484
2519
 
2520
+ # Integer part (as a Num)
2521
+ def integer_part
2522
+ ans = _check_nans
2523
+ return ans if ans
2524
+ return_as_num = {:places=>0}
2525
+ self.sign < 0 ? self.ceil(return_as_num) : self.floor(return_as_num)
2526
+ end
2527
+
2528
+ # Fraction part (as a Num)
2529
+ def fraction_part
2530
+ ans = _check_nans
2531
+ return ans if ans
2532
+ self - self.integer_part
2533
+ end
2534
+
2485
2535
  # Return the value of the number as an signed integer and a scale.
2486
2536
  def to_int_scale
2487
2537
  if special?
@@ -3680,7 +3730,7 @@ class Num < Numeric
3680
3730
 
3681
3731
  # Parse numeric text literals (internal use)
3682
3732
  def _parser(txt)
3683
- md = /^\s*([-+])?(?:(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([-+]?\d+))?|Inf(?:inity)?|(s)?NaN(\d*))\s*$/i.match(txt)
3733
+ md = /^\s*([-+])?(?:(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:E([-+]?\d+))?|Inf(?:inity)?|(s)?NaN(\d*))\s*$/i.match(txt)
3684
3734
  if md
3685
3735
  OpenStruct.new :sign=>md[1], :int=>md[2], :frac=>md[3], :onlyfrac=>md[4], :exp=>md[5],
3686
3736
  :signal=>md[6], :diag=>md[7]
@@ -483,9 +483,9 @@ module Flt
483
483
  float = true
484
484
  context = BinNum::FloatContext
485
485
  end
486
- x = context.num_class.context(context) do |context|
487
- context.rounding = round_mode
488
- Num.convert_exact(Num[eb].Num(sign, f, e), context.num_class, context)
486
+ x = context.num_class.context(context) do |loca_context|
487
+ local_context.rounding = round_mode
488
+ Num.convert_exact(Num[eb].Num(sign, f, e), local_context.num_class, local_context)
489
489
  end
490
490
  if float
491
491
  x = x.to_f
@@ -593,7 +593,7 @@ module Flt
593
593
  min_exp = (e*Math.log(Float::RADIX)/Math.log(base)).ceil
594
594
  @float_min_max_exp_values[k] = min_max = [min_exp, max_exp]
595
595
  end
596
- min_max.map{|e| e - 1} # adjust
596
+ min_max.map{|exp| exp - 1} # adjust
597
597
  end
598
598
  end
599
599
 
@@ -1330,6 +1330,25 @@ module Flt
1330
1330
  end # Support
1331
1331
 
1332
1332
 
1333
-
1334
-
1335
- end # Flt
1333
+ end # Flt
1334
+
1335
+ # The math method of context needs instance_exec to pass parameters to the blocks evaluated
1336
+ # with a modified self (pointing to a context object.) intance_exec is available in Ruby 1.9.1 and
1337
+ # is also defined by ActiveRecord. Here we use Mauricio Fernández implementation if it is not
1338
+ # available.
1339
+ class Object
1340
+ unless defined? instance_exec
1341
+ module InstanceExecHelper; end
1342
+ include InstanceExecHelper
1343
+ def instance_exec(*args, &block) # !> method redefined; discarding old instance_exec
1344
+ mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
1345
+ InstanceExecHelper.module_eval{ define_method(mname, &block) }
1346
+ begin
1347
+ ret = send(mname, *args)
1348
+ ensure
1349
+ InstanceExecHelper.module_eval{ undef_method(mname) } rescue nil
1350
+ end
1351
+ ret
1352
+ end
1353
+ end
1354
+ end
@@ -3,17 +3,18 @@
3
3
  # Tolerance can be used to allow for a tolerance in floating-point comparisons.
4
4
  #
5
5
  # A Tolerance can be defined independently of the type (floating-point numeric class)
6
- # it will be used with; The actual tolerance value will be compute for a particular reference value:
6
+ # it will be used with; The actual tolerance value will be compute for a particular reference value, and
7
+ # for some kinds of tolerance (e.g. epsilon) a value is not available without a reference:
7
8
  #
8
9
  # tol = Tolerance(3, :decimals)
9
- # puts tol.value(DecNum('10.0')).inspect
10
- # puts tol.value(10.0).inspect
11
- # puts tol.value.inspect
10
+ # puts tol.value(DecNum('10.0')).inspect # -> DecNum('0.0005')
11
+ # puts tol.value(10.0).inspect # -> 0.0005
12
+ # puts tol.value.inspect # -> Rational(1, 2000)
12
13
  #
13
14
  # tol = Tolerance(:epsilon)
14
- # puts tol.value(DecNum('10.0')).inspect
15
- # puts tol.value(10.0).inspect
16
- # puts tol.value.inspect
15
+ # puts tol.value(DecNum('10.0')).inspect # -> DecNum('1.00E-26')
16
+ # puts tol.value(10.0).inspect # -> 2.22044604925031e-15
17
+ # puts tol.value.inspect # -> nil
17
18
  #
18
19
  # Tolerances can be:
19
20
  # * Absolute: the tolerance value is a fixed value independent of the values to be compared.
@@ -57,36 +58,36 @@
57
58
  # Examples:
58
59
  #
59
60
  # tol = Tolerance(100, :absolute)
60
- # puts tol.value(1.0)
61
- # puts tol.value(1.5)
62
- # puts tol.value(1.0E10)
63
- # puts tol.eq?(11234.0, 11280.0)
61
+ # puts tol.value(1.0) # -> 100.0
62
+ # puts tol.value(1.5) # -> 100.0
63
+ # puts tol.value(1.0E10) # -> 100.0
64
+ # puts tol.eq?(11234.0, 11280.0) # -> true
64
65
  #
65
66
  # tol = Tolerance(100, :relative)
66
- # puts tol.value(1.0)
67
- # puts tol.value(1.5)
68
- # puts tol.value(1.0E10)
69
- # puts tol.eq?(11234.0, 11280.0)
67
+ # puts tol.value(1.0) # -> 100.0
68
+ # puts tol.value(1.5) # -> 150.0
69
+ # puts tol.value(1.0E10) # -> 1000000000000.0
70
+ # puts tol.eq?(11234.0, 11280.0) # -> true
70
71
  #
71
72
  # tol = Tolerance(100, :floating)
72
- # puts tol.value(1.0)
73
- # puts tol.value(1.5)
74
- # puts tol.value(1.0E10)
75
- # puts tol.eq?(11234.0, 11280.0)
73
+ # puts tol.value(1.0) # -> 100.0
74
+ # puts tol.value(1.5) # -> 200.0
75
+ # puts tol.value(1.0E10) # -> 1717986918400.0
76
+ # puts tol.eq?(11234.0, 11280.0) # -> true
76
77
  #
77
78
  # tol = Tolerance(3, :sig_decimals)
78
- # puts tol.eq?(1.234,1.23)
79
+ # puts tol.eq?(1.234,1.23) # -> true
79
80
  #
80
81
  # tol = Tolerance(1, :ulps)
81
- # puts tol.eq?(3.3433, 3.3433.next)
82
- # puts tol.eq?(DecNum('1.1'), DecNum('1.1').next)
82
+ # puts tol.eq?(3.3433, 3.3433.next_plus) # -> true
83
+ # puts tol.eq?(DecNum('1.1'), DecNum('1.1').next_plus) # -> true
83
84
  #
84
85
  # tol = Tolerance(1, :percent)
85
- # puts tol.equal_to?(3.14159, Math::PI)
86
- #
86
+ # puts tol.equal_to?(3.14159, Math::PI) # -> true#
87
87
 
88
- require 'flt'
88
+ require 'flt/num'
89
89
  require 'flt/float'
90
+ require 'flt/bigdecimal'
90
91
 
91
92
  module Flt
92
93
 
@@ -339,7 +340,7 @@ module Flt
339
340
 
340
341
  num_class = xs.first.class
341
342
  context = num_class.context
342
- xs = xs.map{|x| context.Num(x)}
343
+ xs = xs.map{|x| x = context.Num(x); x.zero? ? context.minimum_normal(context.sign(x)) : x}
343
344
  v = cast_value(num_class)
344
345
 
345
346
  # TODO: simplify using context
@@ -1,7 +1,7 @@
1
1
  module Flt
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
- MINOR = 0
4
+ MINOR = 1
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -50,6 +50,13 @@ def exp1(x, c=nil)
50
50
  return +y
51
51
  end
52
52
 
53
+ def return_from_local_context
54
+ DecNum.context do |local_context|
55
+ local_context.precision += 2
56
+ return
57
+ end
58
+ end
59
+
53
60
  class TestBasic < Test::Unit::TestCase
54
61
 
55
62
 
@@ -62,6 +69,8 @@ class TestBasic < Test::Unit::TestCase
62
69
 
63
70
  DecNum.context.precision = 4
64
71
  assert_equal 4, DecNum.context.precision
72
+ return_from_local_context
73
+ assert_equal 4, DecNum.context.precision
65
74
  assert_equal DecNum("0.3333"), DecNum(1)/DecNum(3)
66
75
  DecNum.context.precision = 10
67
76
  assert_equal 10, DecNum.context.precision
@@ -10,6 +10,14 @@ class TestBinArithmetic < Test::Unit::TestCase
10
10
  @test_float_data ||= Array.new(1000){random_num(Float)} + singular_nums(Float) # + special_nums(Float)
11
11
  end
12
12
 
13
+ # TODO: some of these tests may fail under some OSs due to the rounding of subnormal results.
14
+ # On OSX, linux, etc. the rounding of subnormal Float results seems to be as performed by BinNum:
15
+ # as if the exact result is rounded to the number of bits of the subnormal result.
16
+ # But on Windows OSs (even under Cygwin) subnormal results seem to be rounded first to the
17
+ # full Float precision (53 bits).
18
+ # Consider avoid testing with subnormal results, at least on Winddows, or preparing test data,
19
+ # including results, in a reliable machine.
20
+
13
21
  def test_addition
14
22
  float_emulation_context
15
23
  each_pair(@test_float_data) do |x, y|
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__),'helper.rb'))
2
+ require File.dirname(__FILE__) + '/../lib/flt/math'
3
+
4
+ # TODO: Currently tests only with 12 digits of precision; test with more precision.
5
+
6
+ class TestTrig < Test::Unit::TestCase
7
+
8
+
9
+ def setup
10
+ @data12 = {}
11
+ [:sin, :cos, :tan, :asin, :acos, :atan].each do |f|
12
+ @data12[f] = File.read(File.join(File.dirname(__FILE__), "trigtest/#{f}12.txt")).split("\n").map do |line|
13
+ line.split.map{|x| Flt::DecNum(x)}
14
+ end
15
+ end
16
+ end
17
+
18
+ def check(f)
19
+ DecNum.context(:precision=>12) do
20
+ data = @data12[f]
21
+ data.each do |x, result|
22
+ assert_equal result, DecNum::Math.send(f, x), "#{f}(#{x})==#{result}"
23
+ end
24
+ end
25
+ end
26
+
27
+ def check_relaxed(f, ulps=2)
28
+ DecNum.context(:precision=>12) do
29
+ data = @data12[f]
30
+ data.each do |x, result|
31
+ y = DecNum::Math.send(f, x)
32
+ err_ulps = (y-result).abs/result.ulp
33
+ assert err_ulps<=ulps, "#{f}(#{x})==#{result} to within #{ulps} ulps; error: #{err_ulps} ulps (#{y}) [#{DecNum.context.precision}]"
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+ # def test_trig
40
+ # DecNum.context(:precision=>12) do
41
+ # @data12.keys.each do |f|
42
+ # data = @data12[f]
43
+ # data.each do |x, result|
44
+ # assert_equal result, DecNum::Math.send(f, x), "#{f}(#{x})==#{result}"
45
+ # end
46
+ # end
47
+ # end
48
+ # end
49
+
50
+ # separate tests per function
51
+
52
+ def test_sin
53
+ check_relaxed :sin
54
+ end
55
+
56
+ def test_cos
57
+ check_relaxed :cos
58
+ end
59
+
60
+ def test_tan
61
+ check_relaxed :tan
62
+ end
63
+
64
+ def test_asin
65
+ check_relaxed :asin
66
+ end
67
+
68
+ def test_acos
69
+ check_relaxed :acos
70
+ end
71
+
72
+ def test_atan
73
+ check_relaxed :atan
74
+ end
75
+
76
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Goizueta
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-05 00:00:00 +02:00
12
+ date: 2009-11-30 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -87,6 +87,7 @@ files:
87
87
  - test/test_to_int.rb
88
88
  - test/test_to_rf.rb
89
89
  - test/test_tol.rb
90
+ - test/test_trig.rb
90
91
  - test/test_ulp.rb
91
92
  has_rdoc: true
92
93
  homepage: http://flt.rubyforge.org
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
122
  requirements: []
122
123
 
123
124
  rubyforge_project: flt
124
- rubygems_version: 1.3.3
125
+ rubygems_version: 1.3.5
125
126
  signing_key:
126
127
  specification_version: 3
127
128
  summary: Floating Point Numbers
@@ -144,4 +145,5 @@ test_files:
144
145
  - test/test_to_int.rb
145
146
  - test/test_to_rf.rb
146
147
  - test/test_tol.rb
148
+ - test/test_trig.rb
147
149
  - test/test_ulp.rb