How to convert a pair of bytes into a Float using Swift - swift

I am using this article to communicate with an IoT sensor via BLE. In the article, this quote is mentioned:
The first two bytes do not seem to belong to the data (probably a prefix to denote that it is a data packet), but the remaining ones are more interesting. For the accelerometer, we get three signed 16 bit integers (little endian), which can simply be scaled to the range we set up to get our setup sequence. So the +/-2^15 range of the signed 16bit integer corresponds to the +/-16g, resulting in a factor 1/2048. To get the acceleration in m/s², we apply a factor of 9.81/2048. So, the corresponding bluetooth part reads:
<output char="326a9006-85cb-9195-d9dd-464cfbbae75a" conversion="int16LittleEndian" offset="2" length="2">accXRaw</output>
<output char="326a9006-85cb-9195-d9dd-464cfbbae75a" conversion="int16LittleEndian" offset="4" length="2">accYRaw</output>
<output char="326a9006-85cb-9195-d9dd-464cfbbae75a" conversion="int16LittleEndian" offset="6" length="2">accZRaw</output>
To read this code, I am running this Swift code:
private func sensor(from characteristic: CBCharacteristic) {
guard let characteristicData = characteristic.value,
let _ = characteristicData.first else { return }
let data = characteristic.value!
var values = [UInt8](repeating: 0, count: data.count)
data.copyBytes(to: &values, count: data.count)
print("values = \(values)")
}
The result once I do a print is:
values = [3, 4, 250, 255, 199, 249, 91, 191]
Alike the article mentions, I can confirm that the first two bytes do not belong to any data, and are consistently repeating. Bytes values[2-7] are constantly changing, which makes me more confident that the pairs represent accXRaw, accYRaw, and accZRaw. What I want to do now is convert the pairs to doubles.
For example:
values[2], values[3] = [250 255] (accXRaw)
values[4], values[5] = [199 249] (accYRaw)
values[6], values[7] = [91 191] (accZRaw)
In the article, the author does this via a int16 little endian. I want to do the same with swift 5, but not sure if I am doing it correctly. Here is my code:
let xAxis = Float(bitPattern: UInt32(littleEndian: [values[2], values[3], 0x00, 0x00].withUnsafeBytes { $0.load(as: UInt32.self) }))
let yAxis = Float(bitPattern: UInt32(littleEndian: [values[4], values[5], 0x00, 0x00].withUnsafeBytes { $0.load(as: UInt32.self) }))
let zAxis = Float(bitPattern: UInt32(littleEndian: [values[6], values[7], 0x00, 0x00].withUnsafeBytes { $0.load(as: UInt32.self) }))
print("x=\(xAxis), y=\(yAxis), z=\(zAxis)");
The resulting printout is:
values = [3, 4, 250, 255, 199, 249, 91, 191]
x=9.1827e-41, y=8.9603e-41, z=6.8645e-41
These numbers just look weird, and I suspect I am doing something wrong. Am I reading the byte pairs correctly ( at least in line with the article ) ? If not, what mistakes did I make?

Your issue there is that you are not suppose to initialize your Float using the bitPattern initializer and/or use the UInt32(littleEndian:) initializer. What you need is to convert those 2 bytes to Int16, coerce it to Float and then multiply by the factor of 9.81/2048 to get its acceleration.
Expanding on that, you can create a Numeric initializer that takes an object that conforms to DataProtocol (Data or Bytes [UInt8]):
extension Numeric {
init<D: DataProtocol>(_ data: D) {
var value: Self = .zero
let size = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(size == MemoryLayout.size(ofValue: value))
self = value
}
}
Then you can initialize your Int16 object with the subdata (two bytes).
let bytes: [UInt8] = [3, 4, 250, 255, 199, 249, 91, 191]
let xData = bytes[2..<4]
let yData = bytes[4..<6]
let zData = bytes[6..<8]
let factor: Float = 9.81/2048
let xAxis = Float(Int16(xData)) * factor
let yAxis = Float(Int16(yData)) * factor
let zAxis = Float(Int16(zData)) * factor
print("x:", xAxis, "y:", yAxis, "z:", zAxis) // x: -0.028740235 y: -7.6305327 z: -79.27036

Related

What is the correct way in swift to wrap a segment of a UInt8 array as a String?

