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.