How to use a float as key in c# Dictionary with custom comparer that rounds to nearest .01?

自闭症网瘾萝莉.ら 提交于 2021-02-10 20:21:35

问题


I'm looking to implement an IEqualityComparer class that stores and compares floating point keys that are rounded to the nearest 0.01. In particular, I want to make sure I implement the GetHashCode method correctly. I would like to make this as efficient as possible. Can I use just use the float value itself as it's own hash?

I could multiply by 100, cast to int and use an int as a key, but I'm curious if this can be done with float keys.

Note: I would wrap the dictionary in a class to ensure that only values rounded to .01 are ever added or compared.

Follow up question: If I used Decimal (guaranteed to always be rounded to .01) could I just use the default comparer for Decimal with Decimal keys in a Dictionary?

My first thought is to try this implementation. Any pitfalls?

class FloatEqualityComparer : IEqualityComparer<float>
{
    public bool Equals(float b1, float b2)
    {
        int i1 = (int)(b1 * 100);
        int i2 = (int)(b2 * 100);
        if(i1 == i2)
            return true;
        else
            return false;
    }

    public float GetHashCode(float x)
    {
        return x;
    }
}

回答1:


The problem is the GetHashCode implementation. If two floats might be considered equal, they must yield the same hash codes.

Why not

sealed class FloatEqualityComparer : IEqualityComparer<float>
{
    public bool Equals(float x, float y) => Math.Round(x, 3) == Math.Round(y, 3);

    public int GetHashCode(float f) => Math.Round(f, 3).GetHashCode();
}

The reason for this is that the equality test is not performed if two hash codes are different. This improves performance dramatically as otherwise each value would need to be compared to every other value -> O(N2). Therefore, if two values should be compared to one another for equality, their hash codes must collide.

Note that any type may be used as a key in an IDictionary<TKey, TValue>.




回答2:


There is nothing in the .NET documentation to say that floating values returned from Math.Round() will pass the equality comparison when they should, e.g. 2.32 should always equal 2.32, but if either value is plus or minus float.Epsilon, the equality could befalse. This risks creating 2 keys for the same value shifted by only float.Epsilon. I'm solving this unlikely (although buggy) issue by handling the rounding by multiplying and casting to int instead of calling Math.Round().

sealed class FloatEqualityComparer : IEqualityComparer<float>
{
    int GetPreciseInt(float f)
    {
        int i1 = (int)(b1 * 100);
        int i2 = (int)(b2 * 100);
        return (i1 == i2);
    }

    public bool Equals(float f1, float f2) => GetPreciseInt(f1) == GetPreciseInt(f2);
    public int GetHashCode(float f) => GetPreciseInt(f).GetHashCode();
}

*I am not concerned about the edge cases in rounding floating point numbers of finite precision, rather I am concerned about using those rounded imprecise floats as keys in a dictionary.



来源:https://stackoverflow.com/questions/48655003/how-to-use-a-float-as-key-in-c-sharp-dictionary-with-custom-comparer-that-rounds

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!