flt 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,13 @@
1
+ == 1.2.1 2010-06-16
2
+
3
+ * New features
4
+ - Trigonometry for BinNum and reorganization of Math modules.
5
+
6
+ * Bugfixes
7
+ - Context#elimit= didn't work
8
+ - atan could be inaccurate for small arguments
9
+ - asin could be inaccurate for arguments near 1
10
+
1
11
  == 1.2.0 2010-06-15
2
12
 
3
13
  * New Features
data/README.txt CHANGED
@@ -498,6 +498,29 @@ Note also that if we normalize a value we will change it's precision to that of
498
498
  puts x.normalize.number_of_digits # -> 53
499
499
  puts x.normalize.to_s # -> 0.125
500
500
 
501
+ == Mathematical functions
502
+
503
+ There are two mathematical functions modules analogous to Ruby's Math for Float,
504
+ Flt::DecNum::Math and Flt::BinNum::Math.
505
+ Currently they consist of basic trigonometric functions, including hypot, and the
506
+ constants e and pi.
507
+
508
+ Its functions can be accessed in a number of ways:
509
+
510
+ require 'flt/math'
511
+ DecNum.context(:precision=>10) do |context|
512
+ # As module functions:
513
+ puts DecNum::Math.sin(1)*DecNum::Math.pi # -> 2.643559064
514
+ # As functions of a context object:
515
+ puts context.sin(1)*context.pi # -> 2.643559064
516
+ # Through a math block:
517
+ puts DecNum.context.math{sin(1)*pi} # -> 2.643559064
518
+ puts DecNum.math{sin(1)*pi} # -> 2.643559064
519
+ # And can be included to be used ans private instance methods:
520
+ include DecNum::Math
521
+ puts sin(1)*pi # -> 2.643559064
522
+ end
523
+
501
524
  == More Information
502
525
 
503
526
  * Decimal Floating point type: see the base Flt::Num class and the Flt::DecNum class
@@ -507,6 +530,7 @@ Note also that if we normalize a value we will change it's precision to that of
507
530
  * Floating Point Tolerance: see the flt/tolerance.rb[link:files/lib/flt/tolerance_rb.html] file
508
531
  and the Flt::Tolerance class
509
532
  * Constructors: see Flt.DecNum(), Flt.BinNum() and Flt.Tolerance().
533
+ * Mathematical functions: see Flt::MathBase.
510
534
 
511
535
  = DecNum vs BigDecimal
512
536
 
@@ -567,7 +591,7 @@ EXPAND+
567
591
 
568
592
  = Roadmap
569
593
 
570
- * Math (trigonometry) functions for DecNum & BinNum (reorganization of DecNum::Math)
594
+ * Trigonometry optimizations
571
595
  * Complex support.
572
596
  * Implement the missing GDA functions:
573
597
  rotate, shift, trim, and, or, xor, invert,
data/lib/flt/bin_num.rb CHANGED
@@ -205,7 +205,7 @@ class BinNum < Num
205
205
  if special?
206
206
  super
207
207
  else
208
- Math.ldexp(@sign*@coeff, @exp)
208
+ ::Math.ldexp(@sign*@coeff, @exp)
209
209
  end
210
210
  end
211
211
  end
data/lib/flt/math.rb CHANGED
@@ -1,460 +1,587 @@
1
1
  require 'flt/dec_num'
2
2
 
3
3
  module Flt
4
- class DecNum
5
- module Math
6
4
 
7
- extend Flt # to access constructor methods DecNum
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
10
+ module MathBase
8
11
 
9
- module_function
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))
15
+ end
10
16
 
11
- # Trigonometry
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
12
21
 
13
- HALF = DecNum('0.5')
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
14
26
 
15
- # Pi
16
- @@pi_cache = nil # truncated pi digits as a string
17
- @@pi_cache_digits = 0
18
- PI_MARGIN = 10
19
- def pi(round_digits=nil)
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
20
32
 
