Round up a CGFloat in Swift
Asked Answered
F

7

103

How can I round up a CGFloat in Swift? I've tried ceil(CDouble(myCGFloat)) but that only works on iPad Air & iPhone 5S.

When running on another simulated device I get an error saying 'NSNumber' is not a subtype of 'CGFloat'

Fractious answered 12/6, 2014 at 10:20 Comment(4)
I think the reason it's working on iPad Air & iPhone 5S is because CGFloats are 64-bit on those architectures; on 32-bit architectures they're 32 bits.Santossantosdumont
I'm totally confused, becaus Swift tells me it has no idea what floor or ceil (or floorf or ceilf) is..Glenglencoe
@MarkReed I know this was a while ago, but you need to add import FoundationLatinalatinate
For general rounding examples with CGFloat see this answer.Xebec
S
131

Update: Apple have now defined some CGFloat-specific versions of common functions like ceil:

func ceil(x: CGFloat) -> CGFloat

...specifically to cope with the 32/64-bit difference. If you simply use ceil with a CGFloat argument it should now work on all architectures.

My original answer:

This is pretty horrible, I think, but can anyone think of a better way? #if doesn't seem to work for CGFLOAT_IS_DOUBLE; I think you're limited to build configurations, from what I can see in the documentation for conditional compilation.

var x = CGFloat(0.5)

