我有一个不可变的Vector3结构,我想知道如何最好地实现.Equals()方法,以便它有用并且仍然满足Guidelines for Overloading 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。例如,我可以通过检查 u x v ==< 0,0,0&gt ;;来判断两个vector3是否平行;但是对于浮点运算,这通常会失败,因为其中一个零实际上是< 8.205348E-09>。
我正在寻找一种更智能的Equals()方法。无论数字是非常大还是非常小,我希望它可以工作。我
答案 0 :(得分: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;
}
然后我们有一个继承自VectorBase&lt;
的Vector3Dclass Vector3D : VectorBase<double>
完整的载体列表是
* Vector2D - 2 dimensional vector.
* Vector3D - 3 dimensional vector.
* VectorN - A vector with user-defined dimensions.
答案 1 :(得分: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;
}
答案 2 :(得分:1)
除非Vector3暗示的向量之间存在一些最小精度,否则我会将Equals设为简单的FP等式检查。
另外我会创建一个AlmostEquals方法(甚至可能是一个重载的Equals,它不受Equals的约束)。我不会将“模糊逻辑”放入Equals,因为Equals是与GetHashCode的严格契约。
另一方面,如果设置了最小精度要求,则可以使用该要求,这只是标准化的正常等式检查。归一化后执行此操作的原因是GetHashCode也可以使用规范化值。 (只是对值进行delta检查对GetHashCode不起作用)。我不是这种方法的粉丝。
当然,所使用的方法应该考虑如何使用Vector3以及它具有什么角色。也许需要一种更受限制的类型?
答案 3 :(得分:1)
您不应使用距离检查在Vector3中实现Equals。首先这将打破“if(x.Equals(y)&amp;&amp; y.Equals(z))返回true,然后x.Equals(z)返回true。”合同即可。你需要一个例子吗?
其次,为什么需要重载Equals方法? 谁将成为该API的客户?您想要自己比较这些对象,还是想将某些库(如集合或地图)与Vector3实例一起用作值和/或键?在以后的情况下,这可能非常危险!如果你想在哈希映射中使用这样的向量作为键,那么你还需要定义一些返回常量的普通GetHashCode方法。因此,在这种情况下,最好的比较是“this._x == vector._x&amp;&amp; this._y == vector._y&amp;&amp; this._z == vector._z”。
如果你想自己进行这样的比较,那么将更清楚你是否会定义一些其他方法,如“IsClose”,并在方法摘要中解释比较思想。
答案 4 :(得分:0)
“对两个向量进行归一化,然后比较一个小的epsilon值中的每个浮点数都能很好地工作吗?”
是的它应该运作得很好。然而,有更好(更有效)的方法来做到这一点。提取比较数字的指数,然后比较减去的值与上升到最大指数权力的eps。
伪代码看起来像这样:
exp1 = exponentOf(r1)
exp2 = exponentOf(r2)
if absolute_value(r2 - r1) <= epsilon^(max(exp1, exp2))
return equal
else
return not_equal