我正在将CodePlex的地理坐标类集成到我的个人“工具箱”库中。此课程使用float
字段来存储纬度和经度。
由于课程GeoCoordinate
实现IEquatable<GeoCoordinate>
,我习惯性地编写Equals
方法,如下所示:
public bool Equals(GeoCoordinate other)
{
if (other == null) {
return false;
}
return this.latitude == other.latitude && this.longitude == other.longitude;
}
此时我停下来并认为我正在比较浮点变量的相等性,这通常是禁忌。我的思考过程大致如下:
我只能设想一次设置Latitude
和Longitude
属性,这意味着不会累积任何错误来搞砸我的比较。
另一方面,写
是可能的(虽然没有意义)var geo1 = new GeoCoordinate(1.2, 1.2);
var geo2 = new GeoCoordinate(1.2, 1.2);
// geo1.Equals(geo2) will definitely be true, BUT:
geo2.Latitude *= 10;
geo2.Latitude /= 10;
// I would think that now all bets are off
当然这不是我能想到的,但是如果类的公共接口允许它,那么Equals
应该能够处理它。
使用difference < epsilon
测试比较相等性可以解决比较两个实例的问题,但会产生更多问题:
如何为所有值生成相同的哈希码,这些值会比较相等?
让我们说epsilon = 0.11
(随机例子)。因此GeoCoordinate { 1, 1 }
需要与GeoCoordinate { 1.1, 1.1 }
相同的哈希码。但后者需要与GeoCoordinate { 1.2, 1.2 }
相同的哈希码。你可以看到它的发展方向:所有实例都需要具有相同的哈希码。
所有这一切的解决方案是使GeoCoordinate
成为不可变类。这也可以解决GetHashCode
问题:它基于纬度和经度(还有其他),如果这些是可变的,那么使用GeoCoordinate
作为字典中的键就会遇到麻烦。但是,使类不可变有其自身的缺点:
您会建议采用哪种方法?很容易让这个类符合我现在的要求(只是让它变得不可变),但是有更好的方法吗?
修改:我在上面的列表中添加了第3项,将之前的第3项转移到第4位。
我将允许更多时间进行反馈,但现在我采用不可变的方法。可以看到struct
(因为它就是现在的情况)与相关成员here;评论和建议非常欢迎。
答案 0 :(得分:6)
Equals
主要用于词典,因此您应该比较浮动的指南仅适用于epsilon,此处不适用。不要试图将epsilon逻辑放入Equals
。用户的工作就是根据需要做到这一点。证明与epsilon一致的唯一哈希函数与常量哈希函数进行比较相对容易。
您的实现有一个问题:它不处理NaN
。您需要在Equals
方法的各个坐标上使用==
而不是Equals
。
public bool Equals(GeoCoordinate other)
{
if (other == null) {
return false;
}
return this.latitude.Equals( other.latitude) && this.longitude.Equals(other.longitude);
}
“不要使用{{1}进行比较,而是使用epsilon”指南用于使用代码,而不是用于实现代码。所以我实现了一个函数,它返回两个地理坐标之间的距离,并告诉用户将它用于他的epsilon比较。
我肯定会让它变成不可变的(不确定是否为结构或类)。它具有价值语义,因此应该是不可改变的
我通常使用像==
/ Linq-to-Xml
之类的东西进行序列化,因为这允许我在内存模型和磁盘模型之间转换表示。
但你是对的,许多序列化程序不支持非默认构造函数。我认为这是那些序列化程序中的一个大缺陷,而不是我模型中的一个缺陷。有些序列化程序只是访问私有的setter / fields,但我个人认为这很糟糕。
答案 1 :(得分:6)
对我来说,经度/纬度的“位置”非常适合“不可变值”槽。如果您更改不同位置的纬度,位置本身不会更改。从那里,可以成为struct
;对于float
,struct
的大小与x64参考大小相同,所以没有真正的不足。
重新平等;如果位置不完全相同,那么它不是“等于”,至少在“关键”的角度来看,所以我对这里的==
感到满意。如果它有帮助,您可以添加“在(x)距离内”方法。当然,大弧几何也不是完全自由的; p
虽然有想法:
bool Equals(object)
以及添加bool Equals(GeoCoordinate)
GetHashCode()
并实施IEquatable<GeoCoordinate>
答案 2 :(得分:2)
我只会在纬度和经度的基础模型中使用长整数而不是浮点数。在任何情况下,毫秒都不到两英寸,这应该足够详细:以毫秒为单位存储您的坐标,它更简单,更清晰,更防错。
答案 3 :(得分:1)
根据你对lat / lon结构的使用,我会担心使用浮点数而不是lat / lon的双精度数。例如,如果您正在进行lat / lon的实时集成,那么您将需要双精度。这是因为度数是1海里,并且取决于积分的时间步长,每次迭代只会移动非常小的量。
为了进行相等测试,我会使用一个基于等距矩形近似的简单距离公式,并确定如果另一个点在我确定的公差范围内(一英寸或两英寸),则声明它们相等。我将使用的等距矩形近似值为here。