flt 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,587 +1,67 @@
1
1
  require 'flt/dec_num'
2
+ require 'flt/bin_num'
3
+ require 'flt/trigonometry'
2
4
 
3
5
  module Flt
4
6
 
5
- # Mathematical functions. The angular units used by these functions can be specified
6
- # with the +angle+ attribute of the context. The accepted values are:
7
- # * :rad for radians
8
- # * :deg for degrees
9
- # * :grad for gradians
7
+ # Base module for Math modules for specific Num classes. Math modules area analogous to
8
+ # ::Math and provide both a means to access math functions (of the current context for a Num class)
9
+ # and, more useful here, a means to access the functions unqualified by including the module in some
10
+ # scope.
11
+ #
12
+ # The math functions provided by Math modules are trigonometric (sin, cos, tan, asin, acos, atan, hypot),
13
+ # exp, log, log2, and log10.
14
+ #
15
+ # Example:
16
+ # DecNum.context(:precision=>5) do
17
+ # puts DecNum::Math.sqrt(2) # => 1.4142
18
+ # end
19
+ # DecNum.context.precision = 10
20
+ # include DecNum::Math
21
+ # puts sqrt(2) # => 1.414213562
22
+ #
10
23
  module MathBase
11
24
 
12
- # Cosine of an angle given in the units specified by DecNum.context.angle.
13
- def cos(x)
14
- cos_base(num_class.Num(x))
25
+ def self.included(base)
26
+ base.extend ClassMethods
15
27
  end
16
28
 
17
- # Sine of an angle given in the units specified by DecNum.context.angle.
18
- def sin(x)
19
- sin_base(num_class.Num(x))
20
- end
21
-
22
- # Tangent of an angle given in the units specified by DecNum.context.angle.
23
- def tan(x)
24
- tan_base(num_class.Num(x))
25
- end
26
-
27
- # Arc-tangent. The result is in the units specified by DecNum.context.angle.
28
- # If the angular units are radians the result is in [-pi/2, pi/2]; it is in [-90,90] in degrees.
29
- def atan(x)
30
- atan_base(num_class.Num(x))
31
- end
32
-
33
- # Arc-tangent with two arguments (principal value of the argument of the complex number x+i*y).
34
- # The result is in the units specified by DecNum.context.angle.
35
- # If the angular units are radians the result is in [-pi, pi]; it is in [-180,180] in degrees.
36
- def atan2(y, x)
37
- atan2_base(num_class.Num(y), num_class.Num(x))
38
- end
39
-
40
- # Arc-sine. The result is in the units specified by DecNum.context.angle.
41
- # If the angular units are radians the result is in [-pi/2, pi/2]; it is in [-90,90] in degrees.
42
- def asin(x)
43
- asin_base(num_class.Num(x))
44
- end
45
-
46
- # Arc-cosine. The result is in the units specified by DecNum.context.angle.
47
- # If the angular units are radians the result is in [-pi/2, pi/2]; it is in [-90,90] in degrees.
48
- def acos(x)
49
- acos_base(num_class.Num(x))
50
- end
51
-
52
- # Length of the hypotenuse of a right-angle triangle (modulus or absolute value of the complex x+i*y).
53
- def hypot(x, y)
54
- hypot_base(num_class.Num(x), num_class.Num(y))
55
- end
56
-
57
- private
58
-
59
- def cos_base(x)
60
- x = x.abs
61
- rev_sign = false
62
- s = nil
63
- num_class.context do |local_context|
64
- local_context.precision += 3 # extra digits for intermediate steps
65
- x,k,pi_2 = reduce_angle2(x,2)
66
- rev_sign = true if k>1
67
- if k % 2 == 0
68
- x = pi_2 - x
29
+ module ClassMethods
30
+ def num_class(cls, &blk)
31
+ define_method(:num_class){cls}
32
+ if blk
33
+ define_method(:context, &blk)
69
34
  else