21
- round_digits ||= DecNum.context.precision
22
- digits = round_digits
23
- if @@pi_cache_digits <= digits # we need at least one more truncated digit
24
- continue = true
25
- while continue
26
- margin = PI_MARGIN # margin to reduce recomputing with more digits to avoid ending in 0 or 5
27
- digits += margin + 1
28
- fudge = 10
29
- unity = 10**(digits+fudge)
30
- v = 4*(4*iarccot(5, unity) - iarccot(239, unity))
31
- v = v.to_s[0,digits]
32
- # if the last digit is 0 or 5 the truncated value may not be good for rounding
33
- loop do
34
- #last_digit = v%10
35
- last_digit = v[-1,1].to_i
36
- continue = (last_digit==5 || last_digit==0)
37
- if continue && margin>0
38
- # if we have margin we back-up one digit
39
- margin -= 1
40
- v = v[0...-1]
41
- else
42
- break
43
- end
44
- end
45
- end
46
- @@pi_cache_digits = digits + margin - PI_MARGIN # @pi_cache.size
47
- @@pi_cache = v # DecNum(+1, v, 1-digits) # cache truncated value
48
- end
49
- # Now we avoid rounding too much because it is slow
50
- l = round_digits + 1
51
- while (l<@@pi_cache_digits) && [0,5].include?(@@pi_cache[l-1,1].to_i)
52
- l += 1
53
- end
54
- v = @@pi_cache[0,l]
55
- DecNum.context(:precision=>round_digits){+DecNum(+1,v.to_i,1-l)}
56
- end
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
57
39
 
58
- def e(decimals=nil)
59
- DecNum.context do |local_context|
60
- local_context.precision = decimals if decimals
61
- DecNum(1).exp
62
- end
63
- end
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
64
45
 
65
- # Cosine of an angle given in the units specified by DecNum.context.angle:
66
- # * :rad for radians
67
- # * :deg for degrees
68
- # * :grad for gradians
69
- def cos(x)
70
- x = x.abs
71
- rev_sign = false
72
- s = nil
73
- DecNum.context do |local_context|
74
- local_context.precision += 3 # extra digits for intermediate steps
75
- x,k,pi_2 = reduce_angle2(x,2)
76
- rev_sign = true if k>1
77
- if k % 2 == 0
78
- x = pi_2 - x
79
- else
80
- rev_sign = !rev_sign
81
- end
82
- x = to_rad(x)
83
- i, lasts, fact, num = 1, 0, 1, DecNum(x)
84
- s = num
85
- x2 = -x*x
86
- while s != lasts
87
- lasts = s
88
- i += 2
89
- fact *= i * (i-1)
90
- num *= x2
91
- s += num / fact
92
- end
93
- end
94
- return rev_sign ? (-s) : (+s)
95
- end
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
96
51
 
97
- # Sine of an angle given in the units specified by DecNum.context.angle:
98
- # * :rad for radians
99
- # * :deg for degrees
100
- # * :grad for gradians
101
- def sin(x)
102
- sign = x.sign
103
- s = nil
104
- DecNum.context do |local_context|
105
- local_context.precision += 3 # extra digits for intermediate steps
106
- x = x.abs if sign<0
107
- x,k,pi_2 = reduce_angle2(x,2)
108
- sign = -sign if k>1
109
- x = pi_2 - x if k % 2 == 1
110
- x = to_rad(x)
111
- i, lasts, fact, num = 1, 0, 1, DecNum(x)
112
- s = num
113
- x2 = -x*x
114
- while s != lasts
115
- lasts = s
116
- i += 2
117
- fact *= i * (i-1)
118
- num *= x2
119
- s += num / fact
120
- end
121
- end
122
- return (+s).copy_sign(sign)
123
- end
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
124
56
 
125
- # Tangent of an angle
126
- def tan(x)
127
- +DecNum.context do |local_context|
128
- local_context.precision += 2 # extra digits for intermediate steps
129
- s,c = sin(x), cos(x)
130
- s/c
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
69
+ 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
131
82
  end
132
83
  end
84
+ return rev_sign ? (-s) : (+s)
85
+ end
133
86
 
134
- # Arc-tangent in units specified by DecNum.context.angle
135
- def atan(x)
136
- s = nil
137
- conversion = true
138
- DecNum.context do |local_context|
139
- local_context.precision += 2
140
- if x == 0
141
- return DecNum.zero
142
- elsif x.abs > 1
143
- if x.infinite?
144
- s = (quarter_cycle).copy_sign(x)
145
- conversion = false
146
- break
147
- else
148
- # c = (quarter_cycle).copy_sign(x)
149
- c = (HALF*pi).copy_sign(x)
150
- x = 1 / x
151
- end
152
- end
153
- local_context.precision += 2
154
- x_squared = x ** 2
155
- y = x_squared / (1 + x_squared)
156
- y_over_x = y / x
157
- i = DecNum.zero; lasts = 0; s = y_over_x; coeff = 1; num = y_over_x
158
- while s != lasts
159
- lasts = s
160
- i += 2
161
- coeff *= i / (i + 1)
162
- num *= y
163
- s += coeff * num
164
- end
165
- if c && c!= 0
166
- s = c - s
167
- end
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
168
106
  end
