<div dir="ltr">Excellent feedback Andres!<div><br></div><div>For my simple use case (really just trying to learn how to implement unit tests in Smalltalk), I changed the code to the following and it works:</div><div><br></div><div><font face="monospace">testCircleArea<br> "tests the VCircle area method"<br> | c |<br> c := VCircle radius: 3.<br> self assert: c area isCloseTo: 28.274333882308138 withPrecision: 1e-15</font><br></div><div><font face="monospace"><br></font></div><div><font face="arial, sans-serif">I'm very new to Smalltalk and always open to feedback!</font></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Jun 4, 2024 at 3:13 PM Andres Valloud via Cuis-dev <<a href="mailto:cuis-dev@lists.cuis.st">cuis-dev@lists.cuis.st</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex">I think checking for a relative error on the order of 10^-4 when double <br>
precision floating point numbers can represent relative errors on the <br>
order of 10^-16 is not good. This means that the result can be off by a <br>
factor of roughly 10^12 and still be "correct".<br>
<br>
Moreover, the implementation is incorrect because if the numbers being <br>
compared are denormals, multiplying by 0.0001 will lead to things like<br>
<br>
nonZero < 0.0<br>
<br>
in the code, which always fails.<br>
<br>
For example,<br>
<br>
good := 10 raisedTo: 16.<br>
bad := good + (10 raisedTo: 12).<br>
[<br>
TestCase new assert: good asFloat isCloseTo: bad asFloat.<br>
#allGood<br>
] on: TestFailure do: [:ex | ex return: #allWrong]<br>
<br>
returns #allGood (but the relative error is huge!). Meanwhile,<br>
<br>
good := 10 raisedTo: 16.<br>
bad := good + (10 raisedTo: 13).<br>
[<br>
TestCase new assert: good asFloat isCloseTo: bad asFloat.<br>
#allGood<br>
] on: TestFailure do: [:ex | ex return: #allWrong]<br>
<br>
returns #allWrong because apparently the results are unacceptable after <br>
losing more than ~75% of the precision available. Finally,<br>
<br>
good := Float fminDenormalized * 10.<br>
bad := good.<br>
[<br>
TestCase new assert: good asFloat isCloseTo: bad asFloat.<br>
#allGood<br>
] on: TestFailure do: [:ex | ex return: #allWrong]<br>
<br>
returns #allWrong even though good = bad.<br>
<br>
This kind of relative error checking makes sense, of course, but only <br>
when you do substantial numerical analysis homework to know what the <br>
tolerable threshold actually is for the application at hand. In <br>
practice this is not done, and arbitrary statements like "0.0001 <br>
relative error is good enough for everyone" effectively suppress <br>
curiosity into how things actually work. On top of that, this threshold <br>
is so lax that it hides basically all but the most egregious errors <br>
under the rug.<br>
<br>
Intel got into a lot of trouble over its Pentium FDIV bug for errors <br>
much smaller than this. The x87 FPU had trigonometric transcendental <br>
instructions that were grossly imprecise in certain cases (e.g. only <br>
about 15 bits of the 53 bit mantissa made sense and the rest were <br>
rubbish) because of poor argument reduction. The modern x86 CPU vector <br>
units do not have hardware transcendental support because you can use <br>
SSE to implement great precision trigonometry with far less effort than <br>
you can do that in hardware, and in addition the process is very fast. <br>
These are the types of problems you ignore when you say things like <br>
"it's within 10^-4", and obviously this is contrary to understanding, <br>
personal mastery, and so on.<br>
<br>
So instead of that, I would ensure that the actual result is a certain, <br>
well understood number of ulps of the expected value. The ulp tolerance <br>
depends on the numerical analysis for the calculations being done. If <br>
you do not do this, then you constantly run the risk of living in the <br>
world of the Indiana pi bill.<br>
<br>
<a href="https://en.wikipedia.org/wiki/Indiana_pi_bill" rel="noreferrer" target="_blank">https://en.wikipedia.org/wiki/Indiana_pi_bill</a><br>
<br>
There is no royal road to floating point arithmetic.<br>
<br>
On 6/4/24 12:06 PM, Mark Volkmann via Cuis-dev wrote:<br>
> How do you feel about using "approximately equal" tests for things like <br>
> unit tests for the calculation of areas of 2D shapes such as circles?<br>
> <br>
> On Mon, Jun 3, 2024 at 10:47 PM Martin McClure via Cuis-dev <br>
> <<a href="mailto:cuis-dev@lists.cuis.st" target="_blank">cuis-dev@lists.cuis.st</a> <mailto:<a href="mailto:cuis-dev@lists.cuis.st" target="_blank">cuis-dev@lists.cuis.st</a>>> wrote:<br>
> <br>
> Hi Mark,<br>
> <br>
> Welcome to the list -- good to see you diving into Cuis!<br>
> <br>
> As Andres says, there are messages to do this kind of test on Floats.<br>
> <br>
> I find myself compelled, however, to warn about using such<br>
> "approximately equal" tests when inappropriate. I recently discovered<br>
> this kind of usage in an ancient test framework, and it was allowing<br>
> tests to pass that should not have. Those tests are being replaced with<br>
> equality tests.<br>
> <br>
> As I commented to a co-worker just this morning, accepting as correct a<br>
> Float result that is one ULP different from the correct Float is really<br>
> no different than accepting 5 as a correct answer to 2 + 2.<br>
> <br>
> There are, of course, times when figuring out the exact rounding<br>
> expected in a sequence of floating-point operations is impractical, and<br>
> accepting a certain amount of cumulative error is OK. Floats are often<br>
> used in applications where accuracy is only required to some specific<br>
> precision, but it's also good to keep in mind that each Float precisely<br>
> represents one value, and each operation on a Float has only one<br>
> correct<br>
> answer.<br>
> <br>
> Regards,<br>
> -Martin<br>
> <br>
> On 6/3/24 19:49, Andres Valloud via Cuis-dev wrote:<br>
> > Look at Float>>isWithin:floatsFrom:, and see also Float>>ulp.<br>
> ><br>
> > On 6/3/24 4:36 PM, Mark Volkmann via Cuis-dev wrote:<br>
> >> Is there a function that tests whether two Float values are "close"<br>
> >> (within some delta)?<br>
> >> I can write it, but I thought that might be provided.<br>
> >> I looked at all the methods in the Float class, but didn't find one<br>
> >> like that.<br>
> >><br>
> >> --<br>
> >> R. Mark Volkmann<br>
> >> Object Computing, Inc.<br>
> >><br>
> <br>
> -- <br>
> Cuis-dev mailing list<br>
> <a href="mailto:Cuis-dev@lists.cuis.st" target="_blank">Cuis-dev@lists.cuis.st</a> <mailto:<a href="mailto:Cuis-dev@lists.cuis.st" target="_blank">Cuis-dev@lists.cuis.st</a>><br>
> <a href="https://lists.cuis.st/mailman/listinfo/cuis-dev" rel="noreferrer" target="_blank">https://lists.cuis.st/mailman/listinfo/cuis-dev</a><br>
> <<a href="https://lists.cuis.st/mailman/listinfo/cuis-dev" rel="noreferrer" target="_blank">https://lists.cuis.st/mailman/listinfo/cuis-dev</a>><br>
> <br>
> <br>
> <br>
> -- <br>
> R. Mark Volkmann<br>
> Object Computing, Inc.<br>
> <br>
-- <br>
Cuis-dev mailing list<br>
<a href="mailto:Cuis-dev@lists.cuis.st" target="_blank">Cuis-dev@lists.cuis.st</a><br>
<a href="https://lists.cuis.st/mailman/listinfo/cuis-dev" rel="noreferrer" target="_blank">https://lists.cuis.st/mailman/listinfo/cuis-dev</a><br>
</blockquote></div><br clear="all"><div><br></div><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature"><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div dir="ltr"><div><font face="arial, helvetica, sans-serif">R. Mark Volkmann</font></div><div><span style="font-size:12.8px"><font face="arial, helvetica, sans-serif">Object Computing, Inc.</font></span></div></div></div></div></div></div></div></div>