I have some raw data processing to do in an iPhone app. Strings always come out of an extremely large underlying byte array, so I want to be able to pull strings out of the array without triggering out of memory issues.
I can see a String(bytesNoCopy: ...) in the documentation, is this what I want, and how exactly is it supposed to be used?
Assuming an array of uint8 called data and index is a number which shows where the string is inside the array.
var myData:[UInt8] = [
4, // String 1 length
65,66,67,68,0, // String 1 data
4, // String 2 length
69,70,71,71,0 // String 2 data
]
var index = 0
let string1 = readString(&myData, &index)
let string2 = readString(&myData, &index)
print(string1, string2)
// Read a string located at a specific
// position in a byte array, and increment
// the pointer into the array into the next
// position
func readString(_ data:inout [UInt8], _ index:inout Int) -> String {
// Read string length out of data array
let l = Int(readUInt8(&data, &index))
// Read string out of data array without copy
let s = String(
bytesNoCopy: UnsafeMutableRawPointer(data + index), // <-- what goes here??
length: l,
encoding: .utf8,
freeWhenDone: false)
index = index + l
if s == nil {
return ""
}
return s!
}
// Read a byte as an integer from a
// data array, and increment the pointer into
// the data array to the next position.
func readUInt8(_ data:inout [UInt8], _ x:inout Int) -> UInt8 {
let v = data[x]
x = x + 1
return v
}
NOTE: This question is updated to include sample data, and renamed the variable x to index to make it clearer that the question was asking how to create a string from a segment of a byte array.
Here's how you can do try this -
import Foundation
func readString(_ data: inout [UInt8], _ x: inout Int) -> String {
let l = 4
var slice: ArraySlice<UInt8> = data[x..<x+l] // No copy, view into existing Array
x += l
return slice.withUnsafeBytes({ pointer in
// No copy, just making compiler happy (assumption that it is bound to UInt8 is correct
if let bytes = pointer.baseAddress?.assumingMemoryBound(to: UInt8.self) {
return String(
bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes), // No copy
length: slice.count,
encoding: .utf8,
freeWhenDone: false
) ?? ""
} else {
return ""
}
})
}
Test
var a: [UInt8] = [
65, 66, 67, 68,
69, 70, 71, 72
]
var x = 0
let test1 = readString(&a, &x)
print("test1 : \(test1)")
// test1 : ABCD
let test2 = readString(&a, &x)
print("test2 : \(test2)")
// test2 : EFGH

Convert Int to Array of UInt8 in swift

I want to convert a standard integer in a list of UInt8 in swift.
var x:Int = 2019
2019 can be written (for example) in hexadecimal 7E3 so i want some kind of function that converts is to a list of UInt8s which looks like this.
var y:[Uint8] = [0x07, 0xE3]
I already found this: Convert integer to array of UInt8 units but he/she is convertign the ascii symbols of the number not the number itself. So his example 94887253 should give a list like [0x05, 0xA7, 0xDD, 0x55].
In the best case the function i'm looking for has some kind of usage so that i can also choose the minimum length of the resulting array so that for example
foo(42, length:2) -> [0x00, 0x2A]
or
foo(42, length:4) -> [0x00, 0x00, 0x00, 0x2A]
You could do it this way:
let x: Int = 2019
let length: Int = 2 * MemoryLayout<UInt8>.size //You could specify the desired length
let a = withUnsafeBytes(of: x) { bytes in
Array(bytes.prefix(length))
}
let result = Array(a.reversed()) //[7, 227]
Or more generally, we could use a modified version of this snippet:
func bytes<U: FixedWidthInteger,V: FixedWidthInteger>(
of value : U,
to type : V.Type,
droppingZeros: Bool
) -> [V]{
let sizeInput = MemoryLayout<U>.size
let sizeOutput = MemoryLayout<V>.size
precondition(sizeInput >= sizeOutput, "The input memory size should be greater than the output memory size")
var value = value
let a = withUnsafePointer(to: &value, {
$0.withMemoryRebound(
to: V.self,
capacity: sizeInput,
{
Array(UnsafeBufferPointer(start: $0, count: sizeInput/sizeOutput))
})
})
let lastNonZeroIndex =
(droppingZeros ? a.lastIndex { $0 != 0 } : a.indices.last) ?? a.startIndex
return Array(a[...lastNonZeroIndex].reversed())
}
let x: Int = 2019
bytes(of: x, to: UInt8.self, droppingZeros: true) // [7, 227]
bytes(of: x, to: UInt8.self, droppingZeros: false) // [0, 0, 0, 0, 0, 0, 7, 227]

How to convert Int to byte array of 4 bytes in Swift?

