2

I have a code:

    public class Point
    {
        public int x;
        public int y;
        public Point() { x = 0; y = 0; }
        public Point(int a, int b) { x = a; y = b; }
    }
    public struct Coefficients{
        public double a;
        public double b;
        public double c;
        public Coefficients(double a, double b, double c)
        {
            this.a = a;
            this.b = b;
            this.c = c;
        }
        public static Coefficients GetFromPoints(Point point1, Point point2)
        {

            int x1 = point1.x;
            int x2 = point2.x;
            int y1 = point1.y;
            int y2 = point2.y;
            double a = y1- y2;
            double b = x2 - x1;
            double c = x1 * y2 - y1 * x2 ;
            double max = Math.Max(Math.Max(a, b), c);
            double min= Math.Min(Math.Min(a, b), c);
            double divider = Math.Abs(max)> Math.Abs(min)?max:min;
            divider = Math.Abs(divider) > 1? divider : 1;
            return new Coefficients(a/divider, b/divider, c/divider);

        }

    }
public class Solution
    {
        public int MaxPoints(Point[] points)
        {
            var coef_list = new List<Coefficients>();
            for (var x = 0; x < points.Length - 1; x++)
            {
                for (var y = x + 1; y < points.Length; y++)
                {
                    var coef = Coefficients.GetFromPoints(points[x], points[y]);
                    coef_list.Add(coef);
                }
            }
            foreach (var item in coef_list) {
                Debug.WriteLine(item.a);
                Debug.WriteLine(item.b);
                Debug.WriteLine(item.c);
                Debug.WriteLine(item.GetHashCode());
                Debug.WriteLine("---------------");
            }           
            return 0;
        }
    }

As you can see i used a struct and i remarked weird behavior. If i have input data like this:

prg.MaxPoints(new Point[] { new Point(4, -1), new Point(4, 0), new Point(4, 5) });

Debug output is:

-0,25
0
1
-450335288
---------------
-0,25
0
1
-450335288
---------------
-0,25
0
1
-450335288
---------------

But if i change args. order to:

prg.MaxPoints(new Point[] { new Point(4, 0),new Point(4, -1) , new Point(4, 5) });

Debug out is:

-0,25
0
1
1697148360
---------------
-0,25
0
1
-450335288
---------------
-0,25
0
1
-450335288
---------------

And there is one thing that can be important is that in first case we have all "dividers"(GetFromPoints method) are positive (4,24,20) in second case one of them is negative and other two are positive (-4,20,24). Can anybody explain this?

UPD. when i changed

return new Coefficients(a/divider, b/divider, c/divider);

to

return new Coefficients(a/divider, 0, c/divider);//anyway in all of these cases 2-nd argument is 0