169
- return conversion ? rad_to(s) : +s
170
107
  end
108
+ return (+s).copy_sign(sign)
109
+ end
171
110
 
172
- def atan2(y, x)
173
- abs_y = y.abs
174
- abs_x = x.abs
175
- y_is_real = !x.infinite?
176
-
177
- if x != 0
178
- if y_is_real
179
- a = y!=0 ? atan(y / x) : DecNum.zero
180
- a += half_cycle.copy_sign(y) if x < 0
181
- return a
182
- elsif abs_y == abs_x
183
- x = DecNum(1).copy_sign(x)
184
- y = DecNum(1).copy_sign(y)
185
- return half_cycle * (2 - x) / (4 * y)
186
- end
187
- end
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
188
118
 
189
- if y != 0
190
- return atan(DecNum.infinity(y.sign))
191
- elsif x < 0
192
- return half_cycle.copy_sign(x)
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
193
132
  else
194
- return DecNum.zero
133
+ # c = (quarter_cycle).copy_sign(x)
134
+ c = (half*pi).copy_sign(x)
135
+ x = 1 / x
195
136
  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
157
+ end
196
158
  end
159
+ return conversion ? rad_to(s) : +s
160
+ end
197
161
 
198
- def asin(x)
199
- x = +x
200
- return DecNum.context.exception(Num::InvalidOperation, 'asin needs -1 <= x <= 1') if x.abs > 1
201
-
202
- if x == -1
203
- return -quarter_cycle
204
- elsif x == 0
205
- return DecNum.zero
206
- elsif x == 1
207
- return quarter_cycle
208
- end
209
-
210
- DecNum.context do |local_context|
211
- local_context.precision += 3
212
- x = x/(1-x*x).sqrt
213
- x = atan(x)
214
- end
215
- +x
216
- end
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
217
178
 
218
- def acos(x)
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
219
187
 
220
- return DecNum.context.exception(Num::InvalidOperation, 'acos needs -1 <= x <= 2') if x.abs > 1
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
221
191
 
222
192
  if x == -1
223
- return half_cycle
193
+ return -quarter_cycle
224
194
  elsif x == 0
225
- return quarter_cycle
195
+ return num_class.zero
226
196
  elsif x == 1
227
- return DecNum.zero
197
+ return quarter_cycle
228
198
  end
229
199
 
230
- if x < HALF
231
- DecNum.context do |local_context|
232
- local_context.precision += 3
233
- x = x/(1-x*x).sqrt
234
- x = quarter_cycle - atan(x)
235
- end
236
- else
237
- # valid for x>=0
238
- DecNum.context do |local_context|
239
- local_context.precision += 3
240
- x = (1-x*x).sqrt
241
- x = asin(x)
242
- end
200
+ num_class.context do |local_context|
201
+ local_context.precision += 3
202
+ x = x/(1-x*x).sqrt
203
+ x = atan(x)
243
204
  end
244
205
  +x
206
+ end
207
+
208
+ def acos_base(x)
245
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
246
218
  end
247
219
 
248
- def hypot(x, y)
249
- DecNum.context do |local_context|
250
- local_context.precision += 3
251
- (x*x + y*y).sqrt
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)
252
226
  end
253
- end
227
+ else
228
+ # valid for x>=0
229
+ num_class.context(:precision=>required_precision+3) do
254
230
 
255
- # TODO: add angular units to context; add support for degrees
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
256
233
 
257
- def pi2(decimals=nil)
258
- decimals ||= DecNum.context.precision
259
- DecNum.context(:precision=>decimals) do
260
- pi(decimals)*2
234
+ x = asin(x)
261
235
  end
262
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
263
254
 
264
- def invpi(decimals=nil)
265
- decimals ||= DecNum.context.precision
266
- DecNum.context(:precision=>decimals) do
267
- DecNum(1)/pi(decimals)
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
268
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)}
269
297
  end
270
298
 
271
- def inv2pi(decimals=nil)
272
- decimals ||= DecNum.context.precision
273
- DecNum.context(:precision=>decimals) do
274
- DecNum(1)/pi2(decimals)
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]
275
311
  end
312
+ [r, k.modulo(k0*2).to_i, divisor]
276
313
  end
277
314
 
