2011-07-14 20 views
2

在我的iOS應用中,我有一個用CGPoints構建的形狀類。我使用encodeCGPoint:forKey將它保存到一個文件中。我讀回來了。這一切都有效。用NSCoder對CGPoint進行編碼,具有全精度

但是,我讀入的CGPoint值與我保存的值不完全相同。 CGFloat值的低位不穩定。所以CGPointEqualToPoint返回NO,這意味着我的isEqual方法返回NO。這給我帶來麻煩和痛苦。

顯然,從一開始就精確地序列化浮點數是一件麻煩事。但在這種情況下,最好的方法是什麼?我能想到的幾個:

  • 使用encodeFloat代替encodeCGPoint(?會是無助於)
  • 乘以256.0我x和y的值保存之前寫出來的x和y值(它們是都會在-1和1之間,所以這可能有幫助嗎?)
  • 使用encodeDouble而不是encodeCGPoint寫出x和y值(仍可能錯誤地舍入最低位?)
  • 轉換爲NSUInteger和寫出來使用encodeInt32(icky,但它會工作,對不對?)
  • 接受損失的精度,並實現我的isEqual方法使用內厄普西隆比較,而不是CGPointEqualToPoint(嘆氣)

編輯-ADD:所以這個問題,我離開了爲簡單的下半年,是我必須爲這些形狀對象實現散列方法。

散射花車是一個可怕的痛苦(見「Good way to hash a float vector?」),它原來或多或少無效我的問題。該工具包的encodeCGPoint方法以令人討厭的方式舍入其float值 - 這實際上是將它們打印爲具有%g格式的字符串 - 所以我無法使用它並仍然使哈希可靠。因此,我不得不寫我自己的encodePoint函數。只要我這樣做,我還不如寫一個能準確編碼數值的數字。 (將兩個32位浮點數複製到一個64位整數字段中,不,它不是便攜式的,但是這只是iOS而且我正在做這個折衷。)

可靠地存儲CGPoints,我可以回到精確的比較和我想要的任何舊的散列函數。公差範圍對我沒有任何幫助,所以我只是沒有將它們用於此應用程序。

如果我想散列耐受性比較,我會爲N 顯著數字,沒有一個固定的距離小量的公差範圍內比較值。 (也就是說,我希望0.123456比較接近0.123457,但我也希望1234.56比較接近1234.57)。對於大數值和小數值,這對於浮點數學錯誤都是穩定的。我沒有示例代碼,但是從frexpf()函數開始,它不應該太難。

回答

1

直接比較浮點數通常不是正確的遊戲計劃。嘗試使用many other options之一。您的問題的最佳解決方案可能是您最後的建議;不過,我不知道爲什麼那裏有「嘆息」。一個雙精度浮點數有大約16位十進制數字的精度 - 這是非常好的機會,你的程序實際上並不需要需要那麼精確。

+0

卡爾,一個CGFloat的不是雙精度(在iOS)。但是,我必須同意CGPointEqualToPoint對座標進行按位比較並不是很有用。滾動您自己的isEqual並保持CGPointEqualToPoint清晰,這似乎只在您直接複製座標時纔有用。 –

+0

謝謝史蒂文;我沒有真正查看實際類型。同樣的推理立場。即使是單精度浮點數也有7位數的小數精度,它具有*得到*足以滿足OP的目的。 –

+0

是的,7位數字應該讓你的UIView *令人難以置信*接近你想要顯示的屏幕像素;-) –

1

使用epsilon方法,因爲任何時候float和double之間存在隱式轉換時(通常在框架代碼中),「CGFloat值的低位都不穩定」問題表面tgmath.h對於您自己避免這種情況非常有用。碼)

我使用以下功能(公差默認爲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); 
}