70
- rev_sign = !rev_sign
71
- end
72
- x = to_rad(x)
73
- i, lasts, fact, num = 1, 0, 1, num_class.Num(x)
74
- s = num
75
- x2 = -x*x
76
- while s != lasts
77
- lasts = s
78
- i += 2
79
- fact *= i * (i-1)
80
- num *= x2
81
- s += num / fact
35
+ define_method(:context){num_class.context}
82
36
  end
37
+ module_function :num_class, :context
83
38
  end
84
- return rev_sign ? (-s) : (+s)
85
- end
86
-
87
- def sin_base(x)
88
- sign = x.sign
89
- s = nil
90
- num_class.context do |local_context|
91
- local_context.precision += 3 # extra digits for intermediate steps
92
- x = x.abs if sign<0
93
- x,k,pi_2 = reduce_angle2(x,2)
94
- sign = -sign if k>1
95
- x = pi_2 - x if k % 2 == 1
96
- x = to_rad(x)
97
- i, lasts, fact, num = 1, 0, 1, num_class.Num(x)
98
- s = num
99
- x2 = -x*x
100
- while s != lasts
101
- lasts = s
102
- i += 2
103
- fact *= i * (i-1)
104
- num *= x2
105
- s += num / fact
106
- end
107
- end
108
- return (+s).copy_sign(sign)
109
- end
110
-
111
- def tan_base(x)
112
- +num_class.context do |local_context|
113
- local_context.precision += 2 # extra digits for intermediate steps
114
- s,c = sin(x), cos(x)
115
- s/c
116
- end
117
- end
118
-
119
- def atan_base(x)
120
- s = nil
121
- conversion = true
122
- extra_prec = num_class.radix==2 ? 4 : 2
123
- num_class.context do |local_context|
124
- local_context.precision += extra_prec
125
- if x == 0
126
- return DecNum.zero
127
- elsif x.abs > 1
128
- if x.infinite?
129
- s = (quarter_cycle).copy_sign(x)
130
- conversion = false
131
- break
132
- else
133
- # c = (quarter_cycle).copy_sign(x)
134
- c = (half*pi).copy_sign(x)
135
- x = 1 / x
39
+ def math_function(*fs)
40
+ fs.each do |f|
41
+ define_method f do |*args|
42
+ context.send f, *args
136
43
  end
137
- end
138
- local_context.precision += extra_prec
139
- x_squared = x ** 2
140
- if x_squared.zero? || x_squared.subnormal?
141
- s = x
142
- s = c - s if c && c!=0
143
- break
144
- end
145
- y = x_squared / (1 + x_squared)
146
- y_over_x = y / x
147
- i = num_class.zero; lasts = 0; s = y_over_x; coeff = 1; num = y_over_x
148
- while s != lasts
149
- lasts = s
150
- i += 2
151
- coeff *= i / (i + 1)
152
- num *= y
153
- s += coeff * num
154
- end
155
- if c && c!= 0
156
- s = c - s
44
+ module_function f
157
45
  end
158
46
  end
159
- return conversion ? rad_to(s) : +s
160
47
  end
161
48
 
