Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a81f4d1

Browse files
committedJan 27, 2012
Version 1.0.2 -- improved Ft implementation and testing
* Improved the implementation and testing of float equality (Ft). There is a backwards-incompatible change in that Ft no longer accepts an epsilon argument. If Ft doesn't work for you with the built-in value (1e-13) then it's probably a bug in this library. Theoretically, this should be version 2.0.0, but that seems silly and I don't think anyone will be affected. Sorry if you are.
1 parent a9f0312 commit a81f4d1

File tree

6 files changed

+187
-39
lines changed

6 files changed

+187
-39
lines changed
 

‎Announcements.txt

+35
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
1+
2012-01-27 ruby-talk
2+
3+
Whitestone 1.0.1, a unit testing library, was released on 2012-01-02. This
4+
update, 1.0.2, improves the implementation of float-equality testing,
5+
especially regarding numbers near zero. As it stands, floats are considered
6+
equal if their 14th significant figure is off by one. There is no provision
7+
for specifying a tolerance: it is supposed to just work, and if it doesn't
8+
then it's a bug.
9+
10+
Key features of Whitestone include terse testing code, colorful and
11+
helpful output, custom assertions, a powerful and intuitive test
12+
runner, simple debugger integration. The homepage explains and
13+
demonstrates it well.
14+
15+
Here are the assertion methods:
16+
T -- assert true
17+
F -- assert false
18+
N -- assert object is nil
19+
Eq -- assert two objects are equal
20+
Mt -- assert string matches regular expression
21+
Id -- assert two objects are identical (same object)
22+
E -- assert error is raised
23+
Ko -- assert an object is kind_of a class/module
24+
Ft -- assert two floats are essentially equal
25+
C -- assert object is thrown
26+
27+
Homepage: http://gsinclair.github.com/whitestone.html
28+
Code: http://github.com/gsinclair/whitestone
29+
Licence: MIT
30+
31+
Regards,
32+
Gavin
33+
34+
-------------------------------------------------------------------------------
35+
136
2012-01-02 ruby-talk
237

338
Hi all,

‎History.txt

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
=== 1.0.2 / 2012-01-27
2+
3+
* Improved the implementation and testing of float equality (Ft).
4+
There is a backwards-incompatible change in that Ft no longer
5+
accepts an epsilon argument. If Ft doesn't work for you with
6+
the built-in value (1e-13) then it's probably a bug in this
7+
library. Theoretically, this should be version 2.0.0, but that
8+
seems silly and I don't think anyone will be affected. Sorry
9+
if you are.
10+
11+
* Correct Ruby dependency set in the gemspec (>= 1.8.7).
12+
113
=== 1.0.1 / 2012-01-02
214

315
* Correction in README.txt

‎doc/whitestone.markdown

+3
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,9 @@ They are hardcoded and it would be a major effort to customise them!
797797
* July 2010: originally developed under the name 'attest' but not released
798798
* 2 JAN 2012: version 1.0.0
799799
* 2 JAN 2012: version 1.0.1 with corrected README.txt
800+
* 27 JAN 2012: version 1.0.2 with improved implementation and testing of float
801+
equality (Ft). Note slight backwards incompatibility: Ft no longer accepts
802+
'epsilon' argument.
800803

801804
### Future plans
802805

‎lib/whitestone/assertion_classes.rb

+39-12
Original file line numberDiff line numberDiff line change
@@ -263,33 +263,60 @@ def message
263263
end # class Assertion::KindOf
264264

