У меня есть неизменяемая структура 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
В 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.
Вы не должны применять 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", с объяснением идеи сравнения в сводке методов.
Если не существует некоторой минимальной точности между векторами, которая подразумевается с Vector3, я бы сделал Equals простой проверкой равенства FP.
Кроме того, я бы создал метод AlmostEquals (возможно, даже перегруженный Equals, свободный от контракта Equals). Я бы не поставил "нечеткую логику" в Equals, потому что Equals как строгий контракт с GetHashCode.
С другой стороны, если задано требование минимальной точности, которое можно было бы использовать и было бы просто нормальной проверкой нормализации нормализации. Причиной сделать это после нормализации было бы так, чтобы GetHashCode мог также использовать нормированные значения. (Только дельта-проверки по значениям не будут работать для GetHashCode). Однако я не поклонник этого подхода.
Конечно, используемый подход должен учитывать, как использовать Vector3 и какую роль он имеет. Возможно, существует необходимость в более ограниченном типе?
Что случилось со следующим?
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;
}
"Будет ли нормализовать оба вектора, а затем сравнивать каждый поплавок с небольшим значением эпсилона, хорошо работать?"
Да, это должно работать неплохо. Однако есть лучший (более эффективный) способ сделать это. Извлеките экспоненты сравниваемых чисел, затем сравните вычитаемое значение с eps, умноженное на максимальную мощность экспонентов.
Псевдо код будет выглядеть так:
exp1 = exponentOf(r1)
exp2 = exponentOf(r2)
if absolute_value(r2 - r1) <= epsilon^(max(exp1, exp2))
return equal
else
return not_equal