intervals 0.3.63 → 0.4.75
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +5 -0
- data/VERSION.txt +1 -1
- data/lib/fpu.rb +5 -204
- data/lib/interval.rb +59 -509
- data/lib/struct_float.rb +1 -65
- data/test/runall.rb +14 -0
- data/test/test_fpu.rb +210 -0
- data/test/test_interval.rb +645 -0
- data/test/test_struct_float.rb +72 -0
- data/var/clean-nan.rb +59 -0
- metadata +8 -3
data/lib/struct_float.rb
CHANGED
@@ -65,69 +65,5 @@ class Float
|
|
65
65
|
|
66
66
|
end
|
67
67
|
|
68
|
-
require 'test/
|
68
|
+
require File.dirname(__FILE__) + '/../test/test_struct_float.rb' if __FILE__ == $0
|
69
69
|
|
70
|
-
# Tests Struct::Float.
|
71
|
-
class Struct::Float::Test < Test::Unit::TestCase
|
72
|
-
|
73
|
-
def assert_consistent(f,s)
|
74
|
-
assert_equal(f,s.to_f)
|
75
|
-
assert_equal(s,f.to_struct)
|
76
|
-
assert_equal(s, eval(s.inspect))
|
77
|
-
assert(f.ulp.infinite? || (f/f.ulp).to_i == f/f.ulp, [f,f.ulp].inspect)
|
78
|
-
end
|
79
|
-
|
80
|
-
def assert_nan(s)
|
81
|
-
# NaN does not have a unique representation, and therefore
|
82
|
-
# it does not make sense to have it as a constant as for
|
83
|
-
# Infinity. In particular a NaN has 2047 as biased exponent and
|
84
|
-
# non-zero fraction
|
85
|
-
assert(2047, s.biased_exp)
|
86
|
-
assert(!s.fraction.zero?, s.fraction)
|
87
|
-
# You cannot test for NaN with equality. You MUST use nan?
|
88
|
-
assert(s.to_f.nan?, s.to_f)
|
89
|
-
assert_equal(s, eval(s.inspect))
|
90
|
-
end
|
91
|
-
|
92
|
-
def test_consistency
|
93
|
-
assert_consistent Infinity, Struct::Float[1,2047,0]
|
94
|
-
assert_consistent -Infinity, Struct::Float[-1,2047,0]
|
95
|
-
assert_consistent 2.0 ** -1074, Struct::Float[1,0,1]
|
96
|
-
assert_consistent 0.0, Struct::Float[1,0,0]
|
97
|
-
assert_consistent -0.0, Struct::Float[-1,0,0]
|
98
|
-
assert_consistent 1.0, Struct::Float[1,1023, 0]
|
99
|
-
assert_consistent 1/3.0, Struct::Float[1,1021,0x5555555555555]
|
100
|
-
assert_consistent 1/5.0, Struct::Float[1,1020,0x999999999999a]
|
101
|
-
end
|
102
|
-
|
103
|
-
def test_nan
|
104
|
-
assert_nan( (0.0/0.0).to_struct)
|
105
|
-
assert_nan( Struct::Float[-1,2047,34])
|
106
|
-
assert_nan( Struct::Float[+1,2047,34])
|
107
|
-
end
|
108
|
-
|
109
|
-
def test_denormalized
|
110
|
-
assert_consistent 34 * 2.0 ** -(1023+51), Struct::Float[1,0,34]
|
111
|
-
end
|
112
|
-
|
113
|
-
def test_normalized
|
114
|
-
assert_consistent -(2**52+34) * 2.0**(1043-1023-52), Struct::Float[-1,1043,34]
|
115
|
-
end
|
116
|
-
|
117
|
-
def test_samples
|
118
|
-
n = 1000
|
119
|
-
srand 12345
|
120
|
-
n.times{
|
121
|
-
s = Struct::Float[2*rand(2) -1, rand(2048), rand(2**52)]
|
122
|
-
f = s.to_f
|
123
|
-
if f.nan?
|
124
|
-
assert_nan(s)
|
125
|
-
else
|
126
|
-
assert_consistent(f,s)
|
127
|
-
end
|
128
|
-
}
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
Test::Unit.run = (__FILE__ != $0)
|
data/test/runall.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# Tests for the whole library.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2006 Stefano Taschini <taschini@ieee.org>.
|
6
|
+
# Licensed under the terms of LGPL: http://www.gnu.org/copyleft/lesser.html
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + '/../lib/interval'
|
9
|
+
|
10
|
+
Dir.glob(File.dirname(__FILE__) + '/test*.rb').each{|f|
|
11
|
+
require f
|
12
|
+
}
|
13
|
+
|
14
|
+
Test::Unit.run = (__FILE__ != $0)
|
data/test/test_fpu.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# Tests for the module FPU.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2006 Stefano Taschini <taschini@ieee.org>.
|
6
|
+
# Licensed under the terms of LGPL: http://www.gnu.org/copyleft/lesser.html
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + '/../lib/fpu' if __FILE__ == $0
|
9
|
+
require 'test/unit'
|
10
|
+
|
11
|
+
# Tests the FPU rounding modes.
|
12
|
+
class FPU::TestRounding < Test::Unit::TestCase
|
13
|
+
include FPU
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@saved = rounding
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
self.rounding = @saved
|
21
|
+
end
|
22
|
+
|
23
|
+
# Nearest rounding of 1/3 is downwards.
|
24
|
+
def test_third
|
25
|
+
assert(FPU.down {1/3.0} == 1/3.0, "1/3 to -");
|
26
|
+
assert(FPU.up {1/3.0} != 1/3.0, "1/3 to +");
|
27
|
+
assert(FPU.down {-1/3.0} != -1/3.0, "-1/3 to -");
|
28
|
+
assert(FPU.up {-1/3.0} == -1/3.0, "-1/3 to +");
|
29
|
+
end
|
30
|
+
|
31
|
+
# 1/4 is exact.
|
32
|
+
def test_fourth
|
33
|
+
assert(FPU.down {1/4.0} == 1/4.0, "1/4 to -");
|
34
|
+
assert(FPU.up {1/4.0} == 1/4.0, "1/4 to +");
|
35
|
+
assert(FPU.down {-1/4.0} == -1/4.0, "-1/4 to -");
|
36
|
+
assert(FPU.up {-1/4.0} == -1/4.0, "-1/4 to +");
|
37
|
+
end
|
38
|
+
|
39
|
+
# Nearest rounding of 1/5 is upwards.
|
40
|
+
def test_fifth
|
41
|
+
assert(FPU.down {1/5.0} != 1/5.0, "1/5 to -");
|
42
|
+
assert(FPU.up {1/5.0} == 1/5.0, "1/5 to +");
|
43
|
+
assert(FPU.down {-1/5.0} == -1/5.0, "-1/5 to -");
|
44
|
+
assert(FPU.up {-1/5.0} != -1/5.0, "-1/5 to +");
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_platform
|
48
|
+
modes = case (RUBY_PLATFORM)
|
49
|
+
when /powerpc/ then [0,2,3]
|
50
|
+
when /i[0-9]86/ then [0,0x0800,0x0400]
|
51
|
+
when /sparc/ then [0,0x80000000, 0xC0000000]
|
52
|
+
else flunk("Cannot be sure of the processor flags for platform "+RUBY_PLATFORM)
|
53
|
+
end
|
54
|
+
assert_equal(modes,
|
55
|
+
[rounding, FPU.up { rounding }, FPU.down { rounding }])
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_rounding_consts
|
59
|
+
assert_equal(PlusRounding , FPU.up { FPU.rounding })
|
60
|
+
assert_equal(MinusRounding , FPU.down { FPU.rounding })
|
61
|
+
assert_equal(StandardRounding, rounding)
|
62
|
+
|
63
|
+
tester = proc { |x| assert_equal(x, (self.rounding = x; FPU.rounding))}
|
64
|
+
tester.call(PlusRounding);
|
65
|
+
tester.call(MinusRounding);
|
66
|
+
tester.call(StandardRounding);
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_exception_safety
|
70
|
+
assert_raise(ArgumentError){FPU.up {[2.0, 0/0.0].sort}}
|
71
|
+
assert_equal(StandardRounding, rounding)
|
72
|
+
assert_raise(ArgumentError){FPU.down {[2.0, 0/0.0].sort}}
|
73
|
+
assert_equal(StandardRounding, rounding)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_power
|
77
|
+
x = 1/3.0;
|
78
|
+
# The cube of one third should depend on the rounding mode
|
79
|
+
assert_operator(FPU.down{x*x*x}, :<, FPU.up{x*x*x})
|
80
|
+
# But using the built-in power operator, usually it doesn't
|
81
|
+
# assert_equal(FPU.down{x**3}, FPU.up{x**3})
|
82
|
+
# So we define an integer power methods that does
|
83
|
+
assert_operator(FPU.down{FPU.power(x, 3)}, :<, FPU.up{FPU.power(x, 3)})
|
84
|
+
|
85
|
+
assert_equal(32, FPU.power(2, 5))
|
86
|
+
assert_equal([FPU.down{x*x*x}, FPU.up{x*x*x}],[FPU.down{FPU.power(x, 3)},FPU.up{FPU.power(x, 3)}])
|
87
|
+
assert_equal(1.25 ** 13, FPU.power(1.25, 13))
|
88
|
+
assert_equal((-1.25) ** 17, FPU.power(-1.25, 17))
|
89
|
+
assert_equal((-1.25) ** 18, FPU.power(-1.25, 18))
|
90
|
+
(1..10).each {|i|
|
91
|
+
assert_equal(2.0 ** i, FPU.power(2.0, i))
|
92
|
+
}
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
if !FPU.respond_to?(:exp_up)
|
99
|
+
warn "***\n*** Module fpu is compiled without transcendetal functions\n***"
|
100
|
+
else
|
101
|
+
# Tests the correct-rounding transcendental functions, if they exist.
|
102
|
+
class FPU::TestTranscendental < Test::Unit::TestCase
|
103
|
+
require 'enumerator'
|
104
|
+
|
105
|
+
def locate(name)
|
106
|
+
([File.dirname(__FILE__)] + $LOAD_PATH).
|
107
|
+
map{|p| File.expand_path("../test/#{name}", File.expand_path(p))}.
|
108
|
+
find(proc {raise LoadError, "Cannot locate #{name}"}) {|p|
|
109
|
+
File.exist?(p)
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
@@test_vectors_cache = {}
|
114
|
+
|
115
|
+
def get_vectors (name)
|
116
|
+
@@test_vectors_cache[name] ||= IO.readlines(locate(name)).map{|x|
|
117
|
+
e1,m1,e2,m2 = x.chomp.split(' ').map{|y| y.to_i}
|
118
|
+
[e1,m1,e2,m2,e2,m2 - 1].enum_slice(2).map{|e,m| Math::ldexp(m,e)}}
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_easy
|
122
|
+
a, b, c= [
|
123
|
+
[-1, 1008, 2553927345288884],
|
124
|
+
[1, 1022, 4503168880409012],
|
125
|
+
[1, 1022, 4503168880409011]].map{|x| Struct::Float[*x].to_f}
|
126
|
+
assert_equal(b, FPU.exp_up(a))
|
127
|
+
assert_equal(c, FPU.exp_down(a))
|
128
|
+
|
129
|
+
assert_equal(Infinity, FPU.exp_up(Infinity))
|
130
|
+
assert_equal(Infinity, FPU.exp_down(Infinity))
|
131
|
+
assert_equal(0, FPU.exp_up(-Infinity))
|
132
|
+
assert_equal(0, FPU.exp_down(-Infinity))
|
133
|
+
|
134
|
+
assert_equal(Infinity, FPU.log_up(Infinity))
|
135
|
+
assert_equal(Infinity, FPU.log_down(Infinity))
|
136
|
+
assert_equal(-Infinity, FPU.log_up(0))
|
137
|
+
assert_equal(-Infinity, FPU.log_down(0))
|
138
|
+
|
139
|
+
assert_equal(Math::PI, 2*FPU.atan_down(Infinity))
|
140
|
+
assert_equal(Math::PI+2 ** -51, 2*FPU.atan_up(Infinity))
|
141
|
+
assert_equal(-Math::PI, 2*FPU.atan_up(-Infinity))
|
142
|
+
assert_equal(-Math::PI-2 ** -51, 2*FPU.atan_down(-Infinity))
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_exp
|
146
|
+
get_vectors("data_exp.txt").map{|x,y,z|
|
147
|
+
assert_equal(y, FPU.exp_up(x))
|
148
|
+
assert_equal(z, FPU.exp_down(x))
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_log
|
153
|
+
get_vectors("data_log.txt").map{|x,y,z|
|
154
|
+
assert_equal(y, FPU.log_up(x))
|
155
|
+
assert_equal(z, FPU.log_down(x))
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_atan
|
160
|
+
get_vectors("data_atan.txt").map{|x,y,z|
|
161
|
+
assert_equal(y, FPU.atan_up(x))
|
162
|
+
assert_equal(z, FPU.atan_down(x))
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_cos
|
167
|
+
get_vectors("data_cos.txt").map{|x,y,z|
|
168
|
+
assert_equal(y, FPU.cos_up(x))
|
169
|
+
assert_equal(z, FPU.cos_down(x))
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_sin
|
174
|
+
get_vectors("data_sin.txt").map{|x,y,z|
|
175
|
+
assert_equal(y, FPU.sin_up(x))
|
176
|
+
assert_equal(z, FPU.sin_down(x))
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_tan
|
181
|
+
get_vectors("data_tan.txt").map{|x,y,z|
|
182
|
+
assert_equal(y, FPU.tan_up(x))
|
183
|
+
assert_equal(z, FPU.tan_down(x))
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_cosh
|
188
|
+
get_vectors("data_cosh.txt").map{|x,y,z|
|
189
|
+
assert_equal(y, FPU.cosh_up(x))
|
190
|
+
assert_equal(z, FPU.cosh_down(x))
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_sinh
|
195
|
+
get_vectors("data_sinh.txt").map{|x,y,z|
|
196
|
+
assert_equal(y, FPU.sinh_up(x))
|
197
|
+
assert_equal(z, FPU.sinh_down(x))
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
# It can happen, depending on the implementation, that Math::sinh is broken
|
202
|
+
def broken_math #:nodoc:
|
203
|
+
x = -8218111829489689 * 2.0 **(-67)
|
204
|
+
y = -8218111833737307 * 2.0 **(-67)
|
205
|
+
assert_equal(y, FPU.sinh_up(x))
|
206
|
+
z = Math::sinh(x);
|
207
|
+
assert(z <= y, [FPU.split(x), FPU.split(z), FPU.split(y)].inspect)
|
208
|
+
end
|
209
|
+
|
210
|
+
end end
|
@@ -0,0 +1,645 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# Tests for the class Interval.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2006 Stefano Taschini <taschini@ieee.org>.
|
6
|
+
# Licensed under the terms of LGPL: http://www.gnu.org/copyleft/lesser.html
|
7
|
+
|
8
|
+
# To control the FPU's rounding mode
|
9
|
+
require File.dirname(__FILE__) + '/../lib/interval' if __FILE__ == $0
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
# Assertions to be used when testing intervals.
|
13
|
+
module Interval::Assertions
|
14
|
+
|
15
|
+
def assert_sharp(x)
|
16
|
+
assert(x.sharp?, x.inspect + " should be sharp.")
|
17
|
+
assert((-x).sharp?, (-x).inspect + " should be sharp.")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Assert that an interval is not sharp.
|
21
|
+
def assert_fuzzy(x)
|
22
|
+
assert(!x.sharp?, x.inspect + " should not be sharp.")
|
23
|
+
assert(!(-x).sharp?, (-x).inspect + " should not be sharp.")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Assert that +s+ is a solution to +f+ == 0. Unless the +weak+ flag is set,
|
27
|
+
# assert also that +s+ == +expected+.
|
28
|
+
# If the +weak+ flag is set, assert that +s+ includes +expected+.
|
29
|
+
def assert_solution(s,f,expected, weak = nil)
|
30
|
+
res = s.map{|c| f.call(c)}.inject{|a,c| a|c}
|
31
|
+
# Did something spectacarly unexpected happen?
|
32
|
+
assert(res.simple?, res)
|
33
|
+
# Is it a solution?
|
34
|
+
assert_include(res, 0)
|
35
|
+
# Is it minimal?
|
36
|
+
assert(s.all? {|x| x.extrema.all? {|y| f.call(Interval[y]).include?(0)}}, s.inspect +
|
37
|
+
" is non minmal")
|
38
|
+
# Is it the expected solution?
|
39
|
+
if weak
|
40
|
+
# check the granularity
|
41
|
+
assert_equal(expected.components.size, s.components.size, [expected,s].inspect)
|
42
|
+
# check for inclusion
|
43
|
+
assert_subset(expected, s)
|
44
|
+
else
|
45
|
+
# check for equality
|
46
|
+
assert_equal(expected,s)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Assert that the interval +i+ includes +x+.
|
51
|
+
def assert_include(i, x)
|
52
|
+
assert(i.include?(x), i.inspect + " should include " + x.inspect)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Assert that the interval +i+ does not include +x+.
|
56
|
+
def assert_outside(i, x)
|
57
|
+
assert(!i.include?(x), i.inspect + " should not include " + x.inspect)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Assert that +x+ is a subset of +y+.
|
61
|
+
def assert_subset(x,y)
|
62
|
+
assert(x == x & y, x.inspect + " should be a subset of " + y.inspect)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# Tests Interval fundamentals: construction, equality and inspection.
|
68
|
+
class Interval::TestFundamentals < Test::Unit::TestCase
|
69
|
+
|
70
|
+
def test_constructor_and_equality
|
71
|
+
tester = proc {|i,ex,klass|
|
72
|
+
assert_equal(klass, i.class)
|
73
|
+
assert_equal(ex, i.construction)
|
74
|
+
assert_equal(i, Interval[*i.construction])
|
75
|
+
}
|
76
|
+
|
77
|
+
tester.call Interval[1], [1], Interval::Simple
|
78
|
+
tester.call Interval[1,2], [1,2], Interval::Simple
|
79
|
+
tester.call Interval[[1,2],[3,4]], [[1,2],[3,4]], Interval::Multiple
|
80
|
+
tester.call Interval[[1],[2]], [[1],[2]], Interval::Multiple
|
81
|
+
tester.call Interval[[1]], [1], Interval::Simple
|
82
|
+
tester.call Interval[2,0/0.0], [-Infinity,+Infinity], Interval::Simple
|
83
|
+
tester.call Interval[0/0.0,9], [-Infinity,+Infinity], Interval::Simple
|
84
|
+
tester.call Interval[[1,3],[4,6],[2,5],[9,9]], [[1,6],[9]], Interval::Multiple
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_construction_failure
|
88
|
+
assert_raise(Interval::ConstructionError) { Interval[1,[2,3]] }
|
89
|
+
assert_raise(Interval::ConstructionError) { Interval[1,2,3] }
|
90
|
+
assert_raise(Interval::ConstructionError) { Interval[[1],2] }
|
91
|
+
assert_raise(Interval::ConstructionError) { Interval['a', 1] }
|
92
|
+
assert_raise(Interval::ConstructionError) { Interval['a', 'b'] }
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_inequality
|
96
|
+
assert_not_equal(Interval[1,2], Interval[1,3])
|
97
|
+
assert_not_equal(Interval[1,2], Interval[0,2])
|
98
|
+
assert_not_equal(Interval[1,2], Interval[0,3])
|
99
|
+
assert_not_equal(Interval[1,2], 3)
|
100
|
+
assert_not_equal(Interval[[1,2],[3,4]], Interval[[1,0],[3,4]])
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_inspect_and_to_s
|
104
|
+
tester = proc {|s|
|
105
|
+
i = eval(s)
|
106
|
+
assert_equal(s,i.inspect)
|
107
|
+
assert_equal(s,i.to_s)
|
108
|
+
}
|
109
|
+
tester.call "Interval[1]"
|
110
|
+
tester.call "Interval[1, 2]"
|
111
|
+
tester.call "Interval[[1, 2], [3, 4]]"
|
112
|
+
tester.call "Interval[[1, 2], [4], [5, 6], [Infinity]]"
|
113
|
+
tester.call "Interval[1, 2]"
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
# Tests the arithmetic of Interval::Simple.
|
119
|
+
class Interval::TestSimpleArithmetic < Test::Unit::TestCase
|
120
|
+
|
121
|
+
def test_plus
|
122
|
+
assert_equal(Interval[-Infinity,+Infinity], Interval[-Infinity] + Interval[Infinity])
|
123
|
+
assert_equal(Interval[4,6], Interval[1,2] + Interval[3,4])
|
124
|
+
assert_equal(Interval[3,Infinity], Interval[1,Infinity]+Interval[2])
|
125
|
+
assert_equal(Interval[-Infinity,+Infinity],Interval[-Infinity,-1] + Interval[2,+Infinity])
|
126
|
+
assert_equal(Interval[-Infinity,+Infinity],Interval[-Infinity] + Interval[8,+Infinity])
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_minus
|
130
|
+
assert_equal(Interval[-2,-1], -Interval[1,2])
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_dsub
|
134
|
+
assert_equal(Interval[2], Interval[3,4].dsub(Interval[1,2]))
|
135
|
+
assert_equal(Interval[3,4], Interval[4,6].dsub(Interval[1,2]))
|
136
|
+
assert_equal(
|
137
|
+
Interval[],
|
138
|
+
Interval.union(Interval[3,4].dsub(Interval[0,2])))
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_times
|
142
|
+
assert_equal(Interval[-Infinity,+Infinity],Interval[Infinity] * Interval[0])
|
143
|
+
assert_equal(Interval[+Infinity],Interval[Infinity] * Interval[3])
|
144
|
+
assert_equal(Interval[-8,+10], Interval[1,2] * Interval[-4,5])
|
145
|
+
assert_equal(Interval[3,8], Interval[1,2] * Interval[3,4])
|
146
|
+
assert_equal(Interval[-Infinity,+Infinity],Interval[0,1] * Interval[2,Infinity])
|
147
|
+
assert_equal(Interval[2, Infinity], Interval[-Infinity,-2] * Interval[-Infinity,-1])
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_inverse
|
151
|
+
assert_equal(Interval[0.5,1],Interval[1,2].inverse)
|
152
|
+
assert_equal(Interval[-1,-0.5],(-Interval[1,2]).inverse)
|
153
|
+
assert_equal(Interval[[-Infinity,-1],[0.5,+Infinity]],Interval[-1,2].inverse)
|
154
|
+
assert_equal(Interval[[-Infinity],[1,+Infinity]],Interval[0,1].inverse)
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_power
|
158
|
+
assert_equal((-Interval[1,2]).inverse, (-Interval[1,2]) ** -1)
|
159
|
+
assert_equal(Interval[0,4], Interval[-1,2]**2)
|
160
|
+
assert_equal(Interval[-27,8], Interval[-3,2]**3)
|
161
|
+
|
162
|
+
assert_equal(
|
163
|
+
Interval[FPU.down{(1/3.0)*(1/3.0)},FPU.up{(1/3.0)*(1/3.0)}],
|
164
|
+
(Interval[1]/3.0) ** 2)
|
165
|
+
|
166
|
+
assert_equal(
|
167
|
+
Interval[
|
168
|
+
FPU.down{(1/3.0)*(1/3.0)*(1/3.0)},
|
169
|
+
FPU.up{(1/3.0)*(1/3.0)*(1/3.0)}],
|
170
|
+
(Interval[1]/3.0) ** 3)
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
# Tests other methods of Interval::Simple.
|
176
|
+
class Interval::TestSimpleMethods < Test::Unit::TestCase
|
177
|
+
|
178
|
+
def test_midpoint
|
179
|
+
assert_equal(1.5,Interval[1,2].midpoint)
|
180
|
+
assert_raise(NoMethodError){ Interval[[1,2],[3,4]].midpoint }
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_width
|
184
|
+
assert_equal(1, Interval[1,2].width)
|
185
|
+
assert_equal(1, Interval[1,2].width)
|
186
|
+
tester = proc{|x|
|
187
|
+
assert_equal(
|
188
|
+
2.0 ** (-(Math.log(x)/Math.log(2)).ceil - 52),
|
189
|
+
(1/Interval[x]).width)
|
190
|
+
}
|
191
|
+
tester.call(3.0)
|
192
|
+
tester.call(5.0)
|
193
|
+
tester.call(119.0)
|
194
|
+
tester.call(34e-4)
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
# Tests Interval algebra.
|
200
|
+
class Interval::TestAlgebra < Test::Unit::TestCase
|
201
|
+
|
202
|
+
def test_union
|
203
|
+
assert_equal(Interval[[1, 6], [9, 9]], Interval[[1, 3],[4, 6]] | Interval[[2, 5], [9,9]])
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_plus
|
207
|
+
tester = proc{|x,y,sum|
|
208
|
+
assert_equal(sum,x+y)
|
209
|
+
assert_equal(sum,y+x)
|
210
|
+
}
|
211
|
+
tester.call(Interval[[1,2],[10,Infinity]], Interval[[1,9],[-2,-1]], Interval[[-1,1],[2,Infinity]])
|
212
|
+
tester.call(Interval[1,9], Interval[[1,2],[10,Infinity]], Interval[2,Infinity])
|
213
|
+
tester.call(Interval[1,2], 2, Interval[3,4])
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_intersection
|
217
|
+
tester = proc{|x,y,z|
|
218
|
+
assert_equal(z, x & y)
|
219
|
+
assert_equal(z, y & x)
|
220
|
+
}
|
221
|
+
tester.call(Interval[1,2], Interval[0,3], Interval[1,2])
|
222
|
+
tester.call(Interval[1.1,1.9], Interval[1.3,2.5], Interval[1.3, 1.9])
|
223
|
+
tester.call(Interval[1.1,1.9], Interval[0.3,0.7], Interval[])
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_inverse
|
227
|
+
assert_equal(Interval[[-Infinity, -2.0],[0.0, Infinity]],Interval[[-0.5,0.5],[0.2,Infinity]].inverse)
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_minus
|
231
|
+
assert_equal(Interval[[-4,-3],[-2,-1]], -Interval[[1,2],[3,4]])
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_times
|
235
|
+
tester = proc{|x,y,z|
|
236
|
+
assert_equal(z,x*y)
|
237
|
+
assert_equal(z,y*x)
|
238
|
+
}
|
239
|
+
tester.call(Interval[[1, 2], [3, 4]], Interval[0.5, 2], Interval[0.5,8])
|
240
|
+
tester.call(Interval[1,2], 2, Interval[2,4])
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_division
|
244
|
+
assert_equal(Interval[-Infinity, Infinity], Interval[0,1]/Interval[0,1])
|
245
|
+
assert_equal(Interval[0.5], Interval[1]/2)
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_power
|
249
|
+
assert_equal(Interval[-1,2], (Interval[-1,2]**-1)**-1)
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
#Tests other Interval methods.
|
255
|
+
class Interval::TestMethods < Test::Unit::TestCase
|
256
|
+
include Interval::Assertions
|
257
|
+
|
258
|
+
def test_include?
|
259
|
+
assert_include(Interval[1,2], 1.5)
|
260
|
+
assert_include(Interval[1,2], 1)
|
261
|
+
assert_include(Interval[1,2], 2)
|
262
|
+
assert_outside(Interval[1,2], 0)
|
263
|
+
assert_outside(Interval[1,2], 4)
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_simple?
|
267
|
+
assert(Interval[1,2].simple?)
|
268
|
+
assert(!Interval[[1,2],[3,4]].simple?)
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_empty?
|
272
|
+
assert(!Interval[1,2].empty?)
|
273
|
+
assert(!Interval[[1,2],[4,3]].empty?)
|
274
|
+
assert(Interval[].empty?)
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_sharp?
|
278
|
+
assert_sharp(Interval[5,5])
|
279
|
+
assert_sharp(Interval[[1],[2]])
|
280
|
+
assert_fuzzy(Interval[[1],[2,3]])
|
281
|
+
assert_fuzzy(Interval[5,6])
|
282
|
+
assert_fuzzy(Interval[1.1,1.2])
|
283
|
+
assert_fuzzy(Interval[Struct::Float[-1,0,1].to_f,Struct::Float[+1,0,1].to_f])
|
284
|
+
assert_sharp(Interval[Struct::Float[1,1022,2**52-1].to_f,1])
|
285
|
+
assert_sharp(Interval[Struct::Float[1,1029,2**52-1].to_f,128])
|
286
|
+
assert_sharp(Interval[-0.0,0.0])
|
287
|
+
assert_sharp(Interval[0.0,Struct::Float[1,0,1].to_f])
|
288
|
+
assert_sharp(Interval[6369051672525772, 6369051672525773] * 2.0 ** -52)
|
289
|
+
assert_sharp(Interval[6369051672525772, 6369051672525772] * 2.0 ** -52)
|
290
|
+
assert_fuzzy(Interval[6369051672525771, 6369051672525773] * 2.0 ** -52)
|
291
|
+
assert_fuzzy(Interval[6369051672525771, 6369051672525785] * 2.0 ** -52)
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_hull
|
295
|
+
assert_equal(Interval[1,4], Interval[[1,2],[3,4]].hull)
|
296
|
+
assert_equal(Interval[1,4], Interval[1,4].hull)
|
297
|
+
assert_equal(Interval[], Interval[].hull)
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_conversion_from_range
|
301
|
+
assert_equal(Interval[1.2, 5.3], (1.2 .. 5.3).to_interval)
|
302
|
+
assert_equal(Interval[1, 4], (1 .. 4).to_interval)
|
303
|
+
assert_equal(Interval[1, 3], (1 ... 4).to_interval)
|
304
|
+
assert_raise(Interval::ConstructionError){ (1 ... 3.1).to_interval }
|
305
|
+
if defined?(Rational)
|
306
|
+
x = Rational(1,3)
|
307
|
+
assert_equal(Interval[1, x], (1 .. x).to_interval)
|
308
|
+
assert_raise(Interval::ConstructionError){ (1 ... x).to_interval }
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
|
314
|
+
# Tests the Interval-based non-linear solver.
|
315
|
+
class Interval::TestNewton < Test::Unit::TestCase
|
316
|
+
include Interval::Assertions
|
317
|
+
|
318
|
+
def test_sqrt2
|
319
|
+
f, fp = proc {|x| x**2 - 2}, proc {|x| 2*x}
|
320
|
+
s = Math::sqrt(2)
|
321
|
+
assert_equal(6369051672525773 * 2.0 **-52, s)
|
322
|
+
assert_equal(Interval[s], Interval[0.1,5.0].newton(f, fp))
|
323
|
+
assert_equal(Interval[], Interval[2.0,5.0].newton(f,fp))
|
324
|
+
assert_equal(Interval[s], Interval[-1.0,10.0].newton(f,fp))
|
325
|
+
assert_equal(Interval[-s], Interval[-5.0,0].newton(f,fp))
|
326
|
+
assert_equal(Interval[[-s],[s]], Interval[-5.0,5.0].newton(f,fp))
|
327
|
+
end
|
328
|
+
|
329
|
+
def test_cubic_1
|
330
|
+
f, fp = proc {|x| x**3 + x - 10}, proc {|x| 3*x**2 + 1}
|
331
|
+
s = Interval[-1,5].newton(f,fp)
|
332
|
+
assert_solution(s, f, Interval[2])
|
333
|
+
end
|
334
|
+
|
335
|
+
def test_cubic_2
|
336
|
+
f, fp = proc {|x| x**3 + x - 15}, proc {|x| 3*x**2 + 1}
|
337
|
+
s = Interval[-1.0,5.0].newton(f,fp)
|
338
|
+
assert_solution(s, f, Interval[5249383869325654 * 2.0 ** -51])
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_cubic_3
|
342
|
+
f, fp = proc {|x| x * (x ** 2 - 1)}, proc {|x| 3*x**2 - 1}
|
343
|
+
s = Interval[-5.0,5.0].newton(f,fp)
|
344
|
+
assert_solution(s, f, Interval[[-1],[0],[1]])
|
345
|
+
end
|
346
|
+
|
347
|
+
def test_dennis_schnabel
|
348
|
+
f1 = proc {|x| (((x-12) * x + 47 ) * x - 60 )* x}
|
349
|
+
f2 = proc {|x| f1.call(x) + 24 }
|
350
|
+
f3 = proc {|x| f1.call(x) + 24.1 }
|
351
|
+
fp = proc {|x| ((4 * x - 12*3) * x + 47*2) * x - 60}
|
352
|
+
|
353
|
+
assert_solution(Interval[-1e2,1e2].newton(f1,fp), f1, Interval[[0],[3],[4],[5]],:weak)
|
354
|
+
assert_solution(Interval[-1e2,1e2].newton(f2,fp), f2, Interval[[0.888305779071752],[1]], :weak)
|
355
|
+
assert_equal(Interval[], Interval[-1e2,1e2].newton(f3,fp))
|
356
|
+
end
|
357
|
+
|
358
|
+
if FPU.respond_to?(:exp_up)
|
359
|
+
def test_exp
|
360
|
+
f, fp = proc{|x| x.exp + x}, proc {|x| x.exp + 1}
|
361
|
+
z0 = Interval[-1e2,1e2].newton(f,fp)
|
362
|
+
assert_solution(z0, f, Interval[-0.56714329040978384])
|
363
|
+
|
364
|
+
f, fp = proc{|x| x*(-x).exp + 1}, proc {|x| (1-x)*(-x).exp}
|
365
|
+
z1 = Interval[-1e2,1e2].newton(f,fp)
|
366
|
+
assert_solution(z0, f, z0, :weak)
|
367
|
+
assert_sharp(z1)
|
368
|
+
end end
|
369
|
+
|
370
|
+
if FPU.respond_to?(:exp_up)
|
371
|
+
def test_trig
|
372
|
+
f = proc{|x| (x*Interval::PI/3.0).cos() -0.5}
|
373
|
+
fp = proc{|x| -(x*Interval::PI/3).sin() * Interval::PI/3}
|
374
|
+
s = Interval[-10.0,10.0].newton(f, fp)
|
375
|
+
assert_solution(s, f, Interval[[-7], [-5], [-1], [1], [5], [7]], :weak)
|
376
|
+
end end
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
if FPU.respond_to?(:exp_up)
|
381
|
+
# Tests Interval transcendental functions.
|
382
|
+
class Interval::TestTranscendental < Test::Unit::TestCase
|
383
|
+
include Interval::Assertions
|
384
|
+
|
385
|
+
def test_exp
|
386
|
+
assert_equal(Interval::E, Interval[1].exp)
|
387
|
+
assert_equal(Interval[1,Interval::E.sup], Interval[0,1].exp)
|
388
|
+
assert_equal(Interval[Interval::E.inf, Infinity], Interval[1,Infinity].exp)
|
389
|
+
assert_equal(Interval[0], Interval[-Infinity].exp)
|
390
|
+
assert_equal(Interval[[0, 1], [Math::E, Infinity]], Interval[[-Infinity,0],[1,Infinity]].exp)
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_log
|
394
|
+
assert_equal(Interval[-Infinity, 0], Interval[-1,+1].log)
|
395
|
+
assert_equal(Interval[0], Interval[1].log)
|
396
|
+
assert_equal(Interval[], Interval[-2,-1].log)
|
397
|
+
assert_equal(Interval[-Infinity], Interval[-2,0].log)
|
398
|
+
assert_equal(Interval[-Infinity,0], Interval[0,1].log)
|
399
|
+
assert_include(Interval::E.log, 1)
|
400
|
+
assert_equal(3 * 2 ** -53, Interval[1].exp.log.width)
|
401
|
+
end
|
402
|
+
|
403
|
+
def test_atan
|
404
|
+
assert_equal(Interval::PI, 4 * Interval[1].atan)
|
405
|
+
assert_equal(Interval[-Interval::PI.sup, Interval::PI.sup], 2 * Interval[-Infinity,Infinity].atan)
|
406
|
+
assert_equal(Interval[0], Interval[0].atan)
|
407
|
+
end
|
408
|
+
|
409
|
+
def test_cosh
|
410
|
+
assert_sharp(Interval[1].cosh)
|
411
|
+
assert_equal(Interval[1], Interval[0].cosh)
|
412
|
+
assert_equal((Interval[1].cosh | Interval[3].cosh).hull, Interval[1,3].cosh)
|
413
|
+
assert_equal(Interval[1,3].cosh, Interval[-3,-1].cosh)
|
414
|
+
assert_equal((Interval[0] | Interval[3].cosh).hull, Interval[-1,3].cosh)
|
415
|
+
assert_equal(Interval[-1,3].cosh, Interval[-3,1].cosh)
|
416
|
+
assert_equal(Interval[0, Infinity], Interval[-Infinity,Infinity].cosh)
|
417
|
+
end
|
418
|
+
|
419
|
+
def test_sinh
|
420
|
+
assert_sharp(Interval[1].sinh)
|
421
|
+
assert_equal(Interval[0], Interval[0].sinh)
|
422
|
+
assert_equal((Interval[-1].sinh | Interval[3].sinh).hull, Interval[-1,3].sinh)
|
423
|
+
assert_equal(Interval[-Infinity, Infinity], Interval[-Infinity,Infinity].sinh)
|
424
|
+
end
|
425
|
+
|
426
|
+
def test_cos
|
427
|
+
assert_equal(Interval[-1,-1 + 2 ** -53], Interval::PI.cos)
|
428
|
+
assert_equal(Interval[1 - 2 ** -53, 1], (2*Interval::PI).cos)
|
429
|
+
assert_equal(Interval[-1,1], Interval[Infinity].cos)
|
430
|
+
assert_equal(Interval[-1,1], Interval[-Infinity].cos)
|
431
|
+
onehalf = (Interval::PI/3).cos
|
432
|
+
assert_include(onehalf, 0.5)
|
433
|
+
assert_equal(Interval[onehalf.inf,1], (-Interval::PI/4 | Interval::PI/3).hull.cos)
|
434
|
+
minusonehalf = (2*Interval::PI/3).cos
|
435
|
+
assert_include(minusonehalf, -0.5)
|
436
|
+
assert_equal(Interval[-1,minusonehalf.sup], (5*Interval::PI/4 | 2*Interval::PI/3).hull.cos)
|
437
|
+
assert_equal((minusonehalf | onehalf).hull, ((Interval::PI | 2*Interval::PI).hull/3).cos)
|
438
|
+
full = (5 * Interval::PI/3 | 3.5 * Interval::PI).hull
|
439
|
+
assert_equal(Interval[-1,1], full.cos)
|
440
|
+
assert_equal(Interval[-1,1], (full + 2 * Interval::PI).cos)
|
441
|
+
assert_equal(Interval[-1,1], (full + 4 * Interval::PI).cos)
|
442
|
+
assert_equal(Interval[-1,1], (full + 6 * Interval::PI).cos)
|
443
|
+
assert_equal(Interval[-1,1], (full + 8 * Interval::PI).cos)
|
444
|
+
assert_equal(Interval[-1,1], (full - 2 * Interval::PI).cos)
|
445
|
+
assert_equal(Interval[-1,1], (full - 4 * Interval::PI).cos)
|
446
|
+
assert_equal(Interval[-1,1], (full - 6 * Interval::PI).cos)
|
447
|
+
assert_equal(Interval[-1,1], (full - 8 * Interval::PI).cos)
|
448
|
+
end
|
449
|
+
|
450
|
+
def test_sin
|
451
|
+
assert_equal(Interval[-1,-1 + 2 ** -53], (3*Interval::PI/2).sin)
|
452
|
+
assert_equal(Interval[1 - 2 ** -53, 1], (Interval::PI/2).sin)
|
453
|
+
assert_equal(Interval[-1,1], Interval[Infinity].sin)
|
454
|
+
assert_equal(Interval[-1,1], Interval[-Infinity].sin)
|
455
|
+
onehalf = (Interval::PI/6).sin
|
456
|
+
assert_include(onehalf, 0.5)
|
457
|
+
assert_equal(Interval[onehalf.inf,1], (3 * Interval::PI/4 | Interval::PI/6).hull.sin)
|
458
|
+
minusonehalf = (7*Interval::PI/6).sin
|
459
|
+
assert_include(minusonehalf, -0.5)
|
460
|
+
assert_equal(Interval[-1,minusonehalf.sup], (7*Interval::PI/4 | 7*Interval::PI/6).hull.sin)
|
461
|
+
assert_equal((-onehalf | onehalf).hull, ((Interval::PI | -Interval::PI).hull/6.0).sin)
|
462
|
+
full = ((5 * Interval::PI/3 | 3.5 * Interval::PI) + Interval::PI/2).hull
|
463
|
+
assert_equal(Interval[-1,1], full.sin)
|
464
|
+
assert_equal(Interval[-1,1], (full + 2 * Interval::PI).cos)
|
465
|
+
assert_equal(Interval[-1,1], (full + 4 * Interval::PI).cos)
|
466
|
+
assert_equal(Interval[-1,1], (full + 6 * Interval::PI).cos)
|
467
|
+
assert_equal(Interval[-1,1], (full + 8 * Interval::PI).cos)
|
468
|
+
assert_equal(Interval[-1,1], (full - 2 * Interval::PI).cos)
|
469
|
+
assert_equal(Interval[-1,1], (full - 4 * Interval::PI).cos)
|
470
|
+
assert_equal(Interval[-1,1], (full - 6 * Interval::PI).cos)
|
471
|
+
assert_equal(Interval[-1,1], (full - 8 * Interval::PI).cos)
|
472
|
+
end
|
473
|
+
|
474
|
+
def test_tan
|
475
|
+
assert_equal(Interval[0], Interval[0].tan)
|
476
|
+
assert_equal(Interval[], Interval[].tan)
|
477
|
+
assert_equal(Interval[-Infinity, Infinity], Interval[0,6].tan)
|
478
|
+
assert_equal(Interval[-Infinity, Infinity], (-Interval::PI/2 | Interval::PI/2).hull.tan)
|
479
|
+
assert_equal(Interval[-Infinity, Infinity], (- Interval::PI/4 | Interval::PI * 1.25).hull.tan)
|
480
|
+
assert_equal(Interval[-Infinity, Infinity], (4.75 *Interval::PI | Interval::PI * 6.25).hull.tan)
|
481
|
+
|
482
|
+
assert_equal(2 ** -51, Interval::PI.tan.width)
|
483
|
+
assert_include(Interval::PI.tan, 0)
|
484
|
+
assert_equal(3 * 2 ** -53, (Interval::PI/4).tan.width)
|
485
|
+
assert_include((Interval::PI/4).tan, 1)
|
486
|
+
|
487
|
+
x = (Interval::PI/4 | Interval::PI * 0.75).hull
|
488
|
+
assert_equal((Interval[x.sup].tan | -Infinity).hull | (Interval[x.inf].tan | Infinity).hull, x.tan)
|
489
|
+
|
490
|
+
x = (Interval::PI | -Interval::PI).hull/4
|
491
|
+
assert_equal((Interval[x.inf].tan | Interval[x.sup].tan).hull, x.tan)
|
492
|
+
end
|
493
|
+
|
494
|
+
end end
|
495
|
+
|
496
|
+
# Tests Kahan's summation formula
|
497
|
+
class Interval::KahanTest < Test::Unit::TestCase
|
498
|
+
include Interval::Assertions
|
499
|
+
|
500
|
+
# Generate a random data vector
|
501
|
+
#
|
502
|
+
# data = Kahan_Test.gen_data(100)
|
503
|
+
#
|
504
|
+
def gen_data(n)
|
505
|
+
(0...n).map {rand - 0.5}
|
506
|
+
end
|
507
|
+
|
508
|
+
class PoorMansRational < Numeric
|
509
|
+
attr :num
|
510
|
+
attr :den
|
511
|
+
|
512
|
+
def initialize(n, d)
|
513
|
+
if d < 0
|
514
|
+
n, d = -n, -d
|
515
|
+
end
|
516
|
+
b, a = [n.abs,d].sort
|
517
|
+
while b > 0
|
518
|
+
a, b = b, a % b
|
519
|
+
end
|
520
|
+
@num = n.div(a)
|
521
|
+
@den = d.div(a)
|
522
|
+
end
|
523
|
+
|
524
|
+
def + (other)
|
525
|
+
case other
|
526
|
+
when self.class
|
527
|
+
self.class.new(num * other.den + den * other.num, den * other.den)
|
528
|
+
when Integer
|
529
|
+
self.class.new(num + den * other, den)
|
530
|
+
else
|
531
|
+
x, y = other.coerce(self)
|
532
|
+
x + y
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
def coerce(other)
|
537
|
+
case other
|
538
|
+
when self.class
|
539
|
+
return other, self
|
540
|
+
when Integer
|
541
|
+
return self.class.new(other, 1), self
|
542
|
+
else
|
543
|
+
super
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
def to_f
|
548
|
+
@num.to_f/@den.to_f
|
549
|
+
end
|
550
|
+
|
551
|
+
end
|
552
|
+
|
553
|
+
unless defined?(Rational)
|
554
|
+
def Rational(n, d = 1)
|
555
|
+
PoorMansRational.new(n,d)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
# Convert to a rational number with no precision loss
|
560
|
+
#
|
561
|
+
# to_r(1.1).to_f == 1.1 # => true
|
562
|
+
#
|
563
|
+
def to_r(x)
|
564
|
+
if !x.finite?
|
565
|
+
self
|
566
|
+
elsif x.zero?
|
567
|
+
0
|
568
|
+
else
|
569
|
+
exp = [Math.frexp(x).last - 53, -1074].max
|
570
|
+
sig = (x / (2.0 ** exp)).to_i
|
571
|
+
if exp >= 0
|
572
|
+
Rational(sig * 2 ** exp)
|
573
|
+
else
|
574
|
+
Rational(sig, 2 ** (-exp))
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
def assert_consistent_sum(raw, strict = nil)
|
580
|
+
# The exact sum, with rationals
|
581
|
+
exact = raw.inject(0){|a,x| a+to_r(x)}.to_f
|
582
|
+
|
583
|
+
# Summing them as floats
|
584
|
+
rawsum = raw.inject{|a,x| a+x}
|
585
|
+
|
586
|
+
# Data as a collection of intervals
|
587
|
+
data = raw.map{|x| Interval[x]}
|
588
|
+
|
589
|
+
# Kahan's sum
|
590
|
+
kahan = Interval.sum(*data)
|
591
|
+
|
592
|
+
# Naive sum
|
593
|
+
naive = data.inject{|a,x| a+x}
|
594
|
+
|
595
|
+
# Kahan must include the exact sum
|
596
|
+
assert_include(kahan, exact)
|
597
|
+
|
598
|
+
# In some cases the raw sum is outside Kahan's result
|
599
|
+
assert_outside(kahan, rawsum) if strict
|
600
|
+
|
601
|
+
# Kahan's result must be sharper than the naive sum
|
602
|
+
assert_subset(kahan, naive)
|
603
|
+
|
604
|
+
#puts [rawsum, exact, kahan.width, naive.width, naive.width/kahan.width].
|
605
|
+
# inspect if @@long && ! kahan.include?(rawsum)
|
606
|
+
end
|
607
|
+
|
608
|
+
def test_basic_consistency
|
609
|
+
assert_equal(Interval[3], Interval.sum(Interval[1], Interval[2]))
|
610
|
+
assert_equal(Interval[[3],[6]], Interval.sum(Interval[[1],[4]], Interval[2]))
|
611
|
+
assert_equal(Interval[-2,14],Interval.sum(Interval[[1,9],[-2,-1]], Interval[[0,3],[1,5]]))
|
612
|
+
assert_equal(Interval[[-22, -20], [-19, -10], [-2, 0], [1, 10]],
|
613
|
+
Interval.sum(Interval[[1,9],[-2,-1]], Interval[[0,1],[-20,-19]]))
|
614
|
+
end
|
615
|
+
|
616
|
+
@@long = ARGV.include?("long")
|
617
|
+
|
618
|
+
def test_large_data
|
619
|
+
assert_consistent_sum [0.694758884739873,
|
620
|
+
0.604772180759511,
|
621
|
+
0.796647997261382,
|
622
|
+
0.102320271311194,
|
623
|
+
0.87286348648478,
|
624
|
+
0.175213643903808,
|
625
|
+
0.82310391214613,
|
626
|
+
0.198147944349148,
|
627
|
+
0.24650422376045,
|
628
|
+
0.471233184991935].map{|x| x-0.5}, :strict
|
629
|
+
srand 12345
|
630
|
+
assert_consistent_sum gen_data(200), :strict
|
631
|
+
if @@long
|
632
|
+
assert_consistent_sum gen_data(2000), :strict
|
633
|
+
assert_consistent_sum gen_data(100)
|
634
|
+
assert_consistent_sum gen_data(100)
|
635
|
+
assert_consistent_sum gen_data(100)
|
636
|
+
assert_consistent_sum gen_data(100)
|
637
|
+
assert_consistent_sum gen_data(100)
|
638
|
+
assert_consistent_sum gen_data(100)
|
639
|
+
assert_consistent_sum gen_data(100)
|
640
|
+
assert_consistent_sum gen_data(1000)
|
641
|
+
assert_consistent_sum gen_data(10000)
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
end
|