I'm using Swift and trying to convert an Int (for example: -1333) to a byte array of 4 bytes. I was able to convert an Int to an array of 8 bytes (-1333 becomes [255, 255, 255, 255, 255, 255, 250, 203]), but I need it to be 4 bytes. I know that there are ways to do this in other languages like Java, but is there a way for Swift? Here's my code: (I used THIS answer)
func createByteArray(originalValue: Int)->[UInt8]{
var result:[UInt8]=Array()
var _number:Int = originalValue
let mask_8Bit=0xFF
var c=0
var size: Int = MemoryLayout.size(ofValue: originalValue)
for i in (0..<size).reversed(){
//at: 0 -> insert at the beginning of the array
result.insert(UInt8( _number&mask_8Bit),at:0)
_number >>= 8 //shift 8 times from left to right
}
return result
}
In Java an integer is always 32-bit, but in Swift it can be 32-bit or 64-bit, depending on the platform. Your code creates a byte array with the same size as that of the Int type, on a 64-bit platform that are 8 bytes.
If you want to restrict the conversion to 32-bit integers then use Int32 instead of Int, the result will then be an array of 4 bytes, independent of the platform.
An alternative conversion method is
let value: Int32 = -1333
let array = withUnsafeBytes(of: value.bigEndian, Array.init)
print(array) // [255, 255, 250, 203]
Or as a generic function for integer type of all sizes:
func byteArray<T>(from value: T) -> [UInt8] where T: FixedWidthInteger {
withUnsafeBytes(of: value.bigEndian, Array.init)
}
Example:
print(byteArray(from: -1333)) // [255, 255, 255, 255, 255, 255, 250, 203]
print(byteArray(from: Int32(-1333))) // [255, 255, 250, 203]
print(byteArray(from: Int16(-1333))) // [250, 203]
However, not like Java using big endian, iOS platform uses 'little endian' instead.
So if it would be
let value: Int32 = -1333
let array2 = withUnsafeBytes(of: value.littleEndian, Array.init)// [203, 250, 255, 255]
You can verify it by wrapping the value into data with below extension and check the bytes
extension FixedWidthInteger {
var data: Data {
let data = withUnsafeBytes(of: self) { Data($0) }
return data
}
}
value.data
Just a reminder for those iOS developers who are digging into memory layout.
It's hard for beginners to understand what is going on in the answers above
public extension FixedWidthInteger {
var bytes: [UInt8] {
withUnsafeBytes(of: bigEndian, Array.init)
}
}
First of all lets rewrite computed property bytes more detailed
var bytes: [UInt8] {
var copyOfSelf = self.bigEndian // this defines the order of bytes in result array
// for int '1' it can be [0, 0, 0, 0, 0, 0, 0, 1]
// or [1, 0, 0, 0, 0, 0, 0, 0]
return withUnsafeBytes(of: copyOfSelf) { urbp: UnsafeRawBufferPointer in
// Array has a constructor
// init<S>(_ s: S) where Element == S.Element, S : Sequence
// so 'UnsafeRawBufferPointer' confirms 'Sequence' protocol
// and 'urbp' can be passed as a parameter to Array constructor
return Array(urbp)
}
}
And here some tests to explain the result
final class FixedWidthIntegerTests: XCTestCase {
func testBytes() {
XCTAssertEqual([0,0,0,0,0,0,0,1], Int(1).bytes)
XCTAssertEqual([255,255,255,255,255,255,255,255], UInt.max.bytes)
XCTAssertEqual([127,255,255,255,255,255,255,255], Int.max.bytes)
XCTAssertEqual([1], Int8(1).bytes)
XCTAssertEqual([0,1], Int16(1).bytes)
XCTAssertEqual([0,0,0,1], Int32(1).bytes)
}
}

How to convert my bytes data to Hex String and then Signed integer (32-bit) Two's complement from that ..?

