在我的iOS应用程序中,我有一个使用CGPoints构建的shape类。我使用encodeCGPoint:forKey将其保存到文件中。我把它读回来了。一切正常。
但是,我读入的CGPoint值并不完全等于我保存的值。 CGFloat值的低位不稳定。所以CGPointEqualToPoint返回NO,这意味着我的isEqual方法返回NO。这会给我带来麻烦和痛苦。
显然,从一开始就精确地序列化花车一直是个麻烦。但在这种情况下,最好的方法是什么?我能想到几个:
EDIT-ADD:因此,为了简单起见,问题的后半部分是我必须为这些形状对象实现哈希方法。
散列浮动也是一种可怕的痛苦(参见“Good way to hash a float vector?”),事实证明它或多或少地使我的问题无效。工具包的encodeCGPoint方法以令人讨厌的方式对其浮点值进行舍入 - 它实际上将它们打印为具有%g格式的字符串 - 因此我无法使用它并且仍然可以使哈希值可靠。
因此,我被迫编写自己的encodePoint函数。只要我这样做,我不妨写一个完全编码值的人。 (将两个32位浮点数复制到一个64位整数字段中,不,它不是可移植的,但这只是iOS,我正在进行权衡。)
通过可靠的CGPoints精确存储,我可以回到精确比较和我想要的任何旧哈希函数。容差范围对我没有任何作用,所以我只是不将它们用于此应用程序。
如果我想要散列和容差比较,我会比较N 有效数字的容差范围内的值,而不是固定距离epsilon。 (也就是说,我希望0.123456比较接近0.123457,但我也希望1234.56比较接近1234.57。)对于浮点数学误差,无论是大值还是小值,这都是稳定的。我没有示例代码,但是从frexpf()函数开始,它应该不会太难。
答案 0 :(得分:1)
直接比较浮点数通常不是正确的游戏计划。尝试其中一个many other options。对你的问题最好的解决方案可能是你的最后一个建议;不过,我不知道为什么会有“叹息”。双精度浮点数具有大约16个十进制数字的精度 - 很有可能你的程序实际上 。
答案 1 :(得分:1)
使用epsilon方法,因为任何时候浮点数和双精度之间存在隐式转换(通常在框架代码中。tgmath.h
对于避免这种问题,因此“CGFloat值的低位不稳定”问题表面。在你自己的代码中。)
我使用以下函数(容差默认为0.5,因为这在CGGeometry的常见情况下很有用):
BOOL OTValueNearToValueWithTolerance(CGFloat v1, CGFloat v2, CGFloat tolerance)
{
return (fabs(v1 - v2) <= tolerance);
}
BOOL OTPointNearToPointWithTolerance(CGPoint p1, CGPoint p2, CGFloat tolerance)
{
return (OTValueNearToValueWithTolerance(p1.x, p2.x, tolerance) && OTValueNearToValueWithTolerance(p1.y, p2.y, tolerance));
}
BOOL OTSizeNearToSizeWithTolerance(CGSize s1, CGSize s2, CGFloat tolerance)
{
return (OTValueNearToValueWithTolerance(s1.width, s2.width, tolerance) && OTValueNearToValueWithTolerance(s1.height, s2.height, tolerance));
}
BOOL OTRectNearToRectWithTolerance(CGRect r1, CGRect r2, CGFloat tolerance)
{
return (OTPointNearToPointWithTolerance(r1.origin, r2.origin, tolerance) && OTSizeNearToSizeWithTolerance(r1.size, r2.size, tolerance));
}
BOOL OTValueNearToValue(CGFloat v1, CGFloat v2)
{
return OTValueNearToValueWithTolerance(v1, v2, 0.5);
}
BOOL OTPointNearToPoint(CGPoint p1, CGPoint p2)
{
return OTPointNearToPointWithTolerance(p1, p2, 0.5);
}
BOOL OTSizeNearToSize(CGSize s1, CGSize s2)
{
return OTSizeNearToSizeWithTolerance(s1, s2, 0.5);
}
BOOL OTRectNearToRect(CGRect r1, CGRect r2)
{
return OTRectNearToRectWithTolerance(r1, r2, 0.5);
}
BOOL OTPointNearToEdgeOfRect(CGPoint point, CGRect rect, CGFloat amount, CGRectEdge edge)
{
CGRect nearRect, otherRect;
CGRectDivide(rect, &nearRect, &otherRect, amount, edge);
return CGRectContainsPoint(nearRect, point);
}