6

In answering this earlier question about getting a use of ceil() on a CGFloat to compile for all architectures, I suggested a solution along these lines:

    var x = CGFloat(0.5)

    var result: CGFloat

    #if arch(x86_64) || arch(arm64)
        result = ceil(x)
    #else
        result = ceilf(x)
    #endif

    // use result

(Background info for those already confused: CGFloat is a "float" type for 32-bit architecture, "double" for 64-bit architecture (i.e. the compilation target), which is why just using either of ceil() or ceilf() on it won't always compile, depending on the target architecture. And note that you don't seem to be able to use CGFLOAT_IS_DOUBLE for conditional compilation, only the architecture flags...)

Now, that's attracted some debate in the comments about fixing things at compile time versus run time, and so forth. My answer was accepted too fast to attract what might be some good debate about this, I think.

So, my new question: is the above a safe, and sensible thing to do, if you want your iOS and OS X code to run on 32- and 64-bit devices? And if it is sane and sensible, is there still a better (at least as efficient, not as "icky") solution?

Community
  • 1
  • 1
Matt Gibson
  • 37,886
  • 9
  • 99
  • 128
  • The real solution is to file a bug report. This kind of thing should not be necessary. Swift needs more forgiving, intelligent numerics. – matt Jun 12 '14 at 13:49
  • @matt Well, to be fair, CGFloat isn't actually a Swift type, it's a Core Graphics type. Swift's Float is always 32-bit, and Swift's Double is always 64-bit. And there are good reasons (performance, memory usage, precision) for wanting to have both available in a language. – Matt Gibson Jun 12 '14 at 13:57
  • 2
    They knew going into this that interaction with Core Graphics types would be common. The language needs to make that interaction easy. It is supposed to be _designed_ for use with Cocoa. – matt Jun 12 '14 at 13:58
  • @matt Well, it's only a beta. Have you raised a Radar? I've seen the problem cropping up a lot, so I'm sure you're right that there should be a simpler way to deal with it. I asked this question partly because I was wondering if I was missing an easier way... – Matt Gibson Jun 12 '14 at 14:04
  • It's just a matter of wrapping those checks in within a CGFloatCeil() – Fernando Mazzon Jun 12 '14 at 14:59

3 Answers3

3

Matt,

Building on your solution, and if you use it in several places, then a little extension might make it more palatable:

extension CGFloat {
    var ceil: CGFloat {
        #if arch(x86_64) || arch(arm64)
            return ceil(x)
        #else
            return ceilf(x)
        #endif
    }
}

The rest of the code will be cleaner:

var x = CGFloat(0.5)
x.ceil
Jean Le Moignan
  • 22,158
  • 3
  • 31
  • 37
2
    var f : CGFloat = 0.5
    var result : CGFloat
    result = CGFloat(ceil(Double(f)))

Tell me what I'm missing, but that seems pretty simple to me.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Well, it's slower than my version, presumably because of the extra variable being initialised, but whether that's enough to warrant the conditional compilation probably depends on what you're doing and how often you're doing it. – Matt Gibson Jun 12 '14 at 14:16
  • @MattGibson have you benchmarked that as as slower? I would expect LLVM to optimise that away. – Rog Apr 09 '15 at 11:52
  • @RogerNolan I can't remember. Bear in mind that this would have been a comment from a *very* early version of the Swift compiler, anyway, and even if I'd benchmarked to to death last June it's unlikely to be true now. – Matt Gibson Apr 09 '15 at 12:39
  • :-) I'd prefer simplicity over optimisation until there is a real performance problem. – Rog Apr 09 '15 at 12:40
1

Note that with current version of Swift the solution below is already implemented in the standard library and all mathematical functions are properly overloaded for Double, Float and CGFloat.

Ceil is an arithmetic operation and in the same way as any other arithmetic operation, there should be an overloaded version for both Double and Float.

var f1: Float = 1.0
var f2: Float = 2.0

var d1: Double = 1.0
var d2: Double = 2.0

var f = f1 + f2
var d = d1 + d2

This works because + is overloaded and works for both types.

Unfortunately, by pulling the math functions from the C library which doesn't support function overloading, we are left with two functions instead of one - ceil and ceilf.

I think the best solution is to overload ceil for Float types:

func ceil(f: CFloat) -> CFloat {
    return ceilf(f)
}

Allowing us to do:

var f: Float = 0.5
var d: Double = 0.5

var f: Float = ceil(f)
var d: Double = ceil(d)

Once we have the same operations defined for both Float and Double, even CGFloat handling will be much simpler.

To answer the comment:

Depending on target processor architecture, CGFloat can be defined either as Float or a Double. That means we should use ceil or ceilf depending on target architecture.

var cgFloat: CGFloat = 1.5

//on 64bit it's a Double
var rounded: CGFloat = ceil(cgFloat)

//on 32bit it's a Float
var rounded: CGFloat = ceilf(cgFloat)

However, we would have to use the ugly #if.

Another option is to use clever casts

var cgFloat: CGFloat = 1.5
var rounded: CGFloat = CGFloat(ceil(Double(cgFloat))

(casting first to Double, then casting the result to CGFloat)

However, when we are working with numbers, we want math functions to be transparent.

var cgFloat1: CGFloat = 1.5
var cgFloat2: CGFloat = 2.5

// this works on both 32 and 64bit architectures!
var sum: CGFloat = cgFloat1 + cgFloat 2

If we overload ceil for Float as shown above, we are able to do

var cgFloat: CGFloat = 1.5

// this works on both 32 and 64bit architectures!
var rounded: CGFloat = ceil(cgFloat)
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Would you care to expand on the CGFloat handling? That's what I'm specifically interested in, and it has complexity that Float and Double don't, given that it's both a double *and* a float. – Matt Gibson Jun 12 '14 at 15:21
  • @MattGibson Added some more info. `CGFloat` is not really complex, the problem is that we are missing real math library for Swift. C functions doesn't work well when you don't have the preprocessor. We need function overloading, not different functions for different types. – Sulthan Jun 12 '14 at 15:40