Skip to content

Add shouldBeNear expectation#23

Open
erikd wants to merge 3 commits into
hspec:mainfrom
erikd:master
Open

Add shouldBeNear expectation#23
erikd wants to merge 3 commits into
hspec:mainfrom
erikd:master

Conversation

@erikd

@erikd erikd commented Aug 17, 2016

Copy link
Copy Markdown

Comparing floating point numbers for equalty is a bad idea. Instead
they should tested to see if they are close enough to one another.

@erikd

erikd commented Sep 3, 2016

Copy link
Copy Markdown
Author

Ping?

@sol

sol commented Sep 3, 2016

Copy link
Copy Markdown
Member

Hey, sorry for the delay. For this to be useful this would at least need tests and more detailed documentation. Specifically I don't understand

  • why is the first case necessary
  • what happens on division by zero, e.g. when comparing 1e-20 and -1e-20

@erikd

erikd commented Sep 3, 2016

Copy link
Copy Markdown
Author

For this to be useful this would at least need tests

There's no tests listed in the cabal file. I've found the tests in the repo and I'm adding tests, but how do I run them?

why is the first case necessary

This is short circuit evaluation for the case where they are actually equal.

what happens on division by zero, e.g. when comparing 1e-20 and -1e-20

The code for the third case says (abs actual + abs expected) which could only be zero if both values are zero and that would be caught be the second case.

@sol

sol commented Sep 3, 2016

Copy link
Copy Markdown
Member

I use ghci or sensei to run tests

Sent from mobile

On 3 Sep 2016, at 4:53 PM, Erik de Castro Lopo notifications@github.com wrote:

For this to be useful this would at least need tests

There's not tests listed on the cabal file. How do I run them?

why is the first case necessary

This is short circuit evaluation for the case where they are actually equal.

what happens on division by zero, e.g. when comparing 1e-20 and -1e-20

The code for the third case says (abs actual + abs expected) which could only be zero if both values are zero and that would be caught be the second case.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

@erikd erikd mentioned this pull request Sep 4, 2016
@erikd
erikd force-pushed the master branch 2 times, most recently from c5210c1 to f8582f5 Compare September 4, 2016 07:10
@sol

sol commented Sep 6, 2016

Copy link
Copy Markdown
Member

Tests are running on Travis now. Can you rebase on current master and make sure the build is green.

I also wouldn't mind if you remove some of the code duplication, e.g. by factoring common stuff out into a where-clause.

Then I would love to discuss the actual stuff, that is the formula and the chosen value for epsilon.

@erikd
erikd force-pushed the master branch 2 times, most recently from dec8922 to 8e4cc5a Compare September 6, 2016 08:28
@erikd

erikd commented Sep 6, 2016

Copy link
Copy Markdown
Author

Ok, the build has passed.

Since the current code uses guards, its not possible to refactor common code into a where clause, but I could make it a separate top level function (and then not export it).

@sol

sol commented Sep 6, 2016

Copy link
Copy Markdown
Member

I think you can use a where clause with guards, no?

Sent from mobile

On 6 Sep 2016, at 5:03 PM, Erik de Castro Lopo notifications@github.com wrote:

Ok, the build has passed.

Since the current code uses guards, its not possible to refactor common code into a where clause, but I could make it a separate top level function (and then not export it).


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

@erikd

erikd commented Sep 6, 2016

Copy link
Copy Markdown
Author

It works! Fixed, rebased and pushed.

@erikd
erikd force-pushed the master branch 3 times, most recently from e43ce35 to 69bfa94 Compare September 6, 2016 10:09
@erikd

erikd commented Sep 6, 2016

Copy link
Copy Markdown
Author

For some reason I couldn't get one the tests working with ghc 7.10 even though it passed on all the other versions.

Comment thread hspec-expectations.cabal Outdated
src
ghc-options: -Wall
if impl(ghc >= 8.0)
ghc-options: -fno-warn-redundant-constraints

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this on master, so this is no longer necessary.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, dropped that patch.

@erikd
erikd force-pushed the master branch 2 times, most recently from 263f489 to 4c7b60b Compare September 7, 2016 04:25
Comment thread src/Test/Hspec/Expectations.hs Outdated
-- If they actually are exactly equal we're done.
| actual == expected = reportFail (actual == expected)
-- If either input is zero, we check if the absolute difference is less than epsilon..
| actual == 0 || expected == 0 = reportFail (abs (actual - expected) < 1e-15)

@sol sol Sep 7, 2016

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source code comments are an anti-pattern, rather than commenting make your code more expressive, e.g.

otherwise = reportFail (relativeDifference < epsilon)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, that is neater.

Comment thread src/Test/Hspec/Expectations.hs Outdated
| actual == 0 || expected == 0 = reportFail (absDifference < epsilon)
| otherwise = reportFail (relativeDifference < epsilon)
where
epsilon = 1e-15

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for me it's much easier to work with code that does not use abbreviations, the reason for this is that I have to use speech recognition for programming due to RSI. for that reason I would much prefer absoluteDifference here

Comment thread src/Test/Hspec/Expectations.hs Outdated
actual `shouldBeNear` expected
-- Short circuit if they are actually equal.
| actual == expected = reportFail (actual == expected)
| actual == 0 || expected == 0 = reportFail (absoluteDifference < epsilon)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now finally regarding the interesting stuff, the formula, looking at the following example

x `shouldBeNear` 1e-300

I haven't tried, but what happens if x is

  1. 0.9e-300
  2. 0

would (1) fail and (2) pass?

should we always normalize against the expected value instead?

actual `shouldBeNear` expected
  | delta < epsilon = return ()
  | otherwise = actual `shouldBe` expected
  where
    epsilon = 1e-15
    absoluteDifference = abs (expected - actual)
    relativeDifference = absoluteDifference / abs expected
    delta
      | expected == 0 = absoluteDifference
      | otherwise = relativeDifference

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tried, but what happens if x is

  1. 0.9e-300
  2. 0

would (1) fail and (2) pass?

That is what currently happens, and I agree, at first sight, doesn't make much sense, but this is the usual recommended way to compare floating point numbers.

However, now that I think about it,

1e-300 `shouldBeNear` 0.0

should pass (because actual is close to expected), but I think maybe

0.0 `shouldBeNear` 1e-300

should fail.

Let me think about this some more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have encoded my expectations in the tests.

Comparing floating point numbers for equalty is a bad idea. Instead
they should tested to see if they are close enough to one another.
@erikd

erikd commented Sep 15, 2016

Copy link
Copy Markdown
Author

@sol Does this seem ok?

absoluteDifference = abs (expected - actual)
relativeDifference = absoluteDifference / (abs actual + abs expected)
reportFail =
expectTrue ("expected: " ++ show expected ++ "\n but got: " ++ show actual)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on this change BTW!

Wouldn't it be nicer to report things like expected 123 ± 1% but got 125 or similar, as the expectation is of it being within a tolerance of the value not being the value?

Base automatically changed from master to main January 21, 2021 03:48
@istathar

Copy link
Copy Markdown

@erikd is this still a valid approach or have you found a different way to tackle comparing Doubles?

@erikd

erikd commented Mar 16, 2022

Copy link
Copy Markdown
Author

@istathar I stopped using hspec about 6 years ago, in favor of hedgehog. However, I suspect this may still be a valid approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants