In article <154dedac-ee33-44d1-bb89-

b1**********@d5g2000hsc.googlegroups.com>,

ja*********@gmail.com says...

[ ... ]

What ever you do, don't call this isEqual. It doesn't test for

equality, and it doesn't establish an equivalence relationship.

Something like isApproximatelyEqual may require a bit more

typing, but it won't confuse the reader.

You have a good point -- this was originally posted as a follow-up to a

post that used the name isEqual, so I used the same even though it's

clearly not accurate. I believe a comment to that effect was made at the

time, but it should have been incorporated into the code.

>
int mag1, mag2;

frexp(a, &mag1);

frexp(b, &mag2);

if ( mag1 != mag2)

return false;

And the above condition is almost certainly wrong. On my Intel

based machine, it means that your function will return false for

the values 1.0 and 0.9999999999999998, regardless of the

precision requested.

Good point.

You really do have to calculate the espilon as a function of the

two values involved, something like:

That's what I was using frexp and ldexp to do. I didn't take all the

corner cases into account, but the idea was definitely to adjust the

magnitude of the precision to fit the magnitudes of the numbers.

Unfortunately, both you and I took roughly the same route, of adjusting

the precision specification to fit the magnitude of the numbers, which

makes some problems such as possible underflow and overflow difficult to

avoid.

After some thought, I realized that it would be better to reverse that:

adjust the magnitude of the numbers to fit the precision specification.

This makes most of the problems disappear completely. I've included the

code at the end of this post.

Except, of course, that still has problems with very big values

(for which a + b might overflow) and very small values (for

which a - b might underflow, and result in zero). And I'm not

really sure what you should do for values which are near the

minimum representable: Do they compare approximately equal to

zero? Do they compare equal even if their signs are not the

same?

See below -- I'm pretty sure my new code avoids problems with either

underflow or overflow.

I'm not sure the problems with numbers extremely close to zero are

entirely real. The question being asked is whether two numbers are equal

to a specified precision. The answer to that is a simple yes/no. That

question may not always be the appropriate one for every possible

calculation, but that doesn't mean there's anything wrong with the code

as it stands.

I'm not sure what the FAQ says, but your code isn't really

correct (although I'm not sure that there is an absolute

definition of "correct" in this case).

This is a basic question of what should be specified, and whether that

specification fits all possible purposes. The code in the FAQ might fit

some specification, but only a very limited one (i.e. that all the

numbers involved must be quite close to 1.0 or -1.0).

I wouldn't attempt to claim that it fits every possible situation, but I

think the following code is (at least starting to get close to) correct.

The specification I'm following is that it is supposed to determine

whether two numbers are equal within a specified relative difference.

Here's the code, along with a few test cases:

#include <math.h>

bool isNearlyEqual(double a, double b, double prec = 1e-6) {

int mag1, mag2;

double num1 = frexp(a, &mag1);

double num2 = frexp(b, &mag2);

if (abs(mag1-mag2) 1)

return false;

num2 = ldexp(num2, mag2-mag1);

return fabs(num2-num1) < prec;

}

#ifdef TEST

#include <iostream>

int main() {

std::cout.setf(std::ios_base::boolalpha);

// these should yield false -- the differ after 5 digits

std::cout << isNearlyEqual(1e-20, 1.00001e-20) << std::endl;

std::cout << isNearlyEqual(1e200, 1.00001e200) << std::endl;

std::cout << isNearlyEqual(-1e10, -1.0001e10) << std::endl;

// these should yield true; all are equal to at least 6 digits

std::cout << isNearlyEqual(1e-20, 1.000001e-20) << std::endl;

std::cout << isNearlyEqual(1e200, 1.000001e200) << std::endl;

std::cout << isNearlyEqual(1.0, 0.99999998) << std::endl;

std::cout << isNearlyEqual(-1e10, -1.000001e10) << std::endl;

return 0;

}

#endif

I'd certainly welcome any comments on this code.

As an aside, I'd note that I don't really like the 'if (abs(mag2-mag1)>

1)' part, but I can't see anything a lot better at the moment. In

theory, I'd like it to take the tolerance into account -- e.g. if

somebody specifies a tolerance of 100, it should allow numbers that are

different by a factor of 100 to yield true. This would add some

complexity, however, and I can't think of any real uses to justify it.

--

Later,

Jerry.

The universe is a figment of its own imagination.