I have a data like a bellow:
let data = Data(bytes: [206, 66, 49, 62])
Then I used this extension (from How to convert Data to hex string in swift) to convert to a hex string:
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef").utf16)
var chars: [unichar] = []
chars.reserveCapacity(2 * count)
for byte in self {
chars.append(hexDigits[Int(byte / 16)])
chars.append(hexDigits[Int(byte % 16)])
}
return String(utf16CodeUnits: chars, count: chars.count)
}
}
And then it is giving "ce42313e" as hex string. Now I am trying to convert this to Signed integer (32-bit) Two's complement .. I tried a couple of ways but not find anything perfectly.
When I give "ce42313e" in this bellow link under hex decimal the value is -834522818
http://www.binaryconvert.com/convert_signed_int.html
bellow is one of those I tried to convert "ce42313e" to int and it's giving me 3460444478 ..instead of -834522818 .
let str = value
let number = Int(str, radix: 16)
Please help out to get that value.
Int(str, radix: 16) interprets the string as the hexadecimal
representation of an unsigned number. You could convert it to
Int32 with
let data = Data(bytes: [206, 66, 49, 62])
let str = data.hexEncodedString()
print(str) // ce42313e
let number = Int32(truncatingBitPattern: Int(str, radix: 16)!)
print(number) // -834522818
But actually you don't need the hex representation for that purpose.
Your data is the big-endian representation of a signed 32-bit integer,
and this is how you can get the number from the data directly:
let data = Data(bytes: [206, 66, 49, 62])
let number = Int32(bigEndian: data.withUnsafeBytes { $0.pointee })
print(number) // -834522818

How to convert Data to hex string in swift

