Swift: extract float from byte data
The floating point types have a static _fromBitPattern
that will return a value. <Type>._BitsType
is a type alias to the correctly sized unsigned integer:
let data: [Byte] = [0x00, 0x00, 0x00, 0x40, 0x86, 0x66, 0x66, 0x00]
let dataPtr = UnsafePointer<Byte>(data)
let byteOffset = 3
let bits = UnsafePointer<Float._BitsType>(dataPtr + byteOffset)[0].bigEndian
let f = Float._fromBitPattern(bits)
You don't see that method in auto-completion, but it's a part of the FloatingPointType
protocol. There's an instance method that will give you back the bits, called ._toBitPattern()
.
Extract and Convert NSData from BLE to Float in Swift
On all current iOS and OS X platforms, char
is a signed quantity,
so that the input fe
is treated as a negative number.
Byte
on the other hand is an alias for UInt8
which is unsigned.
Use [Int8]
array instead to get the same behaviour as in your Objective-C code.
How to convert Int bit pattern to Float in Swift?
UPDATE: In Swift 3, we can now do:
Float(bitPattern: myInt)
After doing some digging with the help of @MartinR, there appear to be two (rather ugly) Swift solutions:
Float._fromBitPattern(myInt)
and
unsafeBitCast(myInt, Float.self)
The former solution no longer appears to be documented in the public API. Therefore, the latter seems like the better approach.
round trip Swift number types to/from Data
Note: The code has been updated for Swift 5 (Xcode 10.2) now. (Swift 3 and Swift 4.2 versions can be found in the edit history.) Also possibly unaligned data is now correctly handled.
How to create Data
from a value
As of Swift 4.2, data can be created from a value simply with
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
Explanation:
withUnsafeBytes(of: value)
invokes the closure with a buffer pointer covering the raw bytes of the value.- A raw buffer pointer is a sequence of bytes, therefore
Data($0)
can be used to create the data.
How to retrieve a value from Data
As of Swift 5, the withUnsafeBytes(_:)
of Data
invokes the closure with an “untyped” UnsafeMutableRawBufferPointer
to the bytes. The load(fromByteOffset:as:)
method the reads the value from the memory:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value) // 42.13
There is one problem with this approach: It requires that the memory is property aligned for the type (here: aligned to a 8-byte address). But that is not guaranteed, e.g. if the data was obtained as a slice of another Data
value.
It is therefore safer to copy the bytes to the value:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
Explanation:
withUnsafeMutableBytes(of:_:)
invokes the closure with a mutable buffer pointer covering the raw bytes of the value.- The
copyBytes(to:)
method ofDataProtocol
(to whichData
conforms) copies bytes from the data to that buffer.
The return value of copyBytes()
is the number of bytes copied. It is equal to the size of the destination buffer, or less if the data does not contain enough bytes.
Generic solution #1
The above conversions can now easily be implemented as generic methods of struct Data
:
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
var value: T = 0
guard count >= MemoryLayout.size(ofValue: value) else { return nil }
_ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
return value
}
}
The constraint T: ExpressibleByIntegerLiteral
is added here so that we can easily initialize the value to “zero” – that is not really a restriction because this method can be used with “trival” (integer and floating point) types anyway, see below.
Example:
let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = data.to(type: Double.self) {
print(roundtrip) // 42.13
} else {
print("not enough data")
}
Similarly, you can convert arrays to Data
and back:
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data($0) }
}
func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
_ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
return array
}
}
Example:
let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData) // <0100ff7f 0080>
let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]
Generic solution #2
The above approach has one disadvantage: It actually works only with "trivial"
types like integers and floating point types. "Complex" types like Array
and String
have (hidden) pointers to the underlying storage and cannot be
passed around by just copying the struct itself. It also would not work with
reference types which are just pointers to the real object storage.
So solve that problem, one can
Define a protocol which defines the methods for converting to
Data
and back:protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}Implement the conversions as default methods in a protocol extension:
extension DataConvertible where Self: ExpressibleByIntegerLiteral{
init?(data: Data) {
var value: Self = 0
guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
self = value
}
var data: Data {
return withUnsafeBytes(of: self) { Data($0) }
}
}I have chosen a failable initializer here which checks that the number of bytes provided
matches the size of the type.And finally declare conformance to all types which can safely be converted to
Data
and back:extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
// add more types here ...
This makes the conversion even more elegant:
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
The advantage of the second approach is that you cannot inadvertently do unsafe conversions. The disadvantage is that you have to list all "safe" types explicitly.
You could also implement the protocol for other types which require a non-trivial conversion, such as:
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
// Note: a conversion to UTF-8 cannot fail.
return Data(self.utf8)
}
}
or implement the conversion methods in your own types to do whatever is
necessary so serialize and deserialize a value.
Byte order
No byte order conversion is done in the above methods, the data is always in
the host byte order. For a platform independent representation (e.g.
“big endian” aka “network” byte order), use the corresponding integer
properties resp. initializers. For example:
let value = 1000
let data = value.bigEndian.data
print(data as NSData) // <00000000 000003e8>
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip)) // 1000
}
Of course this conversion can also be done generally, in the generic
conversion method.
How to parse a float, sent by a Bluetooth device, in Swift?
Assuming you are following the standard IEEE format, which really looks you are — I have a hard time believing this Bluetooth device of yours would use anything else on the wire — then try this initializer instead (available since Swift 3):
init(
sign: FloatingPointSign,
exponentBitPattern: UInt,
significandBitPattern: UInt32
)
This is defined by the BinaryFloatingPoint
protocol:
The values passed as
exponentBitPattern
andsignificandBitPattern
are interpreted in the binary interchange format defined by the IEEE 754 specification.
The IEEE 754 mentioned above for (single precision) Float
is:
- Sign bit: 1 bit
- Exponent width: 8 bits
- Significand precision: 24 bits (23 explicitly stored)
This other Float
initializer should work fine as well:
init(bitPattern: UInt32)
In both cases, just watch out for big- vs little-endianness issues and you should be fine ;)
Cleaner way in swift3 to read binary into a Float
A possible solution:
let byteOffset = 1
let result = Data(bytes: floatData[byteOffset..<byteOffset+4]).withUnsafeBytes {
(ptr: UnsafePointer<UInt32>) in
Float(bitPattern: UInt32(bigEndian: ptr.pointee))
}
print(result) // 1.5
The Data
object is created from an array slice, and the
closure is called with a pointer to the data. ptr.pointee
dereferences the data, and UInt32(bigEndian:)
converts it to host byte order.
How can I generate an array of floats from an audio file in Swift
AVAudioFile
built-in to iOS (and OS X), is very convenient and will also do format conversions for you:
import AVFoundation
// ...
let url = NSBundle.mainBundle().URLForResource("your audio file", withExtension: "wav")
let file = try! AVAudioFile(forReading: url!)
let format = AVAudioFormat(commonFormat: .PCMFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: 1, interleaved: false)
let buf = AVAudioPCMBuffer(PCMFormat: format, frameCapacity: 1024)
try! file.readIntoBuffer(buf)
// this makes a copy, you might not want that
let floatArray = Array(UnsafeBufferPointer(start: buf.floatChannelData[0], count:Int(buf.frameLength)))
print("floatArray \(floatArray)\n")
Sadly, for doubles it doesn't seem to be enough to substitute .PCMFormatFloat32
with .PCMFormatFloat64
because AVAudioPCMBuffer
doesn't have a float64ChannelData
method.
update because I don't know swift well
You can avoid copying the array by working with the UnsafeBufferPointer
, which is a perfectly good collection type:
let floatArray = UnsafeBufferPointer(start: buf.floatChannelData[0], count:Int(buf.frameLength))
Swift 3 how to parse DATA
One possible approach is to use the
public func withUnsafeBytes<ResultType, ContentType>(_ body: (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType
method to access and dereference the bytes in the data.
The placeholder type ContentType
can be inferred from the context:
let color: UInt32 = data.subdata(in: 0..<4).withUnsafeBytes { $0.pointee }
// ...
let lat: Double = data.subdata(in: 8..<16).withUnsafeBytes { $0.pointee }
// ...
As of Swift 4 you can use subscripting to extract the data:
let color: UInt32 = data[0..<4].withUnsafeBytes { $0.pointee }
// ...
let lat: Double = data[8..<16].withUnsafeBytes { $0.pointee }
// ...
If all fields are properly aligned for their type then you can
use the
public func load<T>(fromByteOffset offset: Int = default, as type: T.Type) -> T
from UnsafeRawPointer
:
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
let rawPointer = UnsafeRawPointer(bytes)
let color = rawPointer.load(fromByteOffset: 0, as: UInt32.self)
// ...
let lat = rawPointer.load(fromByteOffset: 8, as: Double.self)
// ...
}
On a lower level you can use memcpy
which again works with
arbitrarily aligned data:
var color: UInt32 = 0
var lat: Double = 0
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
memcpy(&color, bytes, 4)
// ...
memcpy(&lat, bytes + 8, 8)
// ...
}
I would probably use the first method, unless performance is an
issue where you can use the second or third, depending on whether
all fields are guaranteed to be aligned for their type or not.
Related Topics
Swift 3 Get Start Index (As Int) of Substring
(Swift) Nstimer Stop When Scrolling
Convert Firdatasnapshot to Custom Type
How to Succinctly Get the First 5 Characters of a String in Swift
How to Add Constraints Programmatically to My Uilabel
Swift Uipasteboard Not Copying Png
Treat *Some* Warnings as Errors in Swift
Select All Text in Textfield Upon Click Swiftui
How to Present Different Navigation Title When Large Title Collapse
How to Add External .Vtt Subtitle File to Avplayerviewcontroller in Tvos
Reading an Inputstream into a Data Object
Convert Float Value to String in Swift
Swift: Loop Over Array Elements and Access Previous and Next Elements
How to Connect Aksequencer to a Akcallbackinstrument
Ycombinator Not Working in Swift