162
- def atan2_base(y, x)
163
- abs_y = y.abs
164
- abs_x = x.abs
165
- y_is_real = !x.infinite?
166
-
167
- if x != 0
168
- if y_is_real
169
- a = y!=0 ? atan(y / x) : num_class.zero
170
- a += half_cycle.copy_sign(y) if x < 0
171
- return a
172
- elsif abs_y == abs_x
173
- x = num_class.Num(1).copy_sign(x)
174
- y = num_class.Num(1).copy_sign(y)
175
- return half_cycle * (2 - x) / (4 * y)
176
- end
177
- end
178
-
179
- if y != 0
180
- return atan(num_class.infinity(y.sign))
181
- elsif x < 0
182
- return half_cycle.copy_sign(x)
183
- else
184
- return num_class.zero
185
- end
186
- end
187
-
188
- def asin_base(x)
189
- x = +x
190
- return num_class.context.exception(Num::InvalidOperation, 'asin needs -1 <= x <= 1') if x.abs > 1
191
-
192
- if x == -1
193
- return -quarter_cycle
194
- elsif x == 0
195
- return num_class.zero
196
- elsif x == 1
197
- return quarter_cycle
198
- end
199
-
200
- num_class.context do |local_context|
201
- local_context.precision += 3
202
- x = x/(1-x*x).sqrt
203
- x = atan(x)
204
- end
205
- +x
206
- end
207
-
208
- def acos_base(x)
209
-
210
- return num_class.context.exception(Num::InvalidOperation, 'acos needs -1 <= x <= 2') if x.abs > 1
211
-
212
- if x == -1
213
- return half_cycle
214
- elsif x == 0
215
- return quarter_cycle
216
- elsif x == 1
217
- return num_class.zero
218
- end
219
-
220
- required_precision = num_class.context.precision
221
-
222
- if x < half
223
- num_class.context(:precision=>required_precision+2) do
224
- x = x/(1-x*x).sqrt
225
- x = quarter_cycle - atan(x)
226
- end
227
- else
228
- # valid for x>=0
229
- num_class.context(:precision=>required_precision+3) do
230
-
231
- # x = (1-x*x).sqrt # x*x may require double precision if x*x is near 1
232
- x = (1-BinNum.context(:precision=>required_precision*2){x*x}).sqrt
233
-
234
- x = asin(x)
235
- end
236
- end
237
- +x
238
-
239
- end
240
-
241
- def hypot_base(x, y)
242
- num_class.context do |local_context|
243
- local_context.precision += 3
244
- (x*x + y*y).sqrt
245
- end
246
- end
247
-
248
- def e(digits=nil)
249
- num_class.context do |local_context|
250
- local_context.precision = digits if digits
251
- num_class.Num(1).exp
252
- end
253
- end
254
-
255
- def pi2(decimals=nil)
256
- decimals ||= DecNum.context.precision
257
- num_class.context(:precision=>decimals) do
258
- pi(decimals)*2
259
- end
260
- end
261
-
262
- def invpi(decimals=nil)
263
- decimals ||= DecNum.context.precision
264
- num_class.context(:precision=>decimals) do
265
- num_class.Num(1)/pi(decimals)
266
- end
267
- end
268
-
269
- def inv2pi(decimals=nil)
270
- decimals ||= DecNum.context.precision
271
- num_class.context(:precision=>decimals) do
272
- num_class.Num(1)/pi2(decimals)
273
- end
274
- end
275
-
276
- # class <<self
277
- # private
278
-
279
- def iarccot(x, unity)
280
- xpow = unity / x
281
- n = 1
282
- sign = 1
283
- sum = 0
284
- loop do
285
- term = xpow / n
286
- break if term == 0
287
- sum += sign * (xpow/n)
288
- xpow /= x*x
289
- n += 2
290
- sign = -sign
291
- end
292
- sum
293
- end
294
-
295
- def modtwopi(x)
296
- return +num_class.context(:precision=>num_class.context.precision*3){x.modulo(one_cycle)}
297
- end
298
-
299
- # Reduce angle to [0,2Pi)
300
- def reduce_angle(a)
301
- modtwopi(a)
302
- end
303
-
304
- # Reduce angle to [0,Pi/k0) (result is not rounded to precision)
305
- def reduce_angle2(a,k0=nil) # divisor of pi or nil for pi*2
306
- # we could reduce first to pi*2 to avoid the mod k0 operation
307
- k,r,divisor = DecNum.context do
308
- num_class.context.precision *= 3
309
- m = k0.nil? ? one_cycle : half_cycle/k0
310
- a.divmod(m)+[m]
311
- end
312
- [r, k.modulo(k0*2).to_i, divisor]
313
- end
314
-
315
- def one_cycle
316
- case num_class.context.angle
317
- when :rad
318
- pi2
319
- when :deg
320
- num_class.Num(360)
321
- when :grad
322
- num_class.Num(400)
323
- end
324
- end
325
-
326
- def half_cycle
327
- case num_class.context.angle
328
- when :rad
329
- pi
330
- when :deg
331
- num_class.Num(180)
332
- when :grad
333
- num_class.Num(200)
334
- end
335
- end
336
-
337
- def quarter_cycle
338
- case DecNum.context.angle
339
- when :rad
340
- half*pi
341
- when :deg
342
- num_class.Num(90)
343
- when :grad
344
- num_class.Num(100)
345
- end
346
- end
347
-
348
- def to_rad(x)
349
- case num_class.context.angle
350
- when :rad
351
- +x
352
- else
353
- +num_class.context(:precision=>num_class.context.precision+3){x*pi/half_cycle}
354
- end
355
- end
356
-
357
- def to_deg(x)
358
- case num_class.context.angle
359
- when :deg
360
- +x
361
- else
362
- +num_class.context(:precision=>num_class.context.precision+3){x*num_class.Num(180)/half_cycle}
363
- end
364
- end
365
-
366
- def to_grad(x)
367
- case DecNum.context.angle
368
- when :deg
369
- +x
370
- else
371
- +num_class.context(:precision=>num_class.context.precision+3){x*num_class.Num(200)/half_cycle}
372
- end
373
- end
374
-
375
- def to_angle(angular_units, x)
376
- return +x if angular_units == num_class.context.angle
377
- case angular_units
378
- when :rad
379
- to_rad(x)
380
- when :deg
381
- to_deg(x)
382
- when :grad
383
- to_grad(x)
384
- end
385
- end
386
-
387
- def rad_to(x)
388
- case num_class.context.angle
389
- when :rad
390
- +x
391
- else
392
- +num_class.context(:precision=>num_class.context.precision+3){x*half_cycle/pi}
393
- end
394
- end
395
-
396
- def deg_to(x)
397
- case num_class.context.angle
398
- when :deg
399
- +x
400
- else
401
- +num_class.context(:precision=>num_class.context.precision+3){x*half_cycle/num_class.Num(180)}
402
- end
403
- end
404
-
405
- def grad_to(x)
406
- case num_class.context.angle
407
- when :grad
408
- +x
409
- else
410
- +num_class.context(:precision=>num_class.context.precision+3){x*half_cycle/num_class.Num(200)}
411
- end
412
- end
413
-
414
- def angle_to(x, angular_units)
415
- return +x if angular_units == num_class.context.angle
416
- case angular_units
417
- when :rad
418
- rad_to(x)
419
- when :deg
420
- deg_to(x)
421
- when :grad
422
- grad_to(x)
423
- end
424
- end
425
-
426
- #end
427
-
428
49
  end
