flt 1.3.4 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -2
- data/History.txt +5 -0
- data/Rakefile +1 -1
- data/lib/flt.rb +4 -0
- data/lib/flt/bigdecimal.rb +14 -0
- data/lib/flt/float.rb +19 -0
- data/lib/flt/num.rb +22 -1
- data/lib/flt/support.rb +1 -1266
- data/lib/flt/support/flag_values.rb +341 -0
- data/lib/flt/support/formatter.rb +512 -0
- data/lib/flt/support/rationalizer.rb +312 -0
- data/lib/flt/support/rationalizer_extra.rb +168 -0
- data/lib/flt/support/reader.rb +427 -0
- data/lib/flt/tolerance.rb +1 -0
- data/lib/flt/version.rb +1 -1
- data/test/data/.gitignore +4 -0
- data/test/generate_trig_data.rb +2 -2
- data/test/helper.rb +35 -0
- data/test/test_base_digits.rb +4 -4
- data/test/test_dectest.rb +2 -2
- data/test/test_exact.rb +2 -2
- data/test/test_formatter.rb +2 -2
- data/test/test_rationalizer.rb +141 -0
- data/test/test_trig.rb +1 -1
- metadata +11 -2
@@ -0,0 +1,312 @@
|
|
1
|
+
module Flt
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# This class provides efficient conversion of fraction (as approximate floating point numbers)
|
5
|
+
# to rational numbers.
|
6
|
+
class Rationalizer
|
7
|
+
# Exact conversion to rational. Ruby provides this method for all numeric types
|
8
|
+
# since version 1.9.1, but before that it wasn't available for Float or BigDecimal.
|
9
|
+
# This methods supports old Ruby versions.
|
10
|
+
def self.to_r(x)
|
11
|
+
if x.respond_to?(:to_r)
|
12
|
+
x.to_r
|
13
|
+
else
|
14
|
+
case x
|
15
|
+
when Float
|
16
|
+
# Float did not had a #to_r method until Ruby 1.9.1
|
17
|
+
return Rational(x.to_i, 1) if x.modulo(1) == 0
|
18
|
+
if !x.finite?
|
19
|
+
return Rational(0, 0) if x.nan?
|
20
|
+
return x < 0 ? Rational(-1, 0) : Rational(1, 0)
|
21
|
+
end
|
22
|
+
|
23
|
+
f, e = Math.frexp(x)
|
24
|
+
|
25
|
+
if e < Float::MIN_EXP
|
26
|
+
bits = e + Float::MANT_DIG - Float::MIN_EXP
|
27
|
+
else
|
28
|
+
bits = [Float::MANT_DIG,e].max
|
29
|
+
# return Rational(x.to_i, 1) if bits < e
|
30
|
+
end
|
31
|
+
p = Math.ldexp(f, bits)
|
32
|
+
e = bits - e
|
33
|
+
if e < Float::MAX_EXP
|
34
|
+
q = Math.ldexp(1, e)
|
35
|
+
else
|
36
|
+
q = Float::RADIX**e
|
37
|
+
end
|
38
|
+
Rational(p.to_i, q.to_i)
|
39
|
+
|
40
|
+
when BigDecimal
|
41
|
+
# BigDecimal probably didn't have #to_r at some point
|
42
|
+
s, f, b, e = x.split
|
43
|
+
p = f.to_i
|
44
|
+
p = -p if s < 0
|
45
|
+
e = f.size - e
|
46
|
+
if e < 0
|
47
|
+
p *= b**(-e)
|
48
|
+
e = 0
|
49
|
+
end
|
50
|
+
q = b**(e)
|
51
|
+
Rational(p,q)
|
52
|
+
|
53
|
+
else
|
54
|
+
x.to_r
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Convenience methods
|
60
|
+
module AuxiliarFunctions
|
61
|
+
private
|
62
|
+
|
63
|
+
def num_den(x)
|
64
|
+
x = to_r(x)
|
65
|
+
[x.numerator, x.denominator]
|
66
|
+
end
|
67
|
+
|
68
|
+
# fraction part
|
69
|
+
def fp(x)
|
70
|
+
# y = x.modulo(1); return x<0 ? -y : y;
|
71
|
+
x-ip(x)
|
72
|
+
end
|
73
|
+
|
74
|
+
# integer part
|
75
|
+
def ip(x)
|
76
|
+
# Note that ceil, floor return an Integer for Float and Flt::Num, but not for BigDecimal
|
77
|
+
(x<0 ? x.ceil : x.floor).to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
# round to integer
|
81
|
+
def rnd(x)
|
82
|
+
# Note that round returns an Integer for Float and Flt::Num, but not for BigDecimal
|
83
|
+
x.round.to_i
|
84
|
+
end
|
85
|
+
|
86
|
+
# absolute value
|
87
|
+
def abs(x)
|
88
|
+
x.abs
|
89
|
+
end
|
90
|
+
|
91
|
+
def ceil(x)
|
92
|
+
# Note that ceil returns an Integer for Float and Flt::Num, but not for BigDecimal
|
93
|
+
x.ceil.to_i
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_r(x)
|
97
|
+
Rationalizer.to_r(x)
|
98
|
+
end
|
99
|
+
|
100
|
+
def special?(x)
|
101
|
+
!x.finite? # x.class.context.special?(x)
|
102
|
+
end
|
103
|
+
|
104
|
+
def sign(x)
|
105
|
+
x.class.context.sign(x)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
include AuxiliarFunctions
|
110
|
+
extend AuxiliarFunctions
|
111
|
+
|
112
|
+
# Create Rationalizator with given tolerance.
|
113
|
+
def initialize(tol=Tolerance(:epsilon))
|
114
|
+
@tol = tol
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.[](*args)
|
118
|
+
new *args
|
119
|
+
end
|
120
|
+
|
121
|
+
# Rationalization method that finds the fraction with
|
122
|
+
# smallest denominator fraction within the tolerance distance
|
123
|
+
# of an approximate (floating point) number.
|
124
|
+
#
|
125
|
+
def rationalize(x)
|
126
|
+
# Use the algorithm which has been found most efficient, rationalize_Knuth.
|
127
|
+
rationalize_Knuth(x)
|
128
|
+
end
|
129
|
+
|
130
|
+
# This algorithm is derived from exercise 39 of 4.5.3 in
|
131
|
+
# "The Art of Computer Programming", by Donald E. Knuth.
|
132
|
+
def rationalize_Knuth(x)
|
133
|
+
rationalization(x) do |x, dx|
|
134
|
+
x = to_r(x)
|
135
|
+
dx = to_r(dx)
|
136
|
+
xp,xq = num_den(x-dx)
|
137
|
+
yp,yq = num_den(x+dx)
|
138
|
+
|
139
|
+
a = []
|
140
|
+
fin, odd = false, false
|
141
|
+
while !fin && xp != 0 && yp != 0
|
142
|
+
odd = !odd
|
143
|
+
xp,xq = xq,xp
|
144
|
+
ax = xp.div(xq)
|
145
|
+
xp -= ax*xq
|
146
|
+
|
147
|
+
yp,yq = yq,yp
|
148
|
+
ay = yp.div(yq)
|
149
|
+
yp -= ay*yq
|
150
|
+
|
151
|
+
if ax!=ay
|
152
|
+
fin = true
|
153
|
+
ax,xp,xq = ay,yp,yq if odd
|
154
|
+
end
|
155
|
+
a << ax # .to_i
|
156
|
+
end
|
157
|
+
a[-1] += 1 if xp != 0 && a.size > 0
|
158
|
+
p,q = 1,0
|
159
|
+
(1..a.size).each{|i| p, q = q+p*a[-i], p}
|
160
|
+
[q, p]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# This is algorithm PDQ2 by Joe Horn.
|
165
|
+
def rationalize_Horn(x)
|
166
|
+
rationalization(x) do |z, t|
|
167
|
+
a,b = num_den(t)
|
168
|
+
n0,d0 = (n,d = num_den(z))
|
169
|
+
cn,x,pn,cd,y,pd,lo,hi,mid,q,r = 1,1,0,0,0,1,0,1,1,0,0
|
170
|
+
begin
|
171
|
+
q,r = n.divmod(d)
|
172
|
+
x = q*cn+pn
|
173
|
+
y = q*cd+pd
|
174
|
+
pn = cn
|
175
|
+
cn = x
|
176
|
+
pd = cd
|
177
|
+
cd = y
|
178
|
+
n,d = d,r
|
179
|
+
end until b*(n0*y-d0*x).abs <= a*d0*y
|
180
|
+
|
181
|
+
if q > 1
|
182
|
+
hi = q
|
183
|
+
begin
|
184
|
+
mid = (lo + hi).div(2)
|
185
|
+
x = cn - pn*mid
|
186
|
+
y = cd - pd*mid
|
187
|
+
if b*(n0*y - d0*x).abs <= a*d0*y
|
188
|
+
lo = mid
|
189
|
+
else
|
190
|
+
hi = mid
|
191
|
+
end
|
192
|
+
end until hi - lo <= 1
|
193
|
+
x = cn - pn*lo
|
194
|
+
y = cd - pd*lo
|
195
|
+
end
|
196
|
+
[x, y]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# This is from a RPL program by Tony Hutchins (PDR6).
|
201
|
+
def rationalize_HornHutchins(x)
|
202
|
+
rationalization(x) do |z, t|
|
203
|
+
a,b = num_den(t)
|
204
|
+
n0,d0 = (n,d = num_den(z))
|
205
|
+
cn,x,pn,cd,y,pd,lo,hi,mid,q,r = 1,1,0,0,0,1,0,1,1,0,0
|
206
|
+
begin
|
207
|
+
q,r = n.divmod(d)
|
208
|
+
x = q*cn+pn
|
209
|
+
y = q*cd+pd
|
210
|
+
pn = cn
|
211
|
+
cn = x
|
212
|
+
pd = cd
|
213
|
+
cd = y
|
214
|
+
n,d = d,r
|
215
|
+
end until b*(n0*y-d0*x).abs <= a*d0*y
|
216
|
+
|
217
|
+
if q > 1
|
218
|
+
hi = q
|
219
|
+
begin
|
220
|
+
mid = (lo + hi).div(2)
|
221
|
+
x = cn - pn*mid
|
222
|
+
y = cd - pd*mid
|
223
|
+
if b*(n0*y - d0*x).abs <= a*d0*y
|
224
|
+
lo = mid
|
225
|
+
else
|
226
|
+
hi = mid
|
227
|
+
end
|
228
|
+
end until hi - lo <= 1
|
229
|
+
x = cn - pn*lo
|
230
|
+
y = cd - pd*lo
|
231
|
+
end
|
232
|
+
[x, y]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Best fraction given maximum denominator
|
237
|
+
# Algorithm Copyright (c) 1991 by Joseph K. Horn.
|
238
|
+
#
|
239
|
+
# The implementation of this method uses floating point
|
240
|
+
# arithmetic which limits the magnitude and precision of the results, specially
|
241
|
+
# using Float values.
|
242
|
+
def self.max_denominator(f, max_den=1000000000, num_class=nil)
|
243
|
+
return rationalize_special(f) if special?(f)
|
244
|
+
return nil if max_den < 1
|
245
|
+
num_class ||= f.class
|
246
|
+
context = num_class.context
|
247
|
+
return ip(f),1 if fp(f) == 0
|
248
|
+
|
249
|
+
cast = lambda{|x| context.Num(x)}
|
250
|
+
|
251
|
+
one = cast[1]
|
252
|
+
|
253
|
+
sign = f < 0
|
254
|
+
f = -f if sign
|
255
|
+
|
256
|
+
a,b,c = 0,1,f
|
257
|
+
while b < max_den && c != 0
|
258
|
+
cc = one/c
|
259
|
+
a,b,c = b, ip(cc)*b+a, fp(cc)
|
260
|
+
end
|
261
|
+
|
262
|
+
if b>max_den
|
263
|
+
b -= a*ceil(cast[b-max_den]/a)
|
264
|
+
end
|
265
|
+
|
266
|
+
f1,f2 = [a,b].collect{|x| abs(cast[rnd(x*f)]/x-f)}
|
267
|
+
|
268
|
+
a = f1 > f2 ? b : a
|
269
|
+
|
270
|
+
num,den = rnd(a*f).to_i,a
|
271
|
+
den = 1 if abs(den) < 1
|
272
|
+
|
273
|
+
num = -num if sign
|
274
|
+
|
275
|
+
return num,den
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.rationalize_special(x)
|
279
|
+
if x.nan?
|
280
|
+
[0, 0]
|
281
|
+
else
|
282
|
+
[sign(x), 0]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
private
|
287
|
+
|
288
|
+
def rationalization(x)
|
289
|
+
return Rationalizer.rationalize_special(x) if special?(x)
|
290
|
+
num_tol = @tol.kind_of?(Numeric)
|
291
|
+
if !num_tol && @tol.zero?(x)
|
292
|
+
# num,den = num_den(x)
|
293
|
+
num,den = 0,1
|
294
|
+
else
|
295
|
+
negans = false
|
296
|
+
if x<0
|
297
|
+
negans = true
|
298
|
+
x = -x
|
299
|
+
end
|
300
|
+
dx = num_tol ? @tol : @tol.value(x)
|
301
|
+
|
302
|
+
num, den = yield x, dx
|
303
|
+
|
304
|
+
num = -num if negans
|
305
|
+
end
|
306
|
+
[num, den]
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# Some rationalization algorithms currently not being used
|
2
|
+
|
3
|
+
module Flt
|
4
|
+
module Support
|
5
|
+
|
6
|
+
class Rationalizer
|
7
|
+
|
8
|
+
# Simple Rationalization by Joe Horn
|
9
|
+
def rationalize_Horn_simple(x, smallest_denominator = false)
|
10
|
+
rationalization(x) do |z, t|
|
11
|
+
a,b = num_den(t)
|
12
|
+
n0,d0 = (n,d = z.nio_xr.nio_num_den)
|
13
|
+
cn,x,pn,cd,y,pd,lo,hi,mid,q,r = 1,1,0,0,0,1,0,1,1,0,0
|
14
|
+
begin
|
15
|
+
q,r = n.divmod(d)
|
16
|
+
x = q*cn+pn
|
17
|
+
y = q*cd+pd
|
18
|
+
pn = cn
|
19
|
+
cn = x
|
20
|
+
pd = cd
|
21
|
+
cd = y
|
22
|
+
n,d = d,r
|
23
|
+
end until b*(n0*y-d0*x).abs <= a*d0*y
|
24
|
+
if smallest_denominator
|
25
|
+
if q>1
|
26
|
+
hi = q
|
27
|
+
begin
|
28
|
+
mid = (lo+hi).div(2)
|
29
|
+
x = cn-pn*mid
|
30
|
+
y = cd-pd*mid
|
31
|
+
if b*(n0*y-d0*x).abs <= a*d0*y
|
32
|
+
lo = mid
|
33
|
+
else
|
34
|
+
hi = mid
|
35
|
+
end
|
36
|
+
end until hi-lo <= 1
|
37
|
+
x = cn - pn*lo
|
38
|
+
y = cd - pd*lo
|
39
|
+
end
|
40
|
+
end
|
41
|
+
[x, y]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Smallest denominator rationalization procedure by Joe Horn and Tony Hutchins; this
|
47
|
+
# is the most efficient method as implemented in RPL.
|
48
|
+
# Tony Hutchins has come up with PDR6, an improvement over PDQ2;
|
49
|
+
# though benchmarking does not show any speed improvement under Ruby.
|
50
|
+
def rationalize_Horn_Hutchins(x)
|
51
|
+
rationalization(x) do |x, dx|
|
52
|
+
a,b = num_den(dx)
|
53
|
+
n,d = num_den(x)
|
54
|
+
pc,ce = n,-d
|
55
|
+
pc,cd = 1,0
|
56
|
+
t = a*b
|
57
|
+
begin
|
58
|
+
tt = (-pe).div(ce)
|
59
|
+
pd,cd = cd,pd+tt*cd
|
60
|
+
pe,ce = ce,pe+tt*ce
|
61
|
+
end until b*ce.abs <= t*cd
|
62
|
+
tt = t * (pe<0 ? -1 : (pe>0 ? +1 : 0))
|
63
|
+
tt = (tt*d+b*ce).div(tt*pd+b*pe)
|
64
|
+
[(n*cd-ce-(n*pd-pe)*tt)/d, tt/(cd-tt*pd)]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Smallest denominator rationalization based on exercise 39 of \cite[\S 4.5.3]{Knuth}.
|
69
|
+
# This has been found the most efficient method (except for large tolerances)
|
70
|
+
# as implemented in Ruby.
|
71
|
+
# Here's the rationalization procedure based on the exercise by Knuth.
|
72
|
+
# We need first to calculate the limits (x-dx, x+dx)
|
73
|
+
# of the range where we'll look for the rational number.
|
74
|
+
# If we compute them using floating point and then convert then to fractions this method is
|
75
|
+
# always more efficient than the other procedures implemented here, but it may be
|
76
|
+
# less accurate. We can achieve perfect accuracy as the other methods by doing the
|
77
|
+
# substraction and addition with rationals, but then this method becomes less efficient than
|
78
|
+
# the others for a low number of iterations (low precision required).
|
79
|
+
def rationalize_Knuth_Goizueta(x)
|
80
|
+
rationalization(x) do |x, dx|
|
81
|
+
x = to_r(x)
|
82
|
+
dx = to_r(dx)
|
83
|
+
xp,xq = num_den(x-dx)
|
84
|
+
yp,yq = num_den(x+dx)
|
85
|
+
|
86
|
+
a = []
|
87
|
+
fin,odd = false,false
|
88
|
+
while !fin && xp!=0 && yp!=0
|
89
|
+
odd = !odd
|
90
|
+
xp,xq = xq,xp
|
91
|
+
ax = xp.div(xq)
|
92
|
+
xp -= ax*xq
|
93
|
+
|
94
|
+
yp,yq = yq,yp
|
95
|
+
ay = yp.div(yq)
|
96
|
+
yp -= ay*yq
|
97
|
+
|
98
|
+
if ax!=ay
|
99
|
+
fin = true
|
100
|
+
ax,xp,xq = ay,yp,yq if odd
|
101
|
+
end
|
102
|
+
a << ax # .to_i
|
103
|
+
end
|
104
|
+
a[-1] += 1 if xp!=0 && a.size>0
|
105
|
+
p,q = 1,0
|
106
|
+
(1..a.size).each{|i| p,q=q+p*a[-i],p}
|
107
|
+
[q, p]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# La siguiente variante realiza una iteración menos si xq<xp y una iteración más
|
112
|
+
# si xq>xp.
|
113
|
+
def rationalize_Knuth_Goizueta_b(x)
|
114
|
+
rationalization(x) do |x, dx|
|
115
|
+
x = to_r(x)
|
116
|
+
dx = to_r(dx)
|
117
|
+
xq,xp = num_den(x-dx)
|
118
|
+
yq,yp = num_den(x+dx)
|
119
|
+
|
120
|
+
a = []
|
121
|
+
fin,odd = false,false
|
122
|
+
while !fin && xp!=0 && yp!=0
|
123
|
+
odd = !odd
|
124
|
+
xp,xq = xq,xp
|
125
|
+
ax = xp.div(xq)
|
126
|
+
xp -= ax*xq
|
127
|
+
|
128
|
+
yp,yq = yq,yp
|
129
|
+
ay = yp.div(yq)
|
130
|
+
yp -= ay*yq
|
131
|
+
|
132
|
+
if ax!=ay
|
133
|
+
fin = true
|
134
|
+
ax,xp,xq = ay,yp,yq if odd
|
135
|
+
end
|
136
|
+
a << ax # .to_i
|
137
|
+
end
|
138
|
+
a[-1] += 1 if xp!=0 && a.size>0
|
139
|
+
p,q = 1,0
|
140
|
+
(1..a.size).each{|i| p,q=q+p*a[-i],p}
|
141
|
+
[p, q]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# An exact rationalization method for binary floating point
|
146
|
+
# that yields smallest fractions when possible and is not too slow
|
147
|
+
def exact_binary_rationalization(x)
|
148
|
+
p, q = x, 1
|
149
|
+
while p.modulo(1) != 0
|
150
|
+
p *= 2.0
|
151
|
+
q <<= 1 # q *= 2
|
152
|
+
end
|
153
|
+
Rational(p.to_i, q)
|
154
|
+
end
|
155
|
+
|
156
|
+
# An a here's a shorter implementation relying on the semantics of the power operator, but
|
157
|
+
# which is somewhat slow:
|
158
|
+
def exact_float_rationalization(x)
|
159
|
+
f,e = Math.frexp(x)
|
160
|
+
f = Math.ldexp(f, Float::MANT_DIG)
|
161
|
+
e -= Float::MANT_DIG
|
162
|
+
return Rational(f.to_i*(Float::RADIX**e.to_i), 1)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|