Tom said:
I want the Magnitude of the difference. Say I have a series of floats
0.1 0.2 0.3 0.4 0.5 0.6
If I floor all these values they are 0.0 and all equal which is
obviously incorrect.
If I ceil all these values they are 1.0 and all equal which is also
obviously incorrect.
I don't think anyone suggested applying Floor or ceil to the values
themselves - you'd apply Ceiling to the *difference* between them.
For fun lets also add the values.
1e+10 1e-10
now I compare .1 and .2 i with a normal compare I get 1
if I compare .1 and .3 I get 1
if I compare .1 and .5 I get 1
if I compare .1 and 1e+10 I get 1
if I compare .1 and 1e-10 I get -1
What I need to be able to do is say take 0.1 and 0.4 and 0.6
and say 0.4 is closer to 0.1 than 0.6
Obviously with just a series of floats this is trivial, the problem is
I am trying to accomplish this with a General Interface so I can apply
it to many types including classes, like ICompareable, which is how I
thought ICompareable was supposed to work. I didn't realize that -1 0
1 were the ONLY values that it return, even for ints.
It depends on the implementation. There may be some implementations
which return other values; there's certainly nothing to stop you from
writing one which *does* return other values, as the interface only
specifies it in terms of 0, less than 0 and greater than 0.
Therefore I need to write a new interface that is similar to
IComparer, that will return the Magnitude of the differece. For ints
this is easy it's a-b. But for floats this is more difficult. How can
I write a function that will return a int representing the Magnitude
of the difference of floats, that will work for 1.0 and 1.1 but also
1e-10 and 1e+10 . i.e for 1.0 and 1.1 I may get a result of 5 but for
1e-10 and 1e+10 I may get a result of 500. The number itself isn't
important as long as it represents the magnitude of the diference up
to a certian percision. Possibly the int represents some sort of
logarythimic scale of the magnitude.
A log does indeed sound like the way to go. I'd multiply the natural
log of the difference by 3 million and convert the result into an int.
That will use virtually the whole range of int to cover the whole range
of possible differences, assuming my maths is right. You should also
have a separate check that the result is only zero if the two numbers
actually *are* the same though - it's possible that the log of the
difference will be unrepresentably small, but the numbers themselves
aren't equal. You also need to be careful that the difference doesn't
overflow to start with.
That should guarantee that if the result of Compare between x and y is
greater than the result of Compare between y and z (and both are
positive) then z is closer to y than y is closer to x. It won't
guarantee the other way round, of course.
Oh, stuff it: here's some sample code. (It's interesting how many more
little problems showed up when I started coding it...)
using System;
using System.Collections;
class Test
{
static void Main()
{
LogDoubleComparer comp = new LogDoubleComparer();
// Test an extreme case...
Console.WriteLine (comp.Compare(Double.MaxValue,
Double.MinValue));
// And some less extreme ones
Console.WriteLine (comp.Compare(0.1, 0.4));
Console.WriteLine (comp.Compare(0.4, 0.6));
}
}
class LogDoubleComparer : IComparer
{
public int Compare (object a, object b)
{
if (! (a is double && b is double))
{
throw new ArgumentException
("LogDoubleComparer can only compare doubles");
}
double x = (double)a;
double y = (double)b;
// If they're equal, return 0 now, so we can assume for
// the rest of the method that they're not equal.
if (x==y)
{
return 0;
}
if (double.IsNaN(x) || double.IsInfinity(x) ||
double.IsNaN(y) || double.IsInfinity(y))
{
throw new ArgumentException
("LogDoubleComparer can't cope with NaN or infinity");
}
// Divide both by 2.5 to make absolutely sure we'll
// be able to calculate the difference without
// overflow
double c = x/2.5;
double d = y/2.5;
// Find the absolute value of the difference. We'll
// sort out the sign later.
double diff = Math.Abs(c-d);
// Add 2 to the difference to make sure the log is positive
// (We should now not be able to get a log result of 0,
// which is handy.)
diff += 2.0;
// log can now be NegativeInfinity (if diff is 0,
// or presumably if it's too close to zero to call)
// but shouldn't be NaN.
double log = Math.Log(diff);
// Now scale so that we get a wider range of values in int.
// We should still easily be within the range of int though.
log *= 3000000.0;
// Now convert to an int, taking the ceiling to make
// sure we don't actually return 0.
int ret = (int) Math.Ceiling(log);
// Now get the sign right - we need to invert if x < y
if (x < y)
{
ret = -ret;
}
return ret;
}
}