flt 1.0.0 → 1.1.0

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