flt 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +20 -1
- data/lib/flt.rb +1 -0
- data/lib/flt/bigdecimal.rb +6 -2
- data/lib/flt/bin_num.rb +2 -2
- data/lib/flt/dec_num.rb +3 -2
- data/lib/flt/float.rb +34 -6
- data/lib/flt/math.rb +330 -24
- data/lib/flt/num.rb +65 -15
- data/lib/flt/support.rb +26 -7
- data/lib/flt/tolerance.rb +27 -26
- data/lib/flt/version.rb +1 -1
- data/test/test_basic.rb +9 -0
- data/test/test_bin_arithmetic.rb +8 -0
- data/test/test_trig.rb +76 -0
- metadata +5 -3
data/History.txt
CHANGED
@@ -1,4 +1,23 @@
|
|
1
|
-
== 1.
|
1
|
+
== 1.1.0 2009-11-30
|
2
|
+
|
3
|
+
* New Features:
|
4
|
+
- Access Math functions through context
|
5
|
+
- Enhanced compatibility with Float
|
6
|
+
- Enhanced DecNum trigonometry
|
7
|
+
- Context eval method
|
8
|
+
- New methods integer_part(), fraction_part()
|
9
|
+
|
10
|
+
* Maintenance changes:
|
11
|
+
- Minor internal refactoring.
|
12
|
+
- Avoid some warnings on Ruby 1.9
|
13
|
+
- Better documentation
|
14
|
+
|
15
|
+
* Bugfixes:
|
16
|
+
- local context blocks didn't restore previous context on return or exceptions,
|
17
|
+
- Tolerance fix for near zero numbers
|
18
|
+
- Other internal problems
|
19
|
+
|
20
|
+
== 1.0.0 2009-08-05
|
2
21
|
|
3
22
|
* First release of the new Flt project (derived from the former Ruby-Decimal.)
|
4
23
|
- New gem name: flt
|
data/lib/flt.rb
CHANGED
data/lib/flt/bigdecimal.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# Support classes for homogeneous treatment of BigDecimal and Num values by defining BigDecimal.context
|
2
2
|
|
3
|
-
require 'flt'
|
3
|
+
require 'flt/num'
|
4
|
+
|
4
5
|
require 'bigdecimal'
|
5
6
|
require 'bigdecimal/math'
|
6
|
-
|
7
7
|
require 'singleton'
|
8
8
|
|
9
9
|
# Context class with some of the Flt::Num context functionality, to allow the use of BigDecimal numbers
|
@@ -14,6 +14,10 @@ class Flt::BigDecimalContext
|
|
14
14
|
include Singleton
|
15
15
|
# TODO: Context class with precision, rounding, etc. (no singleton)
|
16
16
|
|
17
|
+
def eval
|
18
|
+
yield self
|
19
|
+
end
|
20
|
+
|
17
21
|
def num_class
|
18
22
|
BigDecimal
|
19
23
|
end
|
data/lib/flt/bin_num.rb
CHANGED
@@ -19,13 +19,13 @@ class BinNum < Num
|
|
19
19
|
# Multiply by an integral power of the base: x*(radix**n) for x,n integer;
|
20
20
|
# returns an integer.
|
21
21
|
def int_mult_radix_power(x,n)
|
22
|
-
|
22
|
+
n < 0 ? (x / (1<<(-n))) : (x * (1<<n))
|
23
23
|
end
|
24
24
|
|
25
25
|
# Divide by an integral power of the base: x/(radix**n) for x,n integer;
|
26
26
|
# returns an integer.
|
27
27
|
def int_div_radix_power(x,n)
|
28
|
-
|
28
|
+
n < 0 ? (x * (1<<(-n))) : (x / (1<<n))
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
data/lib/flt/dec_num.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'flt/num'
|
2
|
+
require 'flt/bigdecimal'
|
2
3
|
|
3
4
|
module Flt
|
4
5
|
|
@@ -21,13 +22,13 @@ class DecNum < Num
|
|
21
22
|
# Multiply by an integral power of the base: x*(radix**n) for x,n integer;
|
22
23
|
# returns an integer.
|
23
24
|
def int_mult_radix_power(x,n)
|
24
|
-
x * (10**n)
|
25
|
+
n < 0 ? (x / (10**(-n))) : (x * (10**n))
|
25
26
|
end
|
26
27
|
|
27
28
|
# Divide by an integral power of the base: x/(radix**n) for x,n integer;
|
28
29
|
# returns an integer.
|
29
30
|
def int_div_radix_power(x,n)
|
30
|
-
x / (10**n)
|
31
|
+
n < 0 ? (x * (10**(-n))) : (x / (10**n))
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
data/lib/flt/float.rb
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
#
|
3
3
|
# The set of constants with Float metadata is also augmented.
|
4
4
|
|
5
|
-
require 'flt'
|
5
|
+
require 'flt/num'
|
6
|
+
require 'singleton'
|
7
|
+
|
6
8
|
|
7
9
|
# Float constants.
|
8
10
|
#
|
@@ -79,8 +81,6 @@ class Float
|
|
79
81
|
|
80
82
|
end
|
81
83
|
|
82
|
-
require 'singleton'
|
83
|
-
|
84
84
|
# Context class with some of the Flt::Num context functionality, to allow the use of Float numbers
|
85
85
|
# similarly to other Num values; this eases the implementation of functions compatible with either
|
86
86
|
# Num or Float values.
|
@@ -425,9 +425,14 @@ class Flt::FloatContext
|
|
425
425
|
|
426
426
|
def math_function(*methods) #:nodoc:
|
427
427
|
methods.each do |method|
|
428
|
-
define_method(method) do |
|
429
|
-
|
428
|
+
define_method(method) do |*args|
|
429
|
+
x = args.shift.to_f
|
430
|
+
Math.send(method, x, *args)
|
430
431
|
end
|
432
|
+
# TODO: consider injecting the math methods into Float
|
433
|
+
# Float.send(:define_method, method) do |*args|
|
434
|
+
# Math.send(method, self, *args)
|
435
|
+
# end
|
431
436
|
end
|
432
437
|
end
|
433
438
|
|
@@ -441,8 +446,31 @@ class Flt::FloatContext
|
|
441
446
|
float_binary_operator :power, :**
|
442
447
|
|
443
448
|
math_function :log, :log10, :exp, :sqrt,
|
444
|
-
:sin, :cos, :tan, :asin, :acos, :atan,
|
449
|
+
:sin, :cos, :tan, :asin, :acos, :atan, :atan2,
|
445
450
|
:sinh, :cosh, :tanh, :asinh, :acosh, :atanh
|
451
|
+
|
452
|
+
def ln(x)
|
453
|
+
log(x)
|
454
|
+
end
|
455
|
+
|
456
|
+
def pi
|
457
|
+
Float::PI
|
458
|
+
end
|
459
|
+
|
460
|
+
def eval
|
461
|
+
yield self
|
462
|
+
end
|
463
|
+
|
464
|
+
def math(*parameters, &blk)
|
465
|
+
if parameters.empty?
|
466
|
+
self.instance_eval &blk
|
467
|
+
else
|
468
|
+
# needs instance_exe (available in Ruby 1.9, ActiveRecord; TODO: include implementation here)
|
469
|
+
self.instance_exec *parameters, &blk
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
|
446
474
|
end
|
447
475
|
|
448
476
|
# Return a (limited) context object for Float.
|
data/lib/flt/math.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'flt/dec_num'
|
2
2
|
|
3
|
+
# TODO:
|
4
|
+
# * convert arguments as Context#_convert does, to accept non DecNum arguments
|
5
|
+
# * Better precision (currently is within about 2 ulps of the correct result)
|
6
|
+
# * Speed optimization
|
7
|
+
|
3
8
|
module Flt
|
4
9
|
class DecNum
|
5
10
|
module Math
|
@@ -8,59 +13,360 @@ module Flt
|
|
8
13
|
|
9
14
|
module_function
|
10
15
|
|
11
|
-
#
|
16
|
+
# Trigonometry
|
17
|
+
|
18
|
+
HALF = DecNum('0.5')
|
12
19
|
|
13
20
|
# Pi
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
$no_cache = false
|
22
|
+
@@pi_cache = nil # truncated pi digits as a string
|
23
|
+
@@pi_cache_digits = 0
|
24
|
+
PI_MARGIN = 10
|
25
|
+
def pi(round_digits=nil)
|
26
|
+
|
27
|
+
round_digits ||= DecNum.context.precision
|
28
|
+
digits = round_digits
|
29
|
+
if @@pi_cache_digits <= digits # we need at least one more truncated digit
|
30
|
+
continue = true
|
31
|
+
while continue
|
32
|
+
margin = PI_MARGIN # margin to reduce recomputing with more digits to avoid ending in 0 or 5
|
33
|
+
digits += margin + 1
|
34
|
+
fudge = 10
|
35
|
+
unity = 10**(digits+fudge)
|
36
|
+
v = 4*(4*iarccot(5, unity) - iarccot(239, unity))
|
37
|
+
v = v.to_s[0,digits]
|
38
|
+
# if the last digit is 0 or 5 the truncated value may not be good for rounding
|
39
|
+
loop do
|
40
|
+
#last_digit = v%10
|
41
|
+
last_digit = v[-1,1].to_i
|
42
|
+
continue = (last_digit==5 || last_digit==0)
|
43
|
+
if continue && margin>0
|
44
|
+
# if we have margin we back-up one digit
|
45
|
+
margin -= 1
|
46
|
+
v = v[0...-1]
|
47
|
+
else
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
@@pi_cache_digits = digits + margin - PI_MARGIN # @pi_cache.size
|
53
|
+
@@pi_cache = v # DecNum(+1, v, 1-digits) # cache truncated value
|
25
54
|
end
|
55
|
+
# Now we avoid rounding too much because it is slow
|
56
|
+
l = round_digits + 1
|
57
|
+
while (l<@@pi_cache_digits) && [0,5].include?(@@pi_cache[l-1,1].to_i)
|
58
|
+
l += 1
|
59
|
+
end
|
60
|
+
v = @@pi_cache[0,l]
|
61
|
+
DecNum.context(:precision=>round_digits){+DecNum(+1,v.to_i,1-l)}
|
62
|
+
end
|
63
|
+
|
64
|
+
def e(decimals=nil)
|
65
|
+
DecNum.context do |local_context|
|
66
|
+
local_context.precision = decimals if decimals
|
67
|
+
DecNum(1).exp
|
26
68
|
end
|
27
|
-
return +s
|
28
69
|
end
|
29
70
|
|
30
71
|
# Cosine of angle in radians
|
31
72
|
def cos(x)
|
32
|
-
|
73
|
+
x = x.abs
|
74
|
+
rev_sign = false
|
75
|
+
s = nil
|
33
76
|
DecNum.context do |local_context|
|
34
|
-
local_context.precision +=
|
77
|
+
local_context.precision += 3 # extra digits for intermediate steps
|
78
|
+
x,k,pi_2 = reduce_angle2(x,2)
|
79
|
+
rev_sign = true if k>1
|
80
|
+
if k % 2 == 0
|
81
|
+
x = pi_2 - x
|
82
|
+
else
|
83
|
+
rev_sign = !rev_sign
|
84
|
+
end
|
85
|
+
i, lasts, fact, num = 1, 0, 1, DecNum(x)
|
86
|
+
s = num
|
87
|
+
x2 = -x*x
|
35
88
|
while s != lasts
|
36
89
|
lasts = s
|
37
90
|
i += 2
|
38
91
|
fact *= i * (i-1)
|
39
|
-
num *=
|
40
|
-
|
41
|
-
s += num / fact * sign
|
92
|
+
num *= x2
|
93
|
+
s += num / fact
|
42
94
|
end
|
43
95
|
end
|
44
|
-
return +s
|
96
|
+
return rev_sign ? (-s) : (+s)
|
45
97
|
end
|
46
98
|
|
47
99
|
# Sine of angle in radians
|
48
100
|
def sin(x)
|
49
|
-
|
101
|
+
sign = x.sign
|
102
|
+
s = nil
|
50
103
|
DecNum.context do |local_context|
|
51
|
-
local_context.precision +=
|
104
|
+
local_context.precision += 3 # extra digits for intermediate steps
|
105
|
+
x = x.abs if sign<0
|
106
|
+
x,k,pi_2 = reduce_angle2(x,2)
|
107
|
+
sign = -sign if k>1
|
108
|
+
x = pi_2 - x if k % 2 == 1
|
109
|
+
x = +x
|
110
|
+
i, lasts, fact, num = 1, 0, 1, DecNum(x)
|
111
|
+
s = num
|
112
|
+
x2 = -x*x
|
52
113
|
while s != lasts
|
53
114
|
lasts = s
|
54
115
|
i += 2
|
55
116
|
fact *= i * (i-1)
|
56
|
-
num *=
|
57
|
-
|
58
|
-
|
117
|
+
num *= x2
|
118
|
+
s += num / fact
|
119
|
+
end
|
120
|
+
end
|
121
|
+
return (+s).copy_sign(sign)
|
122
|
+
end
|
123
|
+
|
124
|
+
def tan(x)
|
125
|
+
+DecNum.context do |local_context|
|
126
|
+
local_context.precision += 2 # extra digits for intermediate steps
|
127
|
+
s,c = sin(x), cos(x)
|
128
|
+
s/c
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Arc-tangent.
|
133
|
+
def atan(x)
|
134
|
+
s = nil
|
135
|
+
DecNum.context do |local_context|
|
136
|
+
local_context.precision += 2
|
137
|
+
if x == 0
|
138
|
+
return DecNum.zero
|
139
|
+
elsif x.abs > 1
|
140
|
+
if x.infinite?
|
141
|
+
s = (pi*HALF).copy_sign(x)
|
142
|
+
break
|
143
|
+
else
|
144
|
+
c = (pi*HALF).copy_sign(x)
|
145
|
+
x = 1 / x
|
146
|
+
end
|
147
|
+
end
|
148
|
+
local_context.precision += 2
|
149
|
+
x_squared = x ** 2
|
150
|
+
y = x_squared / (1 + x_squared)
|
151
|
+
y_over_x = y / x
|
152
|
+
i = DecNum.zero; lasts = 0; s = y_over_x; coeff = 1; num = y_over_x
|
153
|
+
while s != lasts
|
154
|
+
lasts = s
|
155
|
+
i += 2
|
156
|
+
coeff *= i / (i + 1)
|
157
|
+
num *= y
|
158
|
+
s += coeff * num
|
159
|
+
end
|
160
|
+
if c && c!= 0
|
161
|
+
s = c - s
|
59
162
|
end
|
60
163
|
end
|
61
164
|
return +s
|
62
165
|
end
|
63
166
|
|
167
|
+
def atan2(y, x)
|
168
|
+
abs_y = y.abs
|
169
|
+
abs_x = x.abs
|
170
|
+
y_is_real = !x.infinite?
|
171
|
+
|
172
|
+
if x != 0
|
173
|
+
if y_is_real
|
174
|
+
a = y!=0 ? atan(y / x) : DecNum.zero
|
175
|
+
a += pi.copy_sign(y) if x < 0
|
176
|
+
return a
|
177
|
+
elsif abs_y == abs_x
|
178
|
+
x = DecNum(1).copy_sign(x)
|
179
|
+
y = DecNum(1).copy_sign(y)
|
180
|
+
return pi * (2 - x) / (4 * y)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
if y != 0
|
185
|
+
return atan(DecNum.infinity(y.sign))
|
186
|
+
elsif x < 0
|
187
|
+
return pi.copy_sign(x)
|
188
|
+
else
|
189
|
+
return DecNum.zero
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def asin(x)
|
194
|
+
x = +x
|
195
|
+
return DecNum.context.exception(Num::InvalidOperation, 'asin needs -1 <= x <= 1') if x.abs > 1
|
196
|
+
|
197
|
+
if x == -1
|
198
|
+
return -pi*HALF
|
199
|
+
elsif x == 0
|
200
|
+
return DecNum.zero
|
201
|
+
elsif x == 1
|
202
|
+
return pi*HALF
|
203
|
+
end
|
204
|
+
|
205
|
+
DecNum.context do |local_context|
|
206
|
+
local_context.precision += 3
|
207
|
+
x = x/(1-x*x).sqrt
|
208
|
+
x = atan(x)
|
209
|
+
end
|
210
|
+
+x
|
211
|
+
end
|
212
|
+
|
213
|
+
def acos(x)
|
214
|
+
|
215
|
+
# We can compute acos(x) = pi/2 - asin(x)
|
216
|
+
# but we must take care with x near 1, where that formula would cause loss of precision
|
217
|
+
|
218
|
+
return DecNum.context.exception(Num::InvalidOperation, 'acos needs -1 <= x <= 2') if x.abs > 1
|
219
|
+
|
220
|
+
if x == -1
|
221
|
+
return pi
|
222
|
+
elsif x == 0
|
223
|
+
return +DecNum.context(:precision=>DecNum.context.precision+3){pi*HALF}
|
224
|
+
elsif x == 1
|
225
|
+
return DecNum.zero
|
226
|
+
end
|
227
|
+
|
228
|
+
# some identities:
|
229
|
+
# acos(x) = pi/2 - asin(x) # (but this losses accuracy near x=+1)
|
230
|
+
# acos(x) = pi/2 - atan(x/(1-x*x).sqrt) # this too
|
231
|
+
# acos(x) = asin((1-x*x).sqrt) for x>=0; for x<=0 acos(x) = pi/2 - asin((1-x*x).sqrt)
|
232
|
+
|
233
|
+
if x < HALF
|
234
|
+
DecNum.context do |local_context|
|
235
|
+
local_context.precision += 3
|
236
|
+
x = x/(1-x*x).sqrt
|
237
|
+
x = pi*HALF - atan(x)
|
238
|
+
end
|
239
|
+
else
|
240
|
+
# valid for x>=0
|
241
|
+
DecNum.context do |local_context|
|
242
|
+
local_context.precision += 3
|
243
|
+
x = (1-x*x).sqrt
|
244
|
+
x = asin(x)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
+x
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
# TODO: degrees mode or radians/degrees conversion
|
252
|
+
|
253
|
+
def pi2(decimals=nil)
|
254
|
+
decimals ||= DecNum.context.precision
|
255
|
+
DecNum.context(:precision=>decimals) do
|
256
|
+
pi(decimals)*2
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def invpi(decimals=nil)
|
261
|
+
decimals ||= DecNum.context.precision
|
262
|
+
DecNum.context(:precision=>decimals) do
|
263
|
+
DecNum(1)/pi(decimals)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def inv2pi(decimals=nil)
|
268
|
+
decimals ||= DecNum.context.precision
|
269
|
+
DecNum.context(:precision=>decimals) do
|
270
|
+
DecNum(1)/pi2(decimals)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# class <<self
|
275
|
+
# private
|
276
|
+
|
277
|
+
def iarccot(x, unity)
|
278
|
+
xpow = unity / x
|
279
|
+
n = 1
|
280
|
+
sign = 1
|
281
|
+
sum = 0
|
282
|
+
loop do
|
283
|
+
term = xpow / n
|
284
|
+
break if term == 0
|
285
|
+
sum += sign * (xpow/n)
|
286
|
+
xpow /= x*x
|
287
|
+
n += 2
|
288
|
+
sign = -sign
|
289
|
+
end
|
290
|
+
sum
|
291
|
+
end
|
292
|
+
|
293
|
+
def modtwopi(x)
|
294
|
+
return +DecNum.context(:precision=>DecNum.context.precision*3){x.modulo(pi2)}
|
295
|
+
# This seems to be slower and less accurate:
|
296
|
+
# prec = DecNum.context.precision
|
297
|
+
# pi_2 = pi2(prec*2)
|
298
|
+
# return x if x < pi_2
|
299
|
+
# ex = x.fractional_exponent
|
300
|
+
# DecNum.context do |local_context|
|
301
|
+
# # x.modulo(pi_2)
|
302
|
+
# local_context.precision *= 2
|
303
|
+
# if ex > prec
|
304
|
+
# # consider exponent separately
|
305
|
+
# fd = nil
|
306
|
+
# excess = ex - prec
|
307
|
+
# x = x.scaleb(prec-ex)
|
308
|
+
# # now obtain 2*prec digits from inv2pi after the initial excess digits
|
309
|
+
# digits = nil
|
310
|
+
# inv_2pi = inv2pi(local_context.precision+excess)
|
311
|
+
# DecNum.context do |extended_context|
|
312
|
+
# extended_context.precision += excess
|
313
|
+
# digits = (inv2pi.scaleb(excess)).fraction_part
|
314
|
+
# end
|
315
|
+
# x *= digits*pi_2
|
316
|
+
# end
|
317
|
+
# # compute the fractional part of the division by 2pi
|
318
|
+
# inv_2pi ||= inv2pi
|
319
|
+
# x = pi_2*((x*inv2pi).fraction_part)
|
320
|
+
# end
|
321
|
+
# +x
|
322
|
+
end
|
323
|
+
|
324
|
+
# Reduce angle to [0,2Pi)
|
325
|
+
def reduce_angle(a)
|
326
|
+
modtwopi(a)
|
327
|
+
end
|
328
|
+
|
329
|
+
# Reduce angle to [0,Pi/k0) (result is not rounded to precision)
|
330
|
+
def reduce_angle2(a,k0=nil) # divisor of pi or nil for pi*2
|
331
|
+
# we could reduce first to pi*2 to avoid the mod k0 operation
|
332
|
+
k,r,divisor = DecNum.context do
|
333
|
+
DecNum.context.precision *= 3
|
334
|
+
m = k0.nil? ? pi2 : pi/k0
|
335
|
+
a.divmod(m)+[m]
|
336
|
+
end
|
337
|
+
[r, k.modulo(k0*2).to_i, divisor]
|
338
|
+
end
|
339
|
+
|
340
|
+
#end
|
341
|
+
|
64
342
|
end # Math
|
343
|
+
|
344
|
+
class <<self
|
345
|
+
private
|
346
|
+
# declare math functions and inject them into the context class
|
347
|
+
def math_function(*functions)
|
348
|
+
functions.each do |f|
|
349
|
+
# TODO: consider injecting the math methods into the numeric class
|
350
|
+
# define_method(f) do |*args|
|
351
|
+
# Math.send(f, self, *args)
|
352
|
+
# end
|
353
|
+
Num::ContextBase.send :define_method,f do |*args|
|
354
|
+
x = Num(args.shift)
|
355
|
+
Math.send(f, x, *args)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
math_function :sin, :cos, :tan, :atan, :asin, :acos
|
362
|
+
|
363
|
+
def pi
|
364
|
+
Math.pi
|
365
|
+
end
|
366
|
+
|
367
|
+
def e
|
368
|
+
Math.e
|
369
|
+
end
|
370
|
+
|
65
371
|
end # DecNum
|
66
372
|
end # Flt
|
data/lib/flt/num.rb
CHANGED
@@ -413,6 +413,38 @@ class Num < Numeric
|
|
413
413
|
|
414
414
|
end
|
415
415
|
|
416
|
+
# Evaluate a block under a context (set up the context as a local context)
|
417
|
+
#
|
418
|
+
# When we have a context object we can use this instead of using the context method of the
|
419
|
+
# numeric class, e.g.:
|
420
|
+
# DecNum.context(context) { ... }
|
421
|
+
# This saves verbosity, specially when numeric class is not fixed, in which case
|
422
|
+
# we would have to write:
|
423
|
+
# context.num_class.context(context) { ... }
|
424
|
+
# With this method, we simply write:
|
425
|
+
# context.eval { ... }
|
426
|
+
def eval(&blk)
|
427
|
+
# TODO: consider other names for this method; use ? apply ? local ? with ?
|
428
|
+
num_class.context(self, &blk)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Evalute a block under a context (set up the context as a local context) and inject the
|
432
|
+
# context methods (math and otherwise) into the block scope.
|
433
|
+
#
|
434
|
+
# This allows the use of regular algebraic notations for math functions,
|
435
|
+
# e.g. exp(x) instead of x.exp
|
436
|
+
def math(*parameters, &blk)
|
437
|
+
# TODO: consider renaming this to eval
|
438
|
+
num_class.context(self) do
|
439
|
+
if parameters.empty?
|
440
|
+
num_class.context.instance_eval &blk
|
441
|
+
else
|
442
|
+
# needs instance_exe (available in Ruby 1.9, ActiveRecord; TODO: include implementation here)
|
443
|
+
num_class.context.instance_exec *parameters, &blk
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
416
448
|
# This gives access to the numeric class (Flt::Num-derived) this context is for.
|
417
449
|
def num_class
|
418
450
|
@num_class
|
@@ -1172,18 +1204,21 @@ class Num < Numeric
|
|
1172
1204
|
# options to apply to the local scope.
|
1173
1205
|
# Changes done to the current context are reversed when the scope is exited.
|
1174
1206
|
def self.local_context(*args)
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1207
|
+
begin
|
1208
|
+
keep = self.context # use this so _context is initialized if necessary
|
1209
|
+
self.context = define_context(*args) # this dups the assigned context
|
1210
|
+
result = yield _context
|
1211
|
+
ensure
|
1212
|
+
# TODO: consider the convenience of copying the flags from DecNum.context to keep
|
1213
|
+
# This way a local context does not affect the settings of the previous context,
|
1214
|
+
# but flags are transferred.
|
1215
|
+
# (this could be done always or be controlled by some option)
|
1216
|
+
# keep.flags = DecNum.context.flags
|
1217
|
+
# Another alternative to consider: logically or the flags:
|
1218
|
+
# keep.flags ||= DecNum.context.flags # (this requires implementing || in Flags)
|
1219
|
+
self._context = keep
|
1220
|
+
result
|
1221
|
+
end
|
1187
1222
|
end
|
1188
1223
|
|
1189
1224
|
class <<self
|
@@ -1232,11 +1267,11 @@ class Num < Numeric
|
|
1232
1267
|
end
|
1233
1268
|
|
1234
1269
|
def int_mult_radix_power(x,n)
|
1235
|
-
x * self.radix**n
|
1270
|
+
n < 0 ? (x / self.radix**(-n)) : (x * self.radix**n)
|
1236
1271
|
end
|
1237
1272
|
|
1238
1273
|
def int_div_radix_power(x,n)
|
1239
|
-
x / self.radix**n
|
1274
|
+
n < 0 ? (x * self.radix**(-n) ) : (x / self.radix**n)
|
1240
1275
|
end
|
1241
1276
|
end
|
1242
1277
|
|
@@ -2482,6 +2517,21 @@ class Num < Numeric
|
|
2482
2517
|
@exp
|
2483
2518
|
end
|
2484
2519
|
|
2520
|
+
# Integer part (as a Num)
|
2521
|
+
def integer_part
|
2522
|
+
ans = _check_nans
|
2523
|
+
return ans if ans
|
2524
|
+
return_as_num = {:places=>0}
|
2525
|
+
self.sign < 0 ? self.ceil(return_as_num) : self.floor(return_as_num)
|
2526
|
+
end
|
2527
|
+
|
2528
|
+
# Fraction part (as a Num)
|
2529
|
+
def fraction_part
|
2530
|
+
ans = _check_nans
|
2531
|
+
return ans if ans
|
2532
|
+
self - self.integer_part
|
2533
|
+
end
|
2534
|
+
|
2485
2535
|
# Return the value of the number as an signed integer and a scale.
|
2486
2536
|
def to_int_scale
|
2487
2537
|
if special?
|
@@ -3680,7 +3730,7 @@ class Num < Numeric
|
|
3680
3730
|
|
3681
3731
|
# Parse numeric text literals (internal use)
|
3682
3732
|
def _parser(txt)
|
3683
|
-
md = /^\s*([-+])?(?:(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:
|
3733
|
+
md = /^\s*([-+])?(?:(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:E([-+]?\d+))?|Inf(?:inity)?|(s)?NaN(\d*))\s*$/i.match(txt)
|
3684
3734
|
if md
|
3685
3735
|
OpenStruct.new :sign=>md[1], :int=>md[2], :frac=>md[3], :onlyfrac=>md[4], :exp=>md[5],
|
3686
3736
|
:signal=>md[6], :diag=>md[7]
|
data/lib/flt/support.rb
CHANGED
@@ -483,9 +483,9 @@ module Flt
|
|
483
483
|
float = true
|
484
484
|
context = BinNum::FloatContext
|
485
485
|
end
|
486
|
-
x = context.num_class.context(context) do |
|
487
|
-
|
488
|
-
Num.convert_exact(Num[eb].Num(sign, f, e),
|
486
|
+
x = context.num_class.context(context) do |loca_context|
|
487
|
+
local_context.rounding = round_mode
|
488
|
+
Num.convert_exact(Num[eb].Num(sign, f, e), local_context.num_class, local_context)
|
489
489
|
end
|
490
490
|
if float
|
491
491
|
x = x.to_f
|
@@ -593,7 +593,7 @@ module Flt
|
|
593
593
|
min_exp = (e*Math.log(Float::RADIX)/Math.log(base)).ceil
|
594
594
|
@float_min_max_exp_values[k] = min_max = [min_exp, max_exp]
|
595
595
|
end
|
596
|
-
min_max.map{|
|
596
|
+
min_max.map{|exp| exp - 1} # adjust
|
597
597
|
end
|
598
598
|
end
|
599
599
|
|
@@ -1330,6 +1330,25 @@ module Flt
|
|
1330
1330
|
end # Support
|
1331
1331
|
|
1332
1332
|
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1333
|
+
end # Flt
|
1334
|
+
|
1335
|
+
# The math method of context needs instance_exec to pass parameters to the blocks evaluated
|
1336
|
+
# with a modified self (pointing to a context object.) intance_exec is available in Ruby 1.9.1 and
|
1337
|
+
# is also defined by ActiveRecord. Here we use Mauricio Fernández implementation if it is not
|
1338
|
+
# available.
|
1339
|
+
class Object
|
1340
|
+
unless defined? instance_exec
|
1341
|
+
module InstanceExecHelper; end
|
1342
|
+
include InstanceExecHelper
|
1343
|
+
def instance_exec(*args, &block) # !> method redefined; discarding old instance_exec
|
1344
|
+
mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
|
1345
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
1346
|
+
begin
|
1347
|
+
ret = send(mname, *args)
|
1348
|
+
ensure
|
1349
|
+
InstanceExecHelper.module_eval{ undef_method(mname) } rescue nil
|
1350
|
+
end
|
1351
|
+
ret
|
1352
|
+
end
|
1353
|
+
end
|
1354
|
+
end
|
data/lib/flt/tolerance.rb
CHANGED
@@ -3,17 +3,18 @@
|
|
3
3
|
# Tolerance can be used to allow for a tolerance in floating-point comparisons.
|
4
4
|
#
|
5
5
|
# A Tolerance can be defined independently of the type (floating-point numeric class)
|
6
|
-
# it will be used with; The actual tolerance value will be compute for a particular reference value
|
6
|
+
# it will be used with; The actual tolerance value will be compute for a particular reference value, and
|
7
|
+
# for some kinds of tolerance (e.g. epsilon) a value is not available without a reference:
|
7
8
|
#
|
8
9
|
# tol = Tolerance(3, :decimals)
|
9
|
-
# puts tol.value(DecNum('10.0')).inspect
|
10
|
-
# puts tol.value(10.0).inspect
|
11
|
-
# puts tol.value.inspect
|
10
|
+
# puts tol.value(DecNum('10.0')).inspect # -> DecNum('0.0005')
|
11
|
+
# puts tol.value(10.0).inspect # -> 0.0005
|
12
|
+
# puts tol.value.inspect # -> Rational(1, 2000)
|
12
13
|
#
|
13
14
|
# tol = Tolerance(:epsilon)
|
14
|
-
# puts tol.value(DecNum('10.0')).inspect
|
15
|
-
# puts tol.value(10.0).inspect
|
16
|
-
# puts tol.value.inspect
|
15
|
+
# puts tol.value(DecNum('10.0')).inspect # -> DecNum('1.00E-26')
|
16
|
+
# puts tol.value(10.0).inspect # -> 2.22044604925031e-15
|
17
|
+
# puts tol.value.inspect # -> nil
|
17
18
|
#
|
18
19
|
# Tolerances can be:
|
19
20
|
# * Absolute: the tolerance value is a fixed value independent of the values to be compared.
|
@@ -57,36 +58,36 @@
|
|
57
58
|
# Examples:
|
58
59
|
#
|
59
60
|
# tol = Tolerance(100, :absolute)
|
60
|
-
# puts tol.value(1.0)
|
61
|
-
# puts tol.value(1.5)
|
62
|
-
# puts tol.value(1.0E10)
|
63
|
-
# puts tol.eq?(11234.0, 11280.0)
|
61
|
+
# puts tol.value(1.0) # -> 100.0
|
62
|
+
# puts tol.value(1.5) # -> 100.0
|
63
|
+
# puts tol.value(1.0E10) # -> 100.0
|
64
|
+
# puts tol.eq?(11234.0, 11280.0) # -> true
|
64
65
|
#
|
65
66
|
# tol = Tolerance(100, :relative)
|
66
|
-
# puts tol.value(1.0)
|
67
|
-
# puts tol.value(1.5)
|
68
|
-
# puts tol.value(1.0E10)
|
69
|
-
# puts tol.eq?(11234.0, 11280.0)
|
67
|
+
# puts tol.value(1.0) # -> 100.0
|
68
|
+
# puts tol.value(1.5) # -> 150.0
|
69
|
+
# puts tol.value(1.0E10) # -> 1000000000000.0
|
70
|
+
# puts tol.eq?(11234.0, 11280.0) # -> true
|
70
71
|
#
|
71
72
|
# tol = Tolerance(100, :floating)
|
72
|
-
# puts tol.value(1.0)
|
73
|
-
# puts tol.value(1.5)
|
74
|
-
# puts tol.value(1.0E10)
|
75
|
-
# puts tol.eq?(11234.0, 11280.0)
|
73
|
+
# puts tol.value(1.0) # -> 100.0
|
74
|
+
# puts tol.value(1.5) # -> 200.0
|
75
|
+
# puts tol.value(1.0E10) # -> 1717986918400.0
|
76
|
+
# puts tol.eq?(11234.0, 11280.0) # -> true
|
76
77
|
#
|
77
78
|
# tol = Tolerance(3, :sig_decimals)
|
78
|
-
# puts tol.eq?(1.234,1.23)
|
79
|
+
# puts tol.eq?(1.234,1.23) # -> true
|
79
80
|
#
|
80
81
|
# tol = Tolerance(1, :ulps)
|
81
|
-
# puts tol.eq?(3.3433, 3.3433.
|
82
|
-
# puts tol.eq?(DecNum('1.1'), DecNum('1.1').
|
82
|
+
# puts tol.eq?(3.3433, 3.3433.next_plus) # -> true
|
83
|
+
# puts tol.eq?(DecNum('1.1'), DecNum('1.1').next_plus) # -> true
|
83
84
|
#
|
84
85
|
# tol = Tolerance(1, :percent)
|
85
|
-
# puts tol.equal_to?(3.14159, Math::PI)
|
86
|
-
#
|
86
|
+
# puts tol.equal_to?(3.14159, Math::PI) # -> true#
|
87
87
|
|
88
|
-
require 'flt'
|
88
|
+
require 'flt/num'
|
89
89
|
require 'flt/float'
|
90
|
+
require 'flt/bigdecimal'
|
90
91
|
|
91
92
|
module Flt
|
92
93
|
|
@@ -339,7 +340,7 @@ module Flt
|
|
339
340
|
|
340
341
|
num_class = xs.first.class
|
341
342
|
context = num_class.context
|
342
|
-
xs = xs.map{|x| context.Num(x)}
|
343
|
+
xs = xs.map{|x| x = context.Num(x); x.zero? ? context.minimum_normal(context.sign(x)) : x}
|
343
344
|
v = cast_value(num_class)
|
344
345
|
|
345
346
|
# TODO: simplify using context
|
data/lib/flt/version.rb
CHANGED
data/test/test_basic.rb
CHANGED
@@ -50,6 +50,13 @@ def exp1(x, c=nil)
|
|
50
50
|
return +y
|
51
51
|
end
|
52
52
|
|
53
|
+
def return_from_local_context
|
54
|
+
DecNum.context do |local_context|
|
55
|
+
local_context.precision += 2
|
56
|
+
return
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
53
60
|
class TestBasic < Test::Unit::TestCase
|
54
61
|
|
55
62
|
|
@@ -62,6 +69,8 @@ class TestBasic < Test::Unit::TestCase
|
|
62
69
|
|
63
70
|
DecNum.context.precision = 4
|
64
71
|
assert_equal 4, DecNum.context.precision
|
72
|
+
return_from_local_context
|
73
|
+
assert_equal 4, DecNum.context.precision
|
65
74
|
assert_equal DecNum("0.3333"), DecNum(1)/DecNum(3)
|
66
75
|
DecNum.context.precision = 10
|
67
76
|
assert_equal 10, DecNum.context.precision
|
data/test/test_bin_arithmetic.rb
CHANGED
@@ -10,6 +10,14 @@ class TestBinArithmetic < Test::Unit::TestCase
|
|
10
10
|
@test_float_data ||= Array.new(1000){random_num(Float)} + singular_nums(Float) # + special_nums(Float)
|
11
11
|
end
|
12
12
|
|
13
|
+
# TODO: some of these tests may fail under some OSs due to the rounding of subnormal results.
|
14
|
+
# On OSX, linux, etc. the rounding of subnormal Float results seems to be as performed by BinNum:
|
15
|
+
# as if the exact result is rounded to the number of bits of the subnormal result.
|
16
|
+
# But on Windows OSs (even under Cygwin) subnormal results seem to be rounded first to the
|
17
|
+
# full Float precision (53 bits).
|
18
|
+
# Consider avoid testing with subnormal results, at least on Winddows, or preparing test data,
|
19
|
+
# including results, in a reliable machine.
|
20
|
+
|
13
21
|
def test_addition
|
14
22
|
float_emulation_context
|
15
23
|
each_pair(@test_float_data) do |x, y|
|
data/test/test_trig.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'helper.rb'))
|
2
|
+
require File.dirname(__FILE__) + '/../lib/flt/math'
|
3
|
+
|
4
|
+
# TODO: Currently tests only with 12 digits of precision; test with more precision.
|
5
|
+
|
6
|
+
class TestTrig < Test::Unit::TestCase
|
7
|
+
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@data12 = {}
|
11
|
+
[:sin, :cos, :tan, :asin, :acos, :atan].each do |f|
|
12
|
+
@data12[f] = File.read(File.join(File.dirname(__FILE__), "trigtest/#{f}12.txt")).split("\n").map do |line|
|
13
|
+
line.split.map{|x| Flt::DecNum(x)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def check(f)
|
19
|
+
DecNum.context(:precision=>12) do
|
20
|
+
data = @data12[f]
|
21
|
+
data.each do |x, result|
|
22
|
+
assert_equal result, DecNum::Math.send(f, x), "#{f}(#{x})==#{result}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_relaxed(f, ulps=2)
|
28
|
+
DecNum.context(:precision=>12) do
|
29
|
+
data = @data12[f]
|
30
|
+
data.each do |x, result|
|
31
|
+
y = DecNum::Math.send(f, x)
|
32
|
+
err_ulps = (y-result).abs/result.ulp
|
33
|
+
assert err_ulps<=ulps, "#{f}(#{x})==#{result} to within #{ulps} ulps; error: #{err_ulps} ulps (#{y}) [#{DecNum.context.precision}]"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# def test_trig
|
40
|
+
# DecNum.context(:precision=>12) do
|
41
|
+
# @data12.keys.each do |f|
|
42
|
+
# data = @data12[f]
|
43
|
+
# data.each do |x, result|
|
44
|
+
# assert_equal result, DecNum::Math.send(f, x), "#{f}(#{x})==#{result}"
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
|
50
|
+
# separate tests per function
|
51
|
+
|
52
|
+
def test_sin
|
53
|
+
check_relaxed :sin
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_cos
|
57
|
+
check_relaxed :cos
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_tan
|
61
|
+
check_relaxed :tan
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_asin
|
65
|
+
check_relaxed :asin
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_acos
|
69
|
+
check_relaxed :acos
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_atan
|
73
|
+
check_relaxed :atan
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Javier Goizueta
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-11-30 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -87,6 +87,7 @@ files:
|
|
87
87
|
- test/test_to_int.rb
|
88
88
|
- test/test_to_rf.rb
|
89
89
|
- test/test_tol.rb
|
90
|
+
- test/test_trig.rb
|
90
91
|
- test/test_ulp.rb
|
91
92
|
has_rdoc: true
|
92
93
|
homepage: http://flt.rubyforge.org
|
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
122
|
requirements: []
|
122
123
|
|
123
124
|
rubyforge_project: flt
|
124
|
-
rubygems_version: 1.3.
|
125
|
+
rubygems_version: 1.3.5
|
125
126
|
signing_key:
|
126
127
|
specification_version: 3
|
127
128
|
summary: Floating Point Numbers
|
@@ -144,4 +145,5 @@ test_files:
|
|
144
145
|
- test/test_to_int.rb
|
145
146
|
- test/test_to_rf.rb
|
146
147
|
- test/test_tol.rb
|
148
|
+
- test/test_trig.rb
|
147
149
|
- test/test_ulp.rb
|