flt 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|