278
- # class <<self
279
- # private
280
-
281
- def iarccot(x, unity)
282
- xpow = unity / x
283
- n = 1
284
- sign = 1
285
- sum = 0
286
- loop do
287
- term = xpow / n
288
- break if term == 0
289
- sum += sign * (xpow/n)
290
- xpow /= x*x
291
- n += 2
292
- sign = -sign
293
- end
294
- sum
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)
295
323
  end
324
+ end
296
325
 
297
- def modtwopi(x)
298
- return +DecNum.context(:precision=>DecNum.context.precision*3){x.modulo(one_cycle)}
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)
299
334
  end
335
+ end
300
336
 
301
- # Reduce angle to [0,2Pi)
302
- def reduce_angle(a)
303
- modtwopi(a)
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)
304
345
  end
346
+ end
305
347
 
306
- # Reduce angle to [0,Pi/k0) (result is not rounded to precision)
307
- def reduce_angle2(a,k0=nil) # divisor of pi or nil for pi*2
308
- # we could reduce first to pi*2 to avoid the mod k0 operation
309
- k,r,divisor = DecNum.context do
310
- DecNum.context.precision *= 3
311
- m = k0.nil? ? one_cycle : half_cycle/k0
312
- a.divmod(m)+[m]
313
- end
314
- [r, k.modulo(k0*2).to_i, divisor]
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}
315
354
  end
355
+ end
316
356
 
317
- def one_cycle
318
- case DecNum.context.angle
319
- when :rad
320
- pi2
321
- when :deg
322
- DecNum(360)
323
- when :grad
324
- DecNum(400)
325
- end
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}
326
363
  end
364
+ end
327
365
 
328
- def half_cycle
329
- case DecNum.context.angle
330
- when :rad
331
- pi
332
- when :deg
333
- DecNum(180)
334
- when :grad
335
- DecNum(200)
336
- end
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}
337
372
  end
373
+ end
338
374
 
339
- def quarter_cycle
340
- case DecNum.context.angle
341
- when :rad
342
- HALF*pi
343
- when :deg
344
- DecNum(90)
345
- when :grad
346
- DecNum(100)
347
- end
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)
348
384
  end
385
+ end
349
386
 
350
- def to_rad(x)
351
- case DecNum.context.angle
352
- when :rad
353
- +x
354
- else
355
- +DecNum.context(:precision=>DecNum.context.precision+3){x*pi/half_cycle}
356
- end
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}
357
393
  end
394
+ end
358
395
 
359
- def to_deg(x)
360
- case DecNum.context.angle
361
- when :deg
362
- +x
363
- else
364
- +DecNum.context(:precision=>DecNum.context.precision+3){x*DecNum(180)/half_cycle}
365
- end
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)}
366
402
  end
403
+ end
367
404
 
368
- def to_grad(x)
369
- case DecNum.context.angle
370
- when :deg
371
- +x
372
- else
373
- +DecNum.context(:precision=>DecNum.context.precision+3){x*DecNum(200)/half_cycle}
374
- end
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)}
375
411
  end
412
+ end
376
413
 
377
- def to_angle(angular_units, x)
378
- return +x if angular_units == DecNum.context.angle
379
- case angular_units
380
- when :rad
381
- to_rad(x)
382
- when :deg
383
- to_deg(x)
384
- when :grad
385
- to_grad(x)
386
- end
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)
387
423
  end
424
+ end
388
425
 
389
- def rad_to(x)
390
- case DecNum.context.angle
391
- when :rad
392
- +x
393
- else
394
- +DecNum.context(:precision=>DecNum.context.precision+3){x*half_cycle/pi}
395
- end
426
+ #end
427
+
428
+ end
429
+
430
+
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
396
444
  end
397
445
 
398
- def deg_to(x)
399
- case DecNum.context.angle
400
- when :deg
401
- +x
402
- else
403
- +DecNum.context(:precision=>DecNum.context.precision+3){x*half_cycle/DecNum(180)}
404
- end
446
+ def half
447
+ num_class.Num('0.5')
405
448
  end
449
+ end
406
450
 
407
- def grad_to(x)
408
- case DecNum.context.angle
409
- when :grad
410
- +x
411
- else
412
- +DecNum.context(:precision=>DecNum.context.precision+3){x*half_cycle/DecNum(200)}
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
413
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
414
531
  end
415
532
 
