flt 1.3.4 → 1.4.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.
- 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
|