Как мне реализовать .Equals () в моей структуре Vector3?

2

У меня есть неизменяемая структура Vector3, и мне интересно, как наилучшим образом реализовать метод .Equals(), чтобы он полезен и по-прежнему удовлетворяет Рекомендации для перегрузки равных().

Здесь выполняется частичная реализация моей структуры:

public struct Vector3
{
    private float _x;
    private float _y;
    private float _z;

    public float X { get { return _x; }} 
    public float Y { get { return _y; }}
    public float Z { get { return _z; } }

    public Vector3(float x, float y, float z)
    {
        _x = x;
        _y = y;
        _z = z;
    }

    public override bool Equals(object obj)
    {
        //What should go here?
    }
}

Изменить: я не хочу напрямую сравнивать каждый X, Y, Z из-за характера операций с плавающей запятой. Например, я могу сказать, параллельны ли два vector3s, проверяя, есть ли u x v == < 0, 0, 0 > ; однако с операциями с плавающей запятой это часто терпит неудачу, потому что один из нулей на самом деле < 8.205348E-09 > .

Я ищу метод Equals(), который немного умнее. Я хочу, чтобы это работало, являются ли цифры очень большими или очень маленькими. I

  • 1
    Что не так с проверкой всех трех элементов на равенство? Есть ли причина, чтобы сделать его более сложным? (Предполагая, что нет необходимости проверять на VectorN) OTOH, возможно, существует также необходимость в "NearEquals", учитывая, что равенство FP относительной точности может быть ... вводящим в заблуждение. Поскольку у «Равных» есть контракт с «GetHashCode», я бы не рекомендовал использовать логику «NearEquals» в «Равных».
Теги:
floating-point

5 ответов

2

В NGenerics http://code.google.com/p/ngenerics/ у нас есть базовый класс VectorBase

Из VectorBase

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        var vector = obj as IVector<T>;
        return (EqualsInternal(vector));
    }

    public bool Equals(IVector<T> other)
    {
        return other != null && EqualsInternal(other);
    }


    private bool EqualsInternal(IVector<T> other)
    {

        if (dimensionCount != other.DimensionCount)
        {
            return false;
        }
        for (var i = 0; i < dimensionCount; i++)
        {
            if (!Equals(this[i], other[i]))
            {
                return false;
            }
        }
        return true;
    }

    public static bool operator ==(VectorBase<T> left, IVector<T> right)
    {
        // If both are null, or both are same instance, return true.
        if (ReferenceEquals(left, right))
        {
            return true;
        }

        // If one is null, but not both, return <c>false</c>.
        if (((object)left == null) || (right == null))
        {
            return false;
        }

        // Return true if the fields match:
        return left.EqualsInternal(right);
    }

    public static bool operator !=(VectorBase<T> left, IVector<T> right)
    {
        return !(left == right);
    }


    /// <inheritdoc />
    public override int GetHashCode()
    {
        var hashCode = 0;
        for (var index = 0; index < dimensionCount; index++)
        {
            hashCode ^= this[index].GetHashCode();
        }
        return hashCode;
    }

Тогда у нас есть Vector3D, который наследует от VectorBase <

class Vector3D : VectorBase<double>

Полный список векторов

* Vector2D - 2 dimensional vector.
* Vector3D - 3 dimensional vector.
* VectorN - A vector with user-defined dimensions. 

http://code.google.com/p/ngenerics/wiki/Vectors

1

Вы не должны применять Equals в Vector3, используя дистанционные проверки. Прежде всего это сломается "if (x.Equals(y) & y.Equals(z)) возвращает true, тогда x.Equals(z) возвращает true." контракт. Вам нужен пример для этого?.
Во-вторых, зачем вам перегружать метод Equals? Кто будет клиентом этого API?. Вы хотите сравнить эти объекты самостоятельно или хотите использовать некоторые библиотеки, такие как коллекции или карты, с экземплярами Vector3 в качестве значений и/или ключей? В более позднем случае это может быть очень опасно! Если вы хотите использовать такие векторы, как ключи в хэш-картах, когда вам также понадобится определить тривиальный метод GetHashCode, который будет возвращать константу. Поэтому лучшим сравнением будет "this._x == vector._x & this._y == vector._y & this._z == vector._z" в этом случае.
Если вы хотите иметь такое сравнение для себя, то будет более понятным, если вы определите какой-либо другой метод, например "IsClose", с объяснением идеи сравнения в сводке методов.

1

Если не существует некоторой минимальной точности между векторами, которая подразумевается с Vector3, я бы сделал Equals простой проверкой равенства FP.

Кроме того, я бы создал метод AlmostEquals (возможно, даже перегруженный Equals, свободный от контракта Equals). Я бы не поставил "нечеткую логику" в Equals, потому что Equals как строгий контракт с GetHashCode.

С другой стороны, если задано требование минимальной точности, которое можно было бы использовать и было бы просто нормальной проверкой нормализации нормализации. Причиной сделать это после нормализации было бы так, чтобы GetHashCode мог также использовать нормированные значения. (Только дельта-проверки по значениям не будут работать для GetHashCode). Однако я не поклонник этого подхода.

Конечно, используемый подход должен учитывать, как использовать Vector3 и какую роль он имеет. Возможно, существует необходимость в более ограниченном типе?

1

Что случилось со следующим?

protected const float EqualityVariance = 0.0000001;

public override bool Equals(object obj)
{
    Vector3? vector = obj as Vector3?;
    if (vector == null) return false;

    return Equals((Vector3)vector);
}

public bool Equals(Vector3 vector)
{
    return Math.Abs(this._x - vector._x) < EqualityVariance && 
           Math.Abs(this._y - vector._y) < EqualityVariance && 
           Math.Abs(this._z - vector._z) < EqualityVariance;
}
  • 0
    Вы также должны переопределить ==,! =, Equals (Vector) и gethashcode
  • 0
    Проблема в том, что выполнение == для плавающей запятой часто дает ложные отрицания (по отношению к рассматриваемой проблеме). У вас может быть два числа, которые должны быть 1,0, а одно - 0,999999967672434. Следовательно, вместо a == b можно сделать abs (ab) <0.00000001. (Я не знаю, что такое функция абсолютного значения C # для чисел с плавающей запятой; это только предположение.)
Показать ещё 9 комментариев
0

"Будет ли нормализовать оба вектора, а затем сравнивать каждый поплавок с небольшим значением эпсилона, хорошо работать?"

Да, это должно работать неплохо. Однако есть лучший (более эффективный) способ сделать это. Извлеките экспоненты сравниваемых чисел, затем сравните вычитаемое значение с eps, умноженное на максимальную мощность экспонентов.

Псевдо код будет выглядеть так:

exp1 = exponentOf(r1)
exp2 = exponentOf(r2)
if absolute_value(r2 - r1) <= epsilon^(max(exp1, exp2)) 
    return equal 
else
    return not_equal

Ещё вопросы

Сообщество Overcoder
Наверх
Меню