Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
77
Help us understand the problem. What is going on with this article?
@keroxp

本当は怖いCGFloat

More than 5 years have passed since last update.

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

77
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
keroxp
iOS/Android/Unity/Node.js/Rails/Go/フロントエンド/SRE プログラマ

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
77
Help us understand the problem. What is going on with this article?