flt 1.2.0 → 1.2.1

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.
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