429
50
 
51
+ # Math module for DecNum; uses the current DecNum Context. See Flt::MathBase.
52
+ module DecNum::Math
53
+ include MathBase
54
+ num_class DecNum
55
+ math_function *Trigonometry.public_instance_methods
56
+ math_function :exp, :log, :log2, :log10, :sqrt
57
+ end
430
58
 
431
- class DecNum
432
-
433
- module Math
434
-
435
- extend Flt # to access constructor methods DecNum
436
-
437
- include MathBase # make available for instance methods
438
- extend MathBase # make available for class methods
439
-
440
- module Support
441
- #private
442
- def num_class
443
- DecNum
444
- end
445
-
446
- def half
447
- num_class.Num('0.5')
448
- end
449
- end
450
-
451
- include Support
452
- extend Support
453
-
454
- module_function
455
-
456
-
457
- # Pi
458
- @@pi_cache = nil # truncated pi digits as a string
459
- @@pi_cache_digits = 0
460
- PI_MARGIN = 10
461
- def pi(round_digits=nil)
462
-
463
- round_digits ||= DecNum.context.precision
464
- digits = round_digits
465
- if @@pi_cache_digits <= digits # we need at least one more truncated digit
466
- continue = true
467
- while continue
468
- margin = PI_MARGIN # margin to reduce recomputing with more digits to avoid ending in 0 or 5
469
- digits += margin + 1
470
- fudge = 10
471
- unity = 10**(digits+fudge)
472
- v = 4*(4*iarccot(5, unity) - iarccot(239, unity))
473
- v = v.to_s[0,digits]
474
- # if the last digit is 0 or 5 the truncated value may not be good for rounding
475
- loop do
476
- #last_digit = v%10
477
- last_digit = v[-1,1].to_i
478
- continue = (last_digit==5 || last_digit==0)
479
- if continue && margin>0
480
- # if we have margin we back-up one digit
481
- margin -= 1
482
- v = v[0...-1]
483
- else
484
- break
485
- end
486
- end
487
- end
488
- @@pi_cache_digits = digits + margin - PI_MARGIN # @pi_cache.size
489
- @@pi_cache = v # DecNum(+1, v, 1-digits) # cache truncated value
490
- end
491
- # Now we avoid rounding too much because it is slow
492
- l = round_digits + 1
493
- while (l<@@pi_cache_digits) && [0,5].include?(@@pi_cache[l-1,1].to_i)
494
- l += 1
495
- end
496
- v = @@pi_cache[0,l]
497
- num_class.context(:precision=>round_digits){+num_class.Num(+1,v.to_i,1-l)}
498
- end
499
-
500
-
501
- end # DecNum::Math
502
-
503
- class DecNum::Context
504
- include DecNum::Math
505
- public :sin, :cos, :tan, :atan, :asin, :acos, :atan2, :hypot, :pi, :e
506
- end
507
-
508
- def self.pi
509
- self::Math.pi
510
- end
511
-
512
- def self.e
513
- self::Math.e
514
- end
515
-
516
- end # DecNum
517
-
518
- class BinNum
519
-
520
- module Math
521
-
522
- extend Flt # to access constructor methods DecNum
523
-
524
- include MathBase # make available for instance methods
525
- extend MathBase # make available for class methods
526
-
527
- module Support
528
- #private
529
- def num_class
530
- BinNum
531
- end
532
-
533
- def half
534
- num_class.Num('0.5')
535
- end
536
- end
537
-
538
- include Support
539
- extend Support
540
-
541
- module_function
542
-
543
- # Pi
544
- @@pi = nil
545
- @@pi_cache = [num_class.Num(3), 3, 1, 0, 0, 24]
546
- @@pi_digits = 0
547
- def pi(round_digits=nil)
548
- round_digits ||= num_class.context.precision
549
- if @@pi_digits < round_digits
550
- # provisional implementation (very slow)
551
- lasts = 0
552
- t, s, n, na, d, da = @@pi_cache
553
- num_class.context do |local_context|
554
- local_context.precision = round_digits + 6
555
- while s != lasts
556
- lasts = s
557
- n, na = n+na, na+8
558
- d, da = d+da, da+32
559
- t = (t * n) / d
560
- s += t
561
- end
562
- end
563
- @pi_cache = [t, s, n, na, d, da]
564
- @@pi = s
565
- @@pi_digits = round_digits
566
- end
567
- num_class.context(:precision=>round_digits){+@@pi}
568
- end
569
-
570
- end # BinNum::Math
571
-
572
- class BinNum::Context
573
- include BinNum::Math
574
- public :sin, :cos, :tan, :atan, :asin, :acos, :atan2, :hypot, :pi, :e
575
- end
576
-
577
- def self.pi
578
- self::Math.pi
579
- end
580
-
581
- def self.e
582
- self::Math.e
583
- end
584
-
585
- end # BinNum
59
+ # Math module for DecNum; uses the current DecNum Context. See Flt::MathBase.
60
+ module BinNum::Math
61
+ include MathBase
62
+ num_class BinNum
63
+ math_function *Trigonometry.public_instance_methods
64
+ math_function :exp, :log, :log2, :log10, :sqrt
65
+ end
586
66
 
587
- end # Flt
67
+ end # Flt