Tom <ju********@hotmail.com> wrote:

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;

}

}

--

Jon Skeet - <sk***@pobox.com>

http://www.pobox.com/~skeet
If replying to the group, please do not mail me too