Round up a CGFloat in Swift

2019-01-13 17:00发布

问题:

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'

回答1:

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


回答2:

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.



回答3:

With Swift 3, according to your needs, you may choose one of the 4 following ways to round up a CGFloat.


#1. Using FloatingPoint protocol rounded(_:) method

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

func rounded(_ rule: FloatingPointRoundingRule) -> Self

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

The Playground 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:

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

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 NSDecimalNumber

NSDecimalNumber offers a verbose but powerful solution for rounding numbers.

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 scale: Int16 = 0
let behavior = NSDecimalNumberHandler(roundingMode: NSDecimalNumber.RoundingMode.up, scale: scale, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: true)

let roundedValue1 = NSDecimalNumber(value: Double(value1)).rounding(accordingToBehavior: behavior)
let roundedValue2 = NSDecimalNumber(value: Double(value2)).rounding(accordingToBehavior: behavior)
let roundedValue3 = NSDecimalNumber(value: Double(value3)).rounding(accordingToBehavior: behavior)
let roundedValue4 = NSDecimalNumber(value: Double(value4)).rounding(accordingToBehavior: behavior)
let roundedValue5 = NSDecimalNumber(value: Double(value5)).rounding(accordingToBehavior: behavior)
let roundedValue6 = NSDecimalNumber(value: Double(value6)).rounding(accordingToBehavior: behavior)

print(roundedValue1) // prints 0
print(roundedValue2) // prints 0
print(roundedValue3) // prints -1
print(roundedValue4) // prints 1
print(roundedValue5) // prints 1
print(roundedValue6) // prints 1

#4. 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")


回答4:

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()


回答5:

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

var value: CGFloat = -5.7
value.round(.up) // -5.0