intervals 0.3.63 → 0.4.75
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/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
|