Conversion between CGFloat and NSNumber without unnecessary promotion to Double
Update: One can cast a CGFloat
value to NSNumber
and back:
let c1 = CGFloat(12.3)
let num = c1 as NSNumber
let c2 = num as CGFloat
This preserves the precision of CGFloat
and works with Swift 2
and Swift 3.
(Previous answer – far too complicated): There are two solutions that I found. The first uses the toll-free bridging
between NSNumber
and CFNumber
(as in What is most common and correct practice to get a CGFloat from an NSNumber?
for Objective-C). It uses the fact that CFNumber
has a dedicated
conversion mode for CGFloat
values:
extension NSNumber {
// CGFloat -> NSNumber
class func numberWithCGFloat(var value: CGFloat) -> NSNumber {
return CFNumberCreate(nil , .CGFloatType, &value)
}
// NSNumber -> CGFloat
var cgFloatValue : CGFloat {
var value : CGFloat = 0
CFNumberGetValue(self, .CGFloatType, &value)
return value
}
}
That is simple and nice. The only drawback: I could not figure out
how to make the constructor an init
method instead of a class method
.
The second possible solution is a bit longer:
extension NSNumber {
// CGFloat -> NSNumber
private convenience init(doubleOrFloat d : Double) {
self.init(double : d)
}
private convenience init(doubleOrFloat f : Float) {
self.init(float : f)
}
convenience init(cgFloat : CGFloat) {
self.init(doubleOrFloat: cgFloat.native)
}
// NSNumber -> CGFloat
private func doubleOrFloatValue() -> Double {
return self.doubleValue
}
private func doubleOrFloatValue() -> Float {
return self.floatValue
}
var cgFloatValue : CGFloat {
return CGFloat(floatLiteral: doubleOrFloatValue())
}
}
There are two private "helper" init methods with the same external
parameter name doubleOrFloat
but different parameter types. From the actual
type of cgFloat.native
the compiler determines which one to call
in
convenience init(cgFloat : CGFloat) {
self.init(doubleOrFloat: cgFloat.native)
}
Same idea in the accessor method. From the type of self.native
the compiler determines which of the two doubleOrFloatValue()
methods to call in
var cgFloatValue : CGFloat {
return CGFloat(floatLiteral: doubleOrFloatValue())
}
NSNumber from CGFloat
I believe @
NSNumber
literal is good enough
@(myCGFloat)
Read more here: http://clang.llvm.org/docs/ObjectiveCLiterals.html
Convert CGFloat to NSNumber in Swift
In Swift 3.0
let myFloat : CGFloat = 1234.5
let myNumber = NSNumber(value: Float(myFloat))
or
let myNumber = NSNumber(value: Double(myFloat))
In Swift 2
let myNumber = NSNumber(double: myFloat.native)
or
let myNumber = NSNumber(double: Double(myFloat))
or
let myNumber = NSNumber(float: Float(myFloat))
What is most common and correct practice to get a CGFloat from an NSNumber?
This will get the correct result in any case:
NSNumber *n = @42.42;
CGFloat cgf = [n doubleValue];
because CGFloat
is either float
or double
.
NSNumber
does not have a CGFloatValue
method. You could define one
using the toll-free bridge to CFNumberRef
:
@interface NSNumber (MyCGFloatValue)
-(CGFloat)myCGFloatValue;
@end
@implementation NSNumber (MyCGFloatValue)
-(CGFloat)myCGFloatValue{
CGFloat result;
CFNumberGetValue((__bridge CFNumberRef)(self), kCFNumberCGFloatType, &result);
return result;
}
@end
or using the C11 feature "Generic selection", where the compiler chooses the appropriate
code depending on the type of CGFloat
:
@implementation NSNumber (MyCGFloatValue)
-(CGFloat)myCGFloatValue{
CGFloat result;
result = _Generic(result,
double: [self doubleValue],
float: [self floatValue]);
return result;
}
@end
And then
NSNumber *n = @42.24;
CGFloat f = [n myCGFloatValue];
but I doubt that it is worth the hassle.
How to get this value as a CGFloat?
In Swift:
let f = view.layer.valueForKeyPath("transform.rotation.z")!.floatValue
In Objective-C:
NSNumber* n = [self.layer valueForKeyPath:@"transform.rotation.z"];
CGFloat f = [n floatValue];
Swift equivalent of ceilf for CGFloat
The Swift way to do this now is to use rounded(.up)
(or round(.up)
if you want to change the variable in place). Behind the scenes it is using ceil
, which can take a CGFloat
as a parameter and is architecture independent.
let myCGFloat: CGFloat = 3.001
let y = myCGFloat.rounded(.up) // 4.0
This is equivalent to
var myCGFloat: CGFloat = 3.001
let y = ceil(myCGFloat) // 4.0
Any use of ceilf
is no longer necessary.
See also my fuller answer on CGFloat
with various rounding rules.
Is it safe to set a CGFloat to a double?
Remember that your code is compiled twice, once for 32-bit and once for 64-bit.
When compiling for 32-bit, your code looks like this:
float myDouble = 123.45;
So, the answer to your question is yes, the compiler knows how to handle that code and it will work just fine.
What you do need to watch for is the loss of precision when a 64-bit double value is assigned to a 32-bit float variable. In that case, the compiler will generate a warning for you about the loss of precision, but you will only see the warning when compiling the 32-bit version of your code.
Should conditional compilation be used to cope with difference in CGFloat on different architectures?
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
Should conditional compilation be used to cope with difference in CGFloat on different architectures?
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
Related Topics
Get Png Representation of Nsimage in Swift
Naming Convention for Private Properties
Passing in Variable Number of Args from One Function to Another in Swift
Combined Watch Os and iOS Framework
Swift: Urlsession.Shared.Datatask Says Status Code 304 = 200
Vapor 3 - How to Check for Similar Email Before Saving Object
The #Selector Is Not Compatible with The Closure
Switch to Match Multiple Cases from Optionsettype
Why Is Inceptionv3 Machine Learning Model Not Recognized on My Project
Apple Watch and iPhone Are Not Connected When The App in Phone Goes to Background
Set an Horizontal Scroll to My Barchart in Swift
Swift Wkwebview: Can't Find Variable Error When Calling Method