#if arch(x86_64) || arch(arm64)
var test = ceil(x)
#else
var test = ceilf(x)
#endif
Santossantosdumont answered 12/6, 2014 at 10:43 Comment(11)
Horrible, but best solution so far. By the way, what's the difference between #if and if?Fractious
#if is used for conditional compilation. If you used a similar if statement, the offending code would still be compiled, and you'd still get the compile-time error, even though it wouldn't run at run time. This way whichever line of code that won't compile on the selected architecture is never actually sent for compilation.Santossantosdumont
my concern is about this solution is simply that it is a compiler-time solution only, and not a runtime solution.Honaker
@Honaker Which is why it's fast. Also, the problem is a compile-time problem; it's because CGFloat is defined differently when compiling for different architectures.Santossantosdumont
(Though personally, I'd happily have seen this question left open for a lot longer to attract people with more solutions. I wonder if I can ask it again, only in a way not to have it immediately marked as a duplicate...)Santossantosdumont
@MattGibson, according to your warning about they are stored differently on dirrefent achitectures, I still don't see how this solution would work properly on an 64 bits architecture if you compiled the project for 32 bits architechture, or vica-verse. somehow the logic is broken here, because the two theories mutually exclude each other, or the problem is not such serious and that is just too much ado for nothing.Honaker
If you compile for 64-bit architecture, CGFloat is a double, so ceil works fine. If you compile for 32-bit architecture, CGFloat is a float, so ceilf works fine. What's the problem you're seeing, exactly? (Would you like to open a new question about this? I'd like to see more discussion, but the comments here probably isn't the place.)Santossantosdumont
@MattGibson, I'm only trying to understand what you are selling here because the white gap is when the runtime architecture is different from the compiler-time architecture. according to what you just said, the application would work improperly in runtime because the two architectures will be different. like the CGFloat is stored in e.g. a float in runtime but the binary code would expect something else, because you already forced it in compiling-time to work with e.g. double later in runtime (or vica-verse). please, correct me if I'm wrong.Honaker
@MattGibson, or in a nuthsell: in compiling-time you don't know what the runtime architechure will be; and in my view making such decisions in compiling-time which depends on the runtime achitechture/environment, not a good practice or advice, even it would be faster. sorry for off the thread.Honaker
No, no, this is a perfectly good debate to be having. To get some more opinions, I've created a new question, specifically asking about my solution here. Come join in! :)Santossantosdumont
@holex: Whether CGFloat is float or double in the compiled binary is completely determined at compile time. In a 32-bit binary (which can run on 32-bit and 64-bit hardware), CGFloat is a float. In a 64-bit binary (which can only run on 64-bit hardware), CGFloat is a double. - A Universal binary contains both a 32-bit and a 64-bit binary, and picks the most appropriate for the hardware at runtime for execution. - So for this question (whether to call ceil() or ceilf()) it is only relevant if the code is compiled as 32-bit or 64-bit.Smoker
S
59

With Swift 5, you can choose one of the 3 following paths in order to round up a CGFloat.


#1. Using CGFloat's rounded(_:) method

FloatingPoint protocol gives types that conform to it a rounded(_:) method. CGFloat's rounded(_:) has the following declaration:

func rounded(_ rule: FloatingPointRoundingRule) -> CGFloat

Returns this value rounded to an integral value using the specified rounding rule.

The Playground sample code below shows how to use rounded(_:) in order to round up a CGFloat value:

import CoreGraphics

let value1: CGFloat = -0.4
let value2: CGFloat = -0.5
let value3: CGFloat = -1
let value4: CGFloat = 0.4
let value5: CGFloat = 0.5
let value6: CGFloat = 1

let roundedValue1 = value1.rounded(.up)
let roundedValue2 = value2.rounded(.up)
let roundedValue3 = value3.rounded(.up)
let roundedValue4 = value4.rounded(.up)
let roundedValue5 = value5.rounded(.up)
let roundedValue6 = value6.rounded(.up)

print(roundedValue1) // prints -0.0
print(roundedValue2) // prints -0.0
print(roundedValue3) // prints -1.0
print(roundedValue4) // prints 1.0
print(roundedValue5) // prints 1.0
print(roundedValue6) // prints 1.0

#2. Using ceil(_:) function

Darwin provides a ceil(_:) function that has the following declaration:

func ceil<T>(_ x: T) -> T where T : FloatingPoint

The Playground code below shows how to use ceil(_:) in order to round up a CGFloat value:

import CoreGraphics

let value1: CGFloat = -0.4
let value2: CGFloat = -0.5
let value3: CGFloat = -1
let value4: CGFloat = 0.4
let value5: CGFloat = 0.5
let value6: CGFloat = 1

let roundedValue1 = ceil(value1)
let roundedValue2 = ceil(value2)
let roundedValue3 = ceil(value3)
let roundedValue4 = ceil(value4)
let roundedValue5 = ceil(value5)
let roundedValue6 = ceil(value6)

print(roundedValue1) // prints -0.0
print(roundedValue2) // prints -0.0
print(roundedValue3) // prints -1.0
print(roundedValue4) // prints 1.0
print(roundedValue5) // prints 1.0
print(roundedValue6) // prints 1.0

#3. Using NumberFormatter

If you want to round up a CGFloat and format it with style in the same operation, you may use NumberFormatter.

import Foundation
import CoreGraphics

let value1: CGFloat = -0.4
let value2: CGFloat = -0.5
let value3: CGFloat = -1
let value4: CGFloat = 0.4
let value5: CGFloat = 0.5
let value6: CGFloat = 1

let formatter = NumberFormatter()
formatter.numberStyle = NumberFormatter.Style.decimal
formatter.roundingMode = NumberFormatter.RoundingMode.ceiling
formatter.maximumFractionDigits = 0

let roundedValue1 = formatter.string(for: value1)
let roundedValue2 = formatter.string(for: value2)
let roundedValue3 = formatter.string(for: value3)
let roundedValue4 = formatter.string(for: value4)
let roundedValue5 = formatter.string(for: value5)
let roundedValue6 = formatter.string(for: value6)

print(String(describing: roundedValue1)) // prints Optional("-0")
print(String(describing: roundedValue2)) // prints Optional("-0")
print(String(describing: roundedValue3)) // prints Optional("-1")
print(String(describing: roundedValue4)) // prints Optional("1")
print(String(describing: roundedValue5)) // prints Optional("1")
print(String(describing: roundedValue6)) // prints Optional("1")
Sanguinolent answered 22/3, 2017 at 13:56 Comment(1)
golden (standard of an) answerMartens
B
31

Use it on swift 5

let x = 6.5

// Equivalent to the C 'round' function:
print(x.rounded(.toNearestOrAwayFromZero))
// Prints "7.0"

// Equivalent to the C 'trunc' function:
print(x.rounded(.towardZero))
// Prints "6.0"

// Equivalent to the C 'ceil' function:
print(x.rounded(.up))
// Prints "7.0"

// Equivalent to the C 'floor' function:
print(x.rounded(.down))
// Prints "6.0"
Berkshire answered 8/7, 2019 at 12:2 Comment(1)
Thank you for the clear explinations of the enum choices!Willful
H
22

The most correct syntax would probably be:

var f: CGFloat = 2.5
var roundedF = CGFloat(ceil(Double(f)))

To use ceil I will first make the CGFloat a Double and after ceiling, I convert it back to CGFloat.

That works when CGFloat is defined either as CFloat or CDouble.

You could also define a ceil for floats (This has been actually implemented in Swift 2):

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

Then you will be able to call directly

var roundedF: CGFloat = ceil(f)

while preserving type safety.

I actually believe this should be the solution chosen by Apple, instead of having separate ceil and ceilf functions because they don't make sense in Swift.

Hotchpot answered 12/6, 2014 at 12:5 Comment(8)
Works, but to me it seems like Matt's solution is faster. More messy, but faster.Fractious
@Oskar Check the 2nd solution, I have just added.Hotchpot
With the 2nd solution I still get the error message: 'NSNumber' is not a subtype of 'CGFloat'Fractious
Just the function you gave me with var f: CGFloat = 2.5. Try building that for an iPhone 5S.Fractious
@Oskar I am building it without problems. The literal type 2.5 is correctly inferred as CGFloat.Hotchpot
This (Sulthan's first formulation) is the right answer. I don't see why it isn't the accepted one. I give exactly the same formulation (independently) here: https://mcmap.net/q/211776/-should-conditional-compilation-be-used-to-cope-with-difference-in-cgfloat-on-different-architectures I don't see why this is "messy", and certainly not why it is messier than Matt Gibson's answer. The fact is that it is Swift that is messy. None of this should be necessary; we need intelligent automatic casting of numeric types. File enhancement requests, everybody.Seek
@Seek He didn't say it's messier, he saied it's slower :) I am not sure why the 2nd part of my answer doesn't work for the OP. It seems to be the best solution for me.Hotchpot
I put your solution in CGFloat's extension. Worked well! extension CGFloat { func roundDown() -> CGFloat { return CGFloat(floor(Double(self))) } func roundUp() -> CGFloat { return CGFloat(ceil(Double(self))) } } Now, I can call myFloat.roundDown() or myFloat.roundUp()! Thanks, manCantrip
H
10

from Swift Standard Library you can round it in-place as well:

var value: CGFloat = -5.7
value.round(.up) // -5.0
Honaker answered 12/6, 2014 at 10:26 Comment(4)
println("\(roundUp(Double(180.0)))") //prints 181, should print 180Fractious
you are totally right, I've corrected the first branch.Honaker
How to get decimal value 0.7 from given CGFloat -5.7?Liszt
would you want me to provide a formula like (-5.0) - (-5.7) = 0.7 or something?Honaker
R
6

Building off of holex's answer. I did

func accurateRound(value: Double) -> Int {

            var d : Double = value - Double(Int(value))

            if d < 0.5 {
                return Int(value)
            } else {
                return Int(value) + 1
            }
        }

-edit extension edition-

I also recently turned this into an extension for Floats thought I'd share as well :)

extension Float {
    func roundToInt() -> Int{
        var value = Int(self)
        var f = self - Float(value)
        if f < 0.5{
            return value
        } else {
            return value + 1
        }
    }
}

This is makes it so you can just be like

var f : Float = 3.3
f.roundToInt()
Remorse answered 17/9, 2014 at 23:11 Comment(0)
T
4

Use rounded method.

Demo (swift 5.5.2) :

CGFloat(5.0).rounded(.up) // -> 5

CGFloat(5.1).rounded(.up) // -> 6
CGFloat(5.2).rounded(.up) // -> 6
CGFloat(5.3).rounded(.up) // -> 6
CGFloat(5.4).rounded(.up) // -> 6
CGFloat(5.5).rounded(.up) // -> 6
CGFloat(5.6).rounded(.up) // -> 6
CGFloat(5.7).rounded(.up) // -> 6
CGFloat(5.8).rounded(.up) // -> 6
CGFloat(5.9).rounded(.up) // -> 6
Tryst answered 23/3, 2022 at 16:14 Comment(1)
This should be the accepted answerNeva

© 2022 - 2024 — McMap. All rights reserved.