265265
class FloatEqual < Base
266-
EPSILON = 0.000001
266+
# Experiments have shown this value to be a reliable threshold for
267+
# ratio-based comparison, but that may be machine-dependant and may be
268+
# overturned by more experiments. With this epsilon, floats appear to be
269+
# considered equal if their first 13 significant figures are the same.
270+
#
271+
# Example: 1.1 - 1.0 gives 0.10000000000000009, which differs from 0.1 in
272+
# the 17th digit. Therefore, I could, and perhaps should, be more
273+
# aggressive and set an even smaller ratio like 1e-16. But I assume that
274+
# complicated calculations involving floats would compound representation
275+
# errors, so at the moment I choose to be conservative.
276+
#
277+
# It is by design that the programmer cannot specify a value for epsilon.
278+
# This should "just work", and if someone finds a case where this epsilon
279+
# is not sufficient for normal use, it is probably a bug in this library
280+
# that needs to be addressed. If someone wants to experiment with
281+
# different epsilon values, then they can do, for example
282+
# WhiteStone::Assertion::FloatEqual.const_set :EPSILON, 1e-16
283+
EPSILON = 1e-13
267284
def initialize(mode, *args, &block)
268285
super
269286
no_block_allowed
270287
type_check(args, Numeric)
271-
@actual, @expected, @epsilon = two_or_three_arguments(args).map { |x| x.to_f }
272-
@epsilon ||= EPSILON
288+
@actual, @expected = two_arguments(args).map { |x| x.to_f }
289+
@epsilon = EPSILON
273290
end
274291
def run
275-
if @actual.zero? or @expected.zero?
276-
# There's no scale, so we can only go on difference.
277-
(@actual - @expected) < @epsilon
292+
if @actual.zero? and @expected.zero?
293+
true
294+
elsif @actual.zero? or @expected.zero?
295+
# Precisely one of our values is zero. Ratios don't work in this case,
296+
# so we work around by adding 0.01 to both to get them away from zero.
297+
# We check first to be sure that this would actually work.
298+
if (@actual - @expected).abs < 0.00001
299+
floats_essentially_equal?(@actual + 0.01, @expected + 0.01)
300+
else
301+
# They differ by more than 0.00001 so they're clearly not equal enough.
302+
false
303+
end
278304
else
279-
# We go by ratio. The ratio of two equal numbers is one, so the ratio
280-
# of two practically-equal floats will be very nearly one.
281-
@ratio = (@actual/@expected - 1).abs
282-
@ratio < @epsilon
305+
floats_essentially_equal?(@actual, @expected)
283306
end
284307
end
308+
def floats_essentially_equal?(a, b)
309+
@ratio = (a/b - 1).abs
310+
@ratio < EPSILON
311+
end
285312
def message
286313
String.new.tap { |str|
287314
case @mode
288315
when :assert
289316
str << Col["Float equality test failed"].yb
290317
str << "\n" << Col[" Should be: #{@expected.inspect}"].gb
291318
str << "\n" << Col[" Was: #{@actual.inspect}"].rb
292-
str << "\n" << " Epsilon: #{@epsilon}"
319+
str << "\n" << " Epsilon: #{EPSILON}"
293320
if @ratio
294321
str << "\n" << " Ratio: #{@ratio}"
295322
end
@@ -298,7 +325,7 @@ def message
298325
str << Col[line].yb
299326
str << "\n" << Col[" Value 1: ", @actual.inspect ].fmt(:yb, :rb)
300327
str << "\n" << Col[" Value 2: ", @expected.inspect].fmt(:yb, :rb)
301-
str << "\n" << " Epsilon: #{@epsilon}"
328+
str << "\n" << " Epsilon: #{EPSILON}"
302329
if @ratio
303330
str << "\n" << " Ratio: #{@ratio}"
304331
end

‎lib/whitestone/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Whitestone
2-
VERSION = "1.0.2.pre"
2+
VERSION = "1.0.2"
33
end

‎test/whitestone_test.rb

+97-26
Original file line numberDiff line numberDiff line change
@@ -122,28 +122,41 @@
122122
end
123123

124124
D 'Ft' do
125-
Ft Math::PI, 3.141592 # default tolerance 0.00001
126-
Ft! Math::PI, 3.14
127-
Ft Math::PI, 3.14, 0.1 # tolerance for this line is 0.1
128-
Ft Math::PI, 3.14, 0.01
129-
Ft Math::PI, 3.14, 0.001
130-
Ft! Math::PI, 3.14, 0.0001
125+
D 'basic' do
126+
Ft 45.44332211, 45.443322110000000000001
127+
Ft! 109.123456, 109.123457 # These are too close to be considered
128+
# essentially equal.
129+
end
131130
D 'test values of massively differing magnitude' do
132131
a = 0.000000000837
133132
b = 0.0000000004315 # a and b are _not_ "essentially" equal
134-
c = 100.000000000837
135-
d = 100.0000000004315 # c and d _are_ "essentially" equal
136133
Ft! a, b
137134
Ft! b, a
135+
c = 100.000000000000837
136+
d = 100.0000000000004315 # c and d _are_ "essentially" equal
138137
Ft c, d
139138
Ft d, c
140139
end
140+
D "anomolies like (1.1 - 1.0) does not equal 0.1" do
141+
Ft 1.1 - 1.0, 0.1
142+
Ft 1.1 - 1.0 - 0.1, 0.0
143+
Ft 0.1 + 0.1, 0.2
144+
Ft 0.2 + 0.1, 0.3 # This doesn't work when using ==
145+
Ft 0.3 + 0.1, 0.4
146+
Ft 0.4 + 0.1, 0.5
147+
Ft 0.5 + 0.1, 0.6
148+
Ft 0.6 + 0.1, 0.7
149+
Ft 0.7 + 0.1, 0.8 # Nor does this one
150+
Ft 0.8 + 0.1, 0.9
151+
Ft 0.9 + 0.1, 1.0
152+
end
141153
D 'integer values' do
142154
Ft 4, 4
143155
Ft 4.0, 4
144156
Ft 4, 4.0
145157
Ft -13, -13
146158
Ft -13.0, -13
159+
Ft! 4, 5
147160
end
148161
D 'zero' do
149162
Ft 0, 0
@@ -154,26 +167,84 @@
154167
Ft -1.1102230246251565e-16, 0.0
155168
end
156169
D 'numbers near zero' do
157-
Ft 0, 0.00000000000124, 0.0000000001
158-
Ft 0, 0.00000000000124, 0.00000000001
159-
Ft 0, 0.00000000000124, 0.000000000001
160-
Ft 0, 0.00000000000124, 0.0000000000001
161-
# The next test fails but I don't know what we really should expect.
162-
# Ft! 0, 0.00000000000124, 1e-25
170+
Ft! 0, 0.001
171+
Ft! 0, 0.0001
172+
Ft! 0, 0.00001
173+
Ft! 0, 0.000001
174+
Ft! 0, 0.0000001
175+
Ft! 0, 0.00000001
176+
Ft! 0, 0.000000001
177+
Ft! 0, 0.0000000001
178+
Ft! 0, 0.00000000001
179+
Ft! 0, 0.000000000001
180+
Ft! 0, 0.0000000000001
181+
Ft! 0, 0.00000000000001
182+
Ft 0, 0.000000000000001 # This is the first float that is
183+
Ft 0, 0.0000000000000001 # essentially equal to zero.
184+
Ft 0, 0.00000000000000001
185+
Ft 0, 0.000000000000000001
163186
end
164187
D '(near) equal and negative' do
165-
a = -2.0000051298352
166-
b = -2.0000051298336
167-
Ft a, b, 0.000000001
168-
Ft b, a, 0.000000001
169-
end
170-
D 'tiny numbers' do
171-
Ft 1.234567e-50, 1.234568e-50
172-
Ft! 1.234567e-50, 1.234567e-51
173-
end
174-
D 'huge numbers' do
175-
Ft 1.234567e50, 1.234568e50
176-
Ft! 1.234567e50, 1.234567e51
188+
a = -2.000000000051298352
189+
b = -2.000000000051298336
190+
Ft a, b
191+
Ft b, a
192+
end
193+
D "13th digit differs: not equal enough" do
194+
Ft! 1.234567890123e50, 1.23456789122e50
195+
Ft! 1.234567890123e40, 1.23456789122e40
196+
Ft! 1.234567890123e30, 1.23456789122e30
197+
Ft! 1.234567890123e20, 1.23456789122e20
198+
Ft! 1.234567890123e10, 1.23456789122e10
199+
Ft! 1.234567890123e0, 1.23456789122e0
200+
Ft! 1.234567890123e-10, 1.23456789122e-10
201+
Ft! 1.234567890123e-20, 1.23456789122e-20
202+
Ft! 1.234567890123e-30, 1.23456789122e-30
203+
Ft! 1.234567890123e-40, 1.23456789122e-40
204+
end
205+
D "14th digit differs: equal enough" do
206+
Ft 1.2345678901234e90, 1.2345678901233e90
207+
Ft 1.2345678901234e90, 1.2345678901234e90
208+
Ft 1.2345678901234e90, 1.2345678901235e90
209+
Ft 1.2345678901234e50, 1.2345678901233e50
210+
Ft 1.2345678901234e50, 1.2345678901234e50
211+
Ft 1.2345678901234e50, 1.2345678901235e50
212+
Ft 1.2345678901234e10, 1.2345678901233e10
213+
Ft 1.2345678901234e10, 1.2345678901234e10
214+
Ft 1.2345678901234e10, 1.2345678901235e10
215+
Ft 1.2345678901234e0, 1.2345678901233e0
216+
Ft 1.2345678901234e0, 1.2345678901234e0
217+
Ft 1.2345678901234e0, 1.2345678901235e0
218+
Ft 1.2345678901234e-10, 1.2345678901233e-10
219+
Ft 1.2345678901234e-10, 1.2345678901234e-10
220+
Ft 1.2345678901234e-10, 1.2345678901235e-10
221+
Ft 1.2345678901234e-50, 1.2345678901233e-50
222+
Ft 1.2345678901234e-50, 1.2345678901234e-50
223+
Ft 1.2345678901234e-50, 1.2345678901235e-50
224+
Ft 1.2345678901234e-90, 1.2345678901233e-90
225+
Ft 1.2345678901234e-90, 1.2345678901234e-90
226+
Ft 1.2345678901234e-90, 1.2345678901235e-90
227+
D "but it can only be off by 1" do
228+
Ft! 1.2345678901234e90, 1.2345678901231e90
229+
Ft! 1.2345678901234e90, 1.2345678901232e90
230+
Ft! 1.2345678901234e90, 1.2345678901236e90
231+
Ft! 1.2345678901234e90, 1.2345678901237e90
232+
Ft! 1.2345678901234e10, 1.2345678901231e10
233+
Ft! 1.2345678901234e10, 1.2345678901232e10
234+
Ft! 1.2345678901234e10, 1.2345678901236e10
235+
Ft! 1.2345678901234e10, 1.2345678901237e10
236+
Ft! 1.2345678901234e-10, 1.2345678901231e-10
237+
Ft! 1.2345678901234e-10, 1.2345678901232e-10
238+
Ft! 1.2345678901234e-10, 1.2345678901236e-10
239+
Ft! 1.2345678901234e-10, 1.2345678901237e-10
240+
Ft! 1.2345678901234e-90, 1.2345678901231e-90
241+
Ft! 1.2345678901234e-90, 1.2345678901232e-90
242+
Ft! 1.2345678901234e-90, 1.2345678901236e-90
243+
Ft! 1.2345678901234e-90, 1.2345678901237e-90
244+
end
245+
end
246+
D "Ft does not allow epsilon argument" do
247+
E(AssertionSpecificationError) { Ft? 3.14159265, 3.1415, 0.01 }
177248
end
178249
end
179250

0 commit comments

Comments
 (0)
Please sign in to comment.