CoreGraphicsやCoreAnimationなどで頻繁にお世話になるCocoaの浮動小数点系のCGFloatですが、使い方を間違えると思わぬバグを生みます。
というのも、 CGFloatは32bit/64bit環境によって型が違う からなんですが。
#if defined(__LP64__) && __LP64__
# define CGFLOAT_TYPE double
# define CGFLOAT_IS_DOUBLE 1
# define CGFLOAT_MIN DBL_MIN
# define CGFLOAT_MAX DBL_MAX
#else
# define CGFLOAT_TYPE float
# define CGFLOAT_IS_DOUBLE 0
# define CGFLOAT_MIN FLT_MIN
# define CGFLOAT_MAX FLT_MAX
#endif
/* Definition of the `CGFloat' type and `CGFLOAT_DEFINED'. */
typedef CGFLOAT_TYPE CGFloat;
#define CGFLOAT_DEFINED 1
CGFloatの定義をみると、こんな感じになっています。
64bitアーキテクチャではdouble, それ以外(32bit)ではfloatとして定義されています。
これがどういうあれかというと、浮動小数点が表せる精度が異なるというのもそうなんですが、二つ大きな問題があって、まず一つ目。
NSNumberからCGFloatを取得できない
NSNumberにはintegerValueやunsignedIntegerValueなどの型安全なゲッタがありますが、CoreGraphics特有の型であるCGFloatを安全に取得する方法がありません。なので、NSNumberからCGFloatを取得する際はこんな感じの記述が必要になります。
// 以下のリンクからの参照
#if CGFLOAT_IS_DOUBLE
CGFloat reverseValue = [aNumber doubleValue];
#else
CGFloat reverseValue = [aNumber floatValue];
#endif
とくに、CGFloatを返り値に持つメソッドなどでは注意が必要です。
##CGFLOAT_MAXとCGFLOAT_MINの値が異なる
CGFLOAT_MAXとCGFLOAT_MINは、CGFloatが定義された型によって値が異なります。
32bit環境ではFLT_MIN/FLT_MAX、64bit環境ではDBL_MIN/DBL_MAX。
なので、例えばNSNotFoundのようにCGFloatで無効な値を表したい時に
#define CGFloatNil CGFLOAT_MIN
みたいなことをやってしまうと、上記のNSNumberからのCGFloat値の取得を間違えるとこのマクロが効かなくなります。
// numがCGFloatNilの値を持っているとして...
CGFloat f = [num doubleValue];
if (f == CGFloatNil) {
// ↑の比較は f == CGFLOAT_MIN ⇔ f == DBL_MIN or f == FLT_MIN
// なので、64bit環境ならここに来るが32bit環境だと来ない
}
なので、CGFloatを使う場合は常にこんな感じのカテゴリを作って使ったほうが安全です。
@interface NSNumber (CGFloat)
- (CGFloat)your_class_prefix_CGFloatValue;
@end
@implementation NSNumber (CGFloat)
- (CGFloat)your_class_prefix_CGFloatValue
{
#if CGFLOAT_IS_DOUBLE
return [self doubleValue];
#else
return [self floatValue];
#endif
}
@end
※2014/02/26 22:43 追記
@AknEp さんから(CGFloat)[num doubleValue]を使っても大丈夫そうだというコメントを頂きましたが、下記の理由から止めたほうが良さそうです。常にdoubleValueを使えば問題なさそうですが、CGFloatというネーミング上floatValueを使いたくなるのでやっぱり危険です。