I want the hexadecimal representation of a Data value in Swift.
Eventually I'd want to use it like this:
let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)
A simple implementation (taken from How to hash NSString with SHA1 in Swift?, with an additional option for uppercase output) would be
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return self.map { String(format: format, $0) }.joined()
}
}
I chose a hexEncodedString(options:) method in the style of the existing method base64EncodedString(options:).
Data conforms to the Collection protocol, therefore one can use
map() to map each byte to the corresponding hex string.
The %02x format prints the argument in base 16, filled up to two digits
with a leading zero if necessary. The hh modifier causes the argument
(which is passed as an integer on the stack) to be treated as a one byte
quantity. One could omit the modifier here because $0 is an unsigned
number (UInt8) and no sign-extension will occur, but it does no harm leaving
it in.
The result is then joined to a single string.
Example:
let data = Data([0, 1, 127, 128, 255])
// For Swift < 4.2 use:
// let data = Data(bytes: [0, 1, 127, 128, 255])
print(data.hexEncodedString()) // 00017f80ff
print(data.hexEncodedString(options: .upperCase)) // 00017F80FF
The following implementation is faster by a factor about 50
(tested with 1000 random bytes). It is inspired to
RenniePet's solution
and Nick Moore's solution, but takes advantage of
String(unsafeUninitializedCapacity:initializingUTF8With:)
which was introduced with Swift 5.3/Xcode 12 and is available on macOS 11 and iOS 14 or newer.
This method allows to create a Swift string from UTF-8 units efficiently, without unnecessary copying or reallocations.
An alternative implementation for older macOS/iOS versions is also provided.
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef"
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
let utf8Digits = Array(hexDigits.utf8)
return String(unsafeUninitializedCapacity: 2 * self.count) { (ptr) -> Int in
var p = ptr.baseAddress!
for byte in self {
p[0] = utf8Digits[Int(byte / 16)]
p[1] = utf8Digits[Int(byte % 16)]
p += 2
}
return 2 * self.count
}
} else {
let utf16Digits = Array(hexDigits.utf16)
var chars: [unichar] = []
chars.reserveCapacity(2 * self.count)
for byte in self {
chars.append(utf16Digits[Int(byte / 16)])
chars.append(utf16Digits[Int(byte % 16)])
}
return String(utf16CodeUnits: chars, count: chars.count)
}
}
}
This code extends the Data type with a computed property. It iterates through the bytes of data and concatenates the byte's hex representation to the result:
extension Data {
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
My version. It's about 10 times faster than the [original] accepted answer by Martin R.
public extension Data {
private static let hexAlphabet = Array("0123456789abcdef".unicodeScalars)
func hexStringEncoded() -> String {
String(reduce(into: "".unicodeScalars) { result, value in
result.append(Self.hexAlphabet[Int(value / 0x10)])
result.append(Self.hexAlphabet[Int(value % 0x10)])
})
}
}
Swift 4 - From Data to Hex String
Based upon Martin R's solution but even a tiny bit faster.
extension Data {
/// A hexadecimal string representation of the bytes.
func hexEncodedString() -> String {
let hexDigits = Array("0123456789abcdef".utf16)
var hexChars = [UTF16.CodeUnit]()
hexChars.reserveCapacity(count * 2)
for byte in self {
let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
hexChars.append(hexDigits[index1])
hexChars.append(hexDigits[index2])
}
return String(utf16CodeUnits: hexChars, count: hexChars.count)
}
}
Swift 4 - From Hex String to Data
I've also added a fast solution for converting a hex String into Data (based on a C solution).
extension String {
/// A data representation of the hexadecimal bytes in this string.
func hexDecodedData() -> Data {
// Get the UTF8 characters of this string
let chars = Array(utf8)
// Keep the bytes in an UInt8 array and later convert it to Data
var bytes = [UInt8]()
bytes.reserveCapacity(count / 2)
// It is a lot faster to use a lookup map instead of strtoul
let map: [UInt8] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // #ABCDEFG
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // HIJKLMNO
]
// Grab two characters at a time, map them and turn it into a byte
for i in stride(from: 0, to: count, by: 2) {
let index1 = Int(chars[i] & 0x1F ^ 0x10)
let index2 = Int(chars[i + 1] & 0x1F ^ 0x10)
bytes.append(map[index1] << 4 | map[index2])
}
return Data(bytes)
}
}
Note: this function does not validate the input. Make sure that it is only used for hexadecimal strings with (an even amount of) characters.
Backward compatible and fast solution:
extension Data {
/// Fast convert to hex by reserving memory (instead of mapping and join).
public func toHex(uppercase: Bool = false) -> String {
// Constants (Hex has 2 characters for each Byte).
let size = self.count * 2;
let degitToCharMap = Array((
uppercase ? "0123456789ABCDEF" : "0123456789abcdef"
).utf16);
// Reserve dynamic memory (plus one for null termination).
let buffer = UnsafeMutablePointer<unichar>.allocate(capacity: size + 1);
// Convert each byte.
var index = 0
for byte in self {
buffer[index] = degitToCharMap[Int(byte / 16)];
index += 1;
buffer[index] = degitToCharMap[Int(byte % 16)];
index += 1;
}
// Set Null termination.
buffer[index] = 0;
// Casts to string (without any copying).
return String(utf16CodeUnitsNoCopy: buffer,
count: size, freeWhenDone: true)
}
}
Note that above passes ownership of buffer to returned String object.
Also know that, because Swift's internal String data is UTF16 (but can be UTF8 since Swift 5), all solutions provided in accepted answer do full copy (and are slower), at least if NOT #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) ;-)
As mentioned on my profile, usage under Apache 2.0 license is allowed too (without attribution need).
This doesn't really answer the OP's question since it works on a Swift byte array, not a Data object. And it's much bigger than the other answers. But it should be more efficient since it avoids using String(format: ).
Anyway, in the hopes someone finds this useful ...
public class StringMisc {
// MARK: - Constants
// This is used by the byteArrayToHexString() method
private static let CHexLookup : [Character] =
[ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ]
// Mark: - Public methods
/// Method to convert a byte array into a string containing hex characters, without any
/// additional formatting.
public static func byteArrayToHexString(_ byteArray : [UInt8]) -> String {
var stringToReturn = ""
for oneByte in byteArray {
let asInt = Int(oneByte)
stringToReturn.append(StringMisc.CHexLookup[asInt >> 4])
stringToReturn.append(StringMisc.CHexLookup[asInt & 0x0f])
}
return stringToReturn
}
}
Test case:
// Test the byteArrayToHexString() method
let byteArray : [UInt8] = [ 0x25, 0x99, 0xf3 ]
assert(StringMisc.byteArrayToHexString(byteArray) == "2599F3")
A bit different from other answers here:
extension DataProtocol {
func hexEncodedString(uppercase: Bool = false) -> String {
return self.map {
if $0 < 16 {
return "0" + String($0, radix: 16, uppercase: uppercase)
} else {
return String($0, radix: 16, uppercase: uppercase)
}
}.joined()
}
}
However in my basic XCTest + measure setup this was fastest of the 4 I tried.
Going through a 1000 bytes of (the same) random data 100 times each:
Above: Time average: 0.028 seconds, relative standard deviation: 1.3%
MartinR: Time average: 0.037 seconds, relative standard deviation: 6.2%
Zyphrax: Time average: 0.032 seconds, relative standard deviation: 2.9%
NickMoore: Time average: 0.039 seconds, relative standard deviation: 2.0%
Repeating the test returned the same relative results. (Nick and Martins sometimes swapped)
Edit:
Nowadays I use this:
var hexEncodedString: String {
return self.reduce(into:"") { result, byte in
result.append(String(byte >> 4, radix: 16))
result.append(String(byte & 0x0f, radix: 16))
}
}
Maybe not the fastest, but data.map({ String($0, radix: 16) }).joined() does the job. As mentioned in the comments, this solution was flawed.