416
- def angle_to(x, angular_units)
417
- return +x if angular_units == DecNum.context.angle
418
- case angular_units
419
- when :rad
420
- rad_to(x)
421
- when :deg
422
- deg_to(x)
423
- when :grad
424
- grad_to(x)
425
- end
533
+ def half
534
+ num_class.Num('0.5')
426
535
  end
536
+ end
427
537
 
428
- #end
429
-
430
- end # Math
431
-
432
- class <<self
433
- private
434
- # declare math functions and inject them into the context class
435
- def math_function(*functions)
436
- functions.each do |f|
437
- # TODO: consider injecting the math methods into the numeric class
438
- # define_method(f) do |*args|
439
- # Math.send(f, self, *args)
440
- # end
441
- Num::ContextBase.send :define_method,f do |*args|
442
- x = Num(args.shift)
443
- Math.send(f, x, *args)
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
444
562
  end
563
+ @pi_cache = [t, s, n, na, d, da]
564
+ @@pi = s
565
+ @@pi_digits = round_digits
445
566
  end
567
+ num_class.context(:precision=>round_digits){+@@pi}
446
568
  end
447
- end
448
569
 
449
- math_function :sin, :cos, :tan, :atan, :asin, :acos, :atan2, :hypot
570
+ end # BinNum::Math
450
571
 
451
- def pi
452
- Math.pi
572
+ class BinNum::Context
573
+ include BinNum::Math
574
+ public :sin, :cos, :tan, :atan, :asin, :acos, :atan2, :hypot, :pi, :e
453
575
  end
454
576
 
455
- def e
456
- Math.e
577
+ def self.pi
578
+ self::Math.pi
457
579
  end
458
580
 
459
- end # DecNum
581
+ def self.e
582
+ self::Math.e
583
+ end
584
+
585
+ end # BinNum
586
+
460
587
  end # Flt
data/lib/flt/num.rb CHANGED
@@ -519,7 +519,7 @@ class Num < Numeric
519
519
  # if e > 0 it is taken as emax and emin=1-emax
520
520
  # if e < 0 it is taken as emin and emax=1-emin
521
521
  def elimit=(e)
522
- @emin, @emax = [elimit, 1-elimit].sort
522
+ @emin, @emax = [e, 1-e].sort
523
523
  end
524
524
 
525
525
  # synonym for precision()
@@ -1261,10 +1261,7 @@ class Num < Numeric
1261
1261
  def nan()
1262
1262
  new [+1, nil, :nan]
1263
1263
  end
1264
- end
1265
-
1266
1264
 
1267
- class <<self
1268
1265
  def int_radix_power(n)
1269
1266
  self.radix**n
1270
1267
  end
@@ -1276,6 +1273,11 @@ class Num < Numeric
1276
1273
  def int_div_radix_power(x,n)
1277
1274
  n < 0 ? (x * self.radix**(-n) ) : (x / self.radix**n)
1278
1275
  end
1276
+
1277
+ def math(*args, &blk)
1278
+ self.context.math(*args, &blk)
1279
+ end
1280
+
1279
1281
  end
1280
1282
 
1281
1283
  # A floating point-number value can be defined by:
@@ -4068,7 +4070,7 @@ class Num < Numeric
4068
4070
  def log2_lb(c)
4069
4071
  raise ArgumentError, "The argument to _log2_lb should be nonnegative." if c <= 0
4070
4072
  str_c = c.to_s(16)
4071
- return LOG2_MULT*4*str_c.length - LOG2_LB_CORRECTION[str_c[0,1].to_i(16)]
4073
+ return LOG2_MULT*4*str_c.length - LOG2_LB_CORRECTION[str_c[0,1].to_i(16)-1]
4072
4074
  end
4073
4075
 
4074
4076
  LOG10_MULT = 100
data/lib/flt/version.rb CHANGED
@@ -2,7 +2,7 @@ module Flt
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
4
  MINOR = 2
5
- TINY = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/test/test_trig.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__),'helper.rb'))
2
2
  require File.dirname(__FILE__) + '/../lib/flt/math'
3
3
 
4
- # TODO: Currently tests only with 12 digits of precision; test with more precision.
5
-
6
4
  class TestTrig < Test::Unit::TestCase
7
5
 
8
6
 
@@ -14,14 +12,16 @@ class TestTrig < Test::Unit::TestCase
14
12
  radix = $2.to_i
15
13
  prec = $3.to_i
16
14
  angle = $4.to_sym
15
+ num_class = Flt::Num[radix]
17
16
  @data[radix] ||= {}
18
17
  @data[radix][angle] ||= {}
