flt 1.2.1 → 1.3.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,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