79
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

本当は怖いCGFloat

CoreGraphicsやCoreAnimationなどで頻繁にお世話になるCocoaの浮動小数点系のCGFloatですが、使い方を間違えると思わぬバグを生みます。

というのも、 CGFloatは32bit/64bit環境によって型が違う からなんですが。

CGBase.h

#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を使いたくなるのでやっぱり危険です。

スクリーンショット 2014-02-26 22.43.01.png

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
79
Help us understand the problem. What are the problem?