which means that 0 divided by a negative isn't 0?

  • Yes, but i thought that structs with equal fields should be Equal between themselves but they aren't. `Debug.WriteLine(coef_list[0].a.Equals(coef_list[1].a)); Debug.WriteLine(coef_list[0].b.Equals(coef_list[1].b)); Debug.WriteLine(coef_list[0].c.Equals(coef_list[1].c)); Debug.WriteLine(coef_list[0].Equals(coef_list[1]));` Displays `true true true false` – Vlad Melnik May 07 '17 at 20:09
  • They aren't equal, your math gives you a rounding error, so `-0,25` probably isn't. When outputting the a, b, and c values, use `.ToString("G17")` – Lasse V. Karlsen May 07 '17 at 20:24
  • Peeking at the bytes, it looks like maybe there is an underflow of some sort because they are not *exactly* the same in terms of bytes, but give notionally equal values. In the first case, the first component has a `b` that is exactly zero: `0 0000000000000000`, while in the second case it is slightly different: `0 0000000000000080`. – Mike Zboray May 07 '17 at 20:26
  • @LasseV.Karlsen they are equal, `.ToString("G17")` returns same. And see comment above i checked equality of each field in struct. – Vlad Melnik May 07 '17 at 20:29
  • Also while those two doubles compare as equal and have the same hashcodes the structs containing them do not. My guess is that the runtime does some byte level arithmetic compute hashcodes by default. – Mike Zboray May 07 '17 at 20:31
  • @mikez zero is zero i think 4-4=0 in my case and 1/4=0.25. There are no ceriodic fractions anywhere. – Vlad Melnik May 07 '17 at 20:35
  • 1
    Implementaton of struct default GetHashode is quite complicated (as you can read for example here: http://stackoverflow.com/a/5927853/5311735) and I would not rely on it in any case. – Evk May 07 '17 at 20:41
  • Zero is *not* zero. There can be negative zero which has a different byte sequence which is what is happening here, combined with some interesting behavior of the default GetHashCode. – Mike Zboray May 07 '17 at 20:51

1 Answers1

3

Basically you are getting a negative zero value double. However the runtime's default GetHashCode for structs appears to blindly just combine the underlying bytes and not call the field's GetHashCode. Here is simplified version of what you are seeing:

public struct S
{
    public double value;

    public S(double d)
    {
        value = d;
    }
}

public static void Main(string[] args)
{           
    double d1 = 0;
    double d2 = d1 / -1;

    Console.WriteLine("using double");
    Console.WriteLine("{0} {1}", d1, d1.GetHashCode());
    Console.WriteLine(GetComponentParts(d1));
    Console.WriteLine("{0} {1}", d2, d2.GetHashCode());
    Console.WriteLine(GetComponentParts(d2));
    Console.WriteLine("Equals: {0}, Hashcode:{1}, {2}", d1.Equals(d2), d1.GetHashCode(), d2.GetHashCode());

    Console.WriteLine();
    Console.WriteLine("using a custom struct");

    var s1 = new S(d1);
    var s2 = new S(d2);
    Console.WriteLine(s1.Equals(s2));
    Console.WriteLine(new S(d1).GetHashCode());
    Console.WriteLine(new S(d2).GetHashCode());            
}

// from: https://msdn.microsoft.com/en-us/library/system.double.epsilon(v=vs.110).aspx
private static string GetComponentParts(double value)
{
    string result = String.Format("{0:R}: ", value);
    int indent = result.Length;

    // Convert the double to an 8-byte array.
    byte[] bytes = BitConverter.GetBytes(value);
    // Get the sign bit (byte 7, bit 7).
    result += String.Format("Sign: {0}\n", 
                          (bytes[7] & 0x80) == 0x80 ? "1 (-)" : "0 (+)");

    // Get the exponent (byte 6 bits 4-7 to byte 7, bits 0-6)
    int exponent = (bytes[7] & 0x07F) << 4;
    exponent = exponent | ((bytes[6] & 0xF0) >> 4);  
    int adjustment = exponent != 0 ? 1023 : 1022;
    result += String.Format("{0}Exponent: 0x{1:X4} ({1})\n", new String(' ', indent), exponent - adjustment);

    // Get the significand (bits 0-51)
    long significand = ((bytes[6] & 0x0F) << 48); 
    significand = significand | ((long) bytes[5] << 40);
    significand = significand | ((long) bytes[4] << 32);
    significand = significand | ((long) bytes[3] << 24);
    significand = significand | ((long) bytes[2] << 16);
    significand = significand | ((long) bytes[1] << 8);
    significand = significand | bytes[0];    
    result += String.Format("{0}Mantissa: 0x{1:X13}\n", new String(' ', indent), significand);    

    return result;   
}

The output:

using double
0 0
0: Sign: 0 (+)
Exponent: 0xFFFFFC02 (-1022)
Mantissa: 0x0000000000000

0 0
0: Sign: 1 (-)
Exponent: 0xFFFFFC02 (-1022)
Mantissa: 0x0000000000000

Equals: True, Hashcode:0, 0

using a custom struct
False
346948956
-1800534692

I've defined two double one of which is the "normal" zero and the other which is "negative" zero. The difference between the two is in the double's sign bit. The two values are equal in all apparent ways (Equals comparison, GetHashCode, ToString representation) except on the byte level. However when they are put into a custom struct the runtime's GetHashCode method just combines the raw bits which gives a different hash code for each struct even through they contain equal values. Equals does the same thing and gets a False result.

I admit this is kind of big gotcha. The solution to this is to make sure to you override Equals and GetHashCode to get the proper equality that you want.

Actually a similar issue has been mentioned before apparently the runtime only does this when the struct's fields are all 8 bytes wide.

Community
  • 1
  • 1
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122