19
18
  @data[radix][angle][prec] ||= {}
20
19
  @data[radix][angle][prec][method] = File.read(fn).split("\n").map do |line|
21
- line.split.map{|x| Flt::DecNum(x)}
20
+ line.split.map{|x| num_class.Num(x, :base=>radix)}
22
21
  end
23
22
  end
24
23
  end
24
+ BinNum.context = BinNum::IEEEDoubleContext
25
25
  end
26
26
 
27
27
  def check(f, radix=10, angle=:rad)
@@ -31,7 +31,7 @@ class TestTrig < Test::Unit::TestCase
31
31
  class_num.context.traps[DecNum::DivisionByZero] = false
32
32
  data = @data[radix][angle][prec][f]
33
33
  data.each do |x, result|
34
- assert_equal result, class_num::Math.send(f, x), "#{f}(#{x})==#{result} [#{radix} #{angle} #{prec}]"
34
+ assert_equal result, class_num::Math.send(f, x), "#{f}(#{x})==#{result}\ninput: #{x.to_int_scale.inspect} [#{radix} #{angle} #{prec}]"
35
35
  end
36
36
  end
37
37
  end
@@ -43,14 +43,33 @@ class TestTrig < Test::Unit::TestCase
43
43
  class_num.context(:precision=>prec, :angle=>angle) do
44
44
  class_num.context.traps[DecNum::DivisionByZero] = false
45
45
  data = @data[radix][angle][prec][f]
46
- data.each do |x, result|
47
- y = class_num::Math.send(f, x)
48
- if result.special?
49
- assert_equal result, y, "#{f}(#{x})==#{result} [#{radix} #{angle} #{prec}]"
50
- else
51
- err_ulps = (y-result).abs/result.ulp
52
- assert err_ulps<=ulps, "#{f}(#{x})==#{result} to within #{ulps} ulps; error: #{err_ulps} ulps (#{y}) [#{radix} #{angle} #{prec}]"
46
+ unless data.nil?
47
+ data.each do |x, result|
48
+ y = class_num::Math.send(f, x)
49
+ if result.special?
50
+ assert_equal result, y, "#{f}(#{x})==#{result} [#{radix} #{angle} #{prec}]"
51
+ else
52
+ err_ulps = (y-result).abs/result.ulp
53
+ assert err_ulps<=ulps, "#{f}(#{x})==#{result} to within #{ulps} ulps; error: #{err_ulps} ulps (#{y})\ninput: #{x.to_int_scale.inspect} [#{radix} #{angle} #{prec}]"
54
+ end
53
55
  end
56
+ else
57
+ STDERR.puts "Missing data for radix #{radix.inspect} angle #{angle.inspect} prec #{prec.inspect} #{f.inspect}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def check_bin(f)
64
+ class_num = BinNum
65
+ @data[2][:rad].keys.each do |prec|
66
+ class_num.context do
67
+ #class_num.context.traps[DecNum::DivisionByZero] = false
68
+ data = @data[2][:rad][53][f]
69
+ data.each do |x, result|
70
+ x = class_num.Num(x)
71
+ result = ::Math.send(f, x.to_f)
72
+ assert_equal result, class_num::Math.send(f, x), "#{f}(#{x})==#{result} [bin]"
54
73
  end
55
74
  end
56
75
  end
@@ -104,4 +123,32 @@ class TestTrig < Test::Unit::TestCase
104
123
  check_relaxed :atan, 10, :deg
105
124
  end
106
125
 
126
+ def test_sin_bin
127
+ check_relaxed :sin, 2, :rad
128
+ end
129
+
130
+ def test_cos_bin
131
+ check_relaxed :cos, 2, :rad
132
+ end
133
+
134
+ def test_tan_bin
135
+ check_relaxed :tan, 2, :rad
136
+ end
137
+
138
+ def test_asin_bin
139
+ check_relaxed :asin, 2, :rad
140
+ end
141
+
142
+ def test_acos_bin
143
+ check_relaxed :acos, 2, :rad
144
+ end
145
+
146
+ def test_atan_bin
147
+ check_relaxed :atan, 2, :rad
148
+ end
149
+
150
+ # def test_bin
151
+ # check_bin :sin
152
+ # end
153
+
107
154
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 2
8
- - 0
9
- version: 1.2.0
8
+ - 1
9
+ version: 1.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Javier Goizueta
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-06-15 00:00:00 +02:00
17
+ date: 2010-06-16 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency