Check out the following gist: http://gist.github.com/173975 It''s from a Test-Driven Intro To Ruby class I''m working on. Looks good, right? Not so fast. Check out this spec: it "converts body temperature" do t = @temperature.ctof(37) (t*10).round.should == 986 end Why do we have to round? Cause floating-point math is messed up. If we just follow the pattern and do @temperature.ctof(37).should == 98.6 then the spec fails. Buh? See http://www.ruby-forum.com/topic/169330, especially response http://www.ruby-forum.com/topic/169330#742994 for more detail. Without opening up the whole debate over whether it''s "correct" in some language-puritan way, I''d like to propose that the standard RSpec == matcher be rewritten to compare floats only to a certain degree of precision... say, 10 decimal places. That could be augmented by a new "equals_precisely" matcher that does the current == behavior for floats. Why do it that way and not just make a new "equals_approximately" matcher and use it for cases like the above? Because I submit that >99 times out of 100 the expected behavior is for float comparison to match the rules of regular math, not floating point math. The ftoc/ctof example is a good case of this: ftoc(98.6) == 37 but ctof(37) == 98.600000000000008526512829121202. But if I do a puts or an inspect of that float, it says "98.6" so it''s very difficult for someone who doesn''t know any better (i.e. 90% of Ruby programmers and 99% of Ruby students) to figure out what to do. But if you *do* happen to be doing complicated floating point math then you''ll probably know enough to use "equals_approximately", or at least be able to figure that out when your ultra-precise spec starts failing.>> Temperature.new.ctof(37)=> 98.6>> Temperature.new.ctof(37) == 98.6=> false>> "%.30f" % Temperature.new.ctof(37)=> "98.600000000000008526512829121202" Thoughts? --- Alex Chaffee - alex at stinky.com - http://alexch.github.com Stalk me: http://friendfeed.com/alexch | http://twitter.com/alexch | http://alexch.tumblr.com
...and, while I was composing that message, Brian sent me the following:> Rspec does provide the be_close matcher. See cheat rspec.@temperature.ctof(37).should be_close(98.6, 0.1) This will work (I''ll go update the code now) but it still leaves the problem I mentioned that if you are unfamiliar with the vagaries of floating point math -- or even if you momentarily forget -- then using == will occasionally mysteriously fail. So my proposal remains: can the == matcher do be_close(x, 0.0000000001) for floats? Arguments pro and con? --- Alex Chaffee - alex at stinky.com - http://alexch.github.com Stalk me: http://friendfeed.com/alexch | http://twitter.com/alexch | http://alexch.tumblr.com
On 24 Aug 2009, at 18:56, Alex Chaffee wrote:> == will occasionally mysteriously fail. So my proposal remains: can > the == matcher do be_close(x, 0.0000000001) for floats? Arguments pro > and con?The "problem" you describe is with Ruby''s == operator, not with RSpec. The == matcher must agree with the semantics of Ruby''s == operator in order to do its job. You wouldn''t want the expectation "x.should == 42.0" to pass if x == 42.0 was false (for any reason). Cheers, -Tom
On Mon, Aug 24, 2009 at 12:56 PM, Alex Chaffee<alexch at gmail.com> wrote:> ...and, while I was composing that message, Brian sent me the following: > >> Rspec does provide the be_close matcher. See cheat rspec. > > @temperature.ctof(37).should be_close(98.6, 0.1) > > This will work (I''ll go update the code now) but it still leaves the > problem I mentioned that if you are unfamiliar with the vagaries of > floating point math -- or even if you momentarily forget -- then using > == will occasionally mysteriously fail. So my proposal remains: can > the == matcher do be_close(x, 0.0000000001) for floats? Arguments pro > and con?What about a helpful error message when "should ==" fails on floats: expected 98.6, got 98.6 The expected and actual may appear to be the same due to Ruby''s string representation of floating point numbers. For floating point math, we recommend using the be_close() matcher instead. ???> > --- > Alex Chaffee - alex at stinky.com - http://alexch.github.com > Stalk me: http://friendfeed.com/alexch | http://twitter.com/alexch | > http://alexch.tumblr.com
On Mon, 24 Aug 2009 10:56:00 -0700, you wrote:>This will work (I''ll go update the code now) but it still leaves the >problem I mentioned that if you are unfamiliar with the vagaries of >floating point math -- or even if you momentarily forget -- then using >== will occasionally mysteriously fail. So my proposal remains: can >the == matcher do be_close(x, 0.0000000001) for floats? Arguments pro >and con?If you are unfamiliar with the vagaries of floating-point math, then you need to get familiar, or else avoid using floating-point math. The world is already littered with software that doesn''t work properly because the programmers didn''t understand floating-point math. Perpetuating that kind of ignorance will not make the world a better place. Floating-point math is _inherently_ inexact. Back in my programmer support days, I used to have a standing bet, which I will reiterate now: Show me a technique that you claim "fixes" an inexactness problem with floating-point math. I bet you US$100 that I can supply a trivial example that breaks your fix. -Steve
> What about a helpful error message when "should ==" fails on floats: > > expected 98.6, got 98.6 > > The expected and actual may appear to be the same due to Ruby''s string > representation of floating point numbers. For floating point math, we > recommend using the be_close() matcher instead. > > ???!!! That''s a great idea. Even better would be that it only shows up when the to_s values are the same, so it doesn''t appear for a real failure. - A