用NSCoder编码CGPoint,具有完全的精度

时间:2011-07-14 20:03:18

标签: ios

在我的iOS应用程序中,我有一个使用CGPoints构建的shape类。我使用encodeCGPoint:forKey将其保存到文件中。我把它读回来了。一切正常。

但是,我读入的CGPoint值并不完全等于我保存的值。 CGFloat值的低位不稳定。所以CGPointEqualToPoint返回NO,这意味着我的isEqual方法返回NO。这会给我带来麻烦和痛苦。

显然,从一开始就精确地序列化花车一直是个麻烦。但在这种情况下,最好的方法是什么?我能想到几个:

  • 使用encodeFloat而不是encodeCGPoint写出x和y值(这会有帮助吗?)
  • 在保存之前将我的x和y值乘以256.0(它们都将介于-1和1之间,粗略地说,这可能有帮助吗?)
  • 使用encodeDouble而不是encodeCGPoint写出x和y值(仍然可能错误地将最低位舍入?)
  • 施放到NSUInteger并使用encodeInt32写出来(icky,但它会起作用,对吗?)
  • 接受精度损失,并实现我的isEqual方法以在-epsilon比较中使用而不是CGPointEqualToPoint(叹气)

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()函数开始,它应该不会太难。

2 个答案:

答案 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);
}