36

With Swift I want to convert bytes from a uint8_t array to an integer.

"C" Example:

char bytes[2] = {0x01, 0x02};
NSData *data = [NSData dataWithBytes:bytes length:2];
NSLog(@"data: %@", data); // data: <0102>

uint16_t value2 = *(uint16_t *)data.bytes;
NSLog(@"value2: %i", value2); // value2: 513

Swift Attempt:

let bytes:[UInt8] = [0x01, 0x02]
println("bytes: \(bytes)") // bytes: [1, 2]
let data = NSData(bytes: bytes, length: 2)
println("data: \(data)") // data: <0102>

let integer1 = *data.bytes // This fails
let integer2 = *data.bytes as UInt16 // This fails

let dataBytePointer = UnsafePointer<UInt16>(data.bytes)
let integer3 = dataBytePointer as UInt16 // This fails
let integer4 = *dataBytePointer as UInt16 // This fails
let integer5 = *dataBytePointer // This fails

What is the correct syntax or code to create a UInt16 value from a UInt8 array in Swift?

I am interested in the NSData version and am looking for a solution that does not use a temp array.

zaph
  • 111,848
  • 21
  • 189
  • 228
  • Surely a new language like Swift has an elegant way to convert data received in an `NSData` to integer variables. That is what I am looking for. – zaph Aug 12 '14 at 15:08
  • 1
    Is the uint16 of [0x01, 0x02] 0x0102 or 0x0201? That depends on the processor's endianness. In java it is defined. In C open. In Swift I do not know. The trick `*(uint16)(uint8[2])` also suffers from address alignments. – Joop Eggen Aug 12 '14 at 15:12
  • @JoopEggen Endian-ness is not the issue. – zaph Aug 12 '14 at 15:15
  • Even better is indexing into the data: `let u16 = UnsafePointer(data.bytes)[index]` – zaph Aug 12 '14 at 16:01

8 Answers8

45

If you want to go via NSData then it would work like this:

let bytes:[UInt8] = [0x01, 0x02]
println("bytes: \(bytes)") // bytes: [1, 2]
let data = NSData(bytes: bytes, length: 2)
print("data: \(data)") // data: <0102>

var u16 : UInt16 = 0 ; data.getBytes(&u16)
// Or:
let u16 = UnsafePointer<UInt16>(data.bytes).memory

println("u16: \(u16)") // u16: 513

Alternatively:

let bytes:[UInt8] = [0x01, 0x02]
let u16 = UnsafePointer<UInt16>(bytes).memory
print("u16: \(u16)") // u16: 513

Both variants assume that the bytes are in the host byte order.

Update for Swift 3 (Xcode 8):

let bytes: [UInt8] = [0x01, 0x02]
let u16 = UnsafePointer(bytes).withMemoryRebound(to: UInt16.self, capacity: 1) {
    $0.pointee
}
print("u16: \(u16)") // u16: 513
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I am interested in the `NSData` version but am looking for a solution that does not use a temp array. – zaph Aug 12 '14 at 15:01
  • @Zaph: Which temp array? Perhaps `let u16 = UnsafePointer(data.bytes).memory` is what you are looking for? – Martin R Aug 12 '14 at 15:05
  • That is exactly what I am looking for! Thanks! `let u16 = UnsafePointer(data.bytes).memory` – zaph Aug 12 '14 at 15:18
  • @Zaph be sure you actually have better performance, and that you actually need that. Using unsafe code could make you have worse performance in the end, because the compiler can become oblivious of what your code does and it can't replace your code with something more efficient. – pqnet Aug 13 '14 at 16:39
  • @pqnet I am not concerned about performance at this level, performance is best addressed when measurements show a need and pinpoint where the problem is. As for safe, sure I would like something safe, please provide such a solution. Keep in mind that in this case and many others in the same class raw data bytes are being received without and JSON, XML or other higher level formatting. – zaph Aug 13 '14 at 17:48
18

In Swift 5 or later you can convert the bytes [UInt8] to UInt16 value using withUnsafeBytes { $0.load(as: UInt16.self) }

let bytes: [UInt8] = [1, 2]

loading as UInt16

let uint16 = bytes.withUnsafeBytes { $0.load(as: UInt16.self) }    // 513 

To get rid of the verbosity we can create a generic method extending ContiguousBytes:

extension ContiguousBytes {
    func object<T>() -> T {
        withUnsafeBytes { $0.load(as: T.self) }
    }
}

Usage:

let bytes: [UInt8] = [1, 2]
let uint16: UInt16 = bytes.object()    // 513

And to access the bytes anywhere in the collection:

extension Data {
    func subdata<R: RangeExpression>(in range: R) -> Self where R.Bound == Index {
        subdata(in: range.relative(to: self) )
    }
    func object<T>(at offset: Int) -> T {
        subdata(in: offset...).object()
    }
}

extension Sequence where Element == UInt8  {
    var data: Data { .init(self) }
}

extension Collection where Element == UInt8, Index == Int {
    func object<T>(at offset: Int = 0) -> T {
        data.object(at: offset)
    }
}

Usage:

let bytes: [UInt8] = [255, 255, 1, 2]
let uintMax: UInt16 = bytes.object()      // 65535 at offset zero
let uint16: UInt16 = bytes.object(at: 2)  // 513   at offset two
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
6

How about

let bytes:[UInt8] = [0x01, 0x02]
let result = (UInt16(bytes[1]) << 8) + UInt16(bytes[0])

With a loop, this easily generalizes to larger byte arrays, and it can be wrapped in a function for readability:

let bytes:[UInt8] = [0x01, 0x02, 0x03, 0x04]

func bytesToUInt(byteArray: [UInt8]) -> UInt {
  assert(byteArray.count <= 4)
  var result: UInt = 0
  for idx in 0..<(byteArray.count) {
    let shiftAmount = UInt((byteArray.count) - idx - 1) * 8
    result += UInt(byteArray[idx]) << shiftAmount
  }
  return result
}

println(bytesToUInt(bytes))    // result is 16909060
pjs
  • 18,696
  • 4
  • 27
  • 56
  • Yes and `let b:UInt16 = UInt16(dataBytePointer[0]) * 256 + UInt16(dataBytePointer[1])` also works. Very inelegant and start looking even worse for UInt32. – zaph Aug 12 '14 at 15:03
  • Why worse for UInt32? You can assemble multiple bytes by looping through the array and scaling the shift amount by the current index. – pjs Aug 12 '14 at 15:06
  • Worse because the statement gets longer and more complex. – zaph Aug 12 '14 at 15:13
  • No need to generalize if you only go up to 4. Just write the formula in one line, manually writing shifts. If you insist on a general form use recursion instead of looping, will look better. – pqnet Aug 13 '14 at 16:34
  • @pqnet: I liked that it's easy to generalize to 2, 4, or 8 to yield UInt16, UInt32, or UInt64, respectively. I also intended the loop/function implementation it as a response to Zaph's statement that it was somehow worse when the pattern is so clear it can be automated almost trivially. I personally prefer that it's an all-Swift solution, even though a preference was stated for basing the solution on NSData. I'm a fan of keeping it in the base language and avoiding unsafe pointer constructs. – pjs Aug 13 '14 at 18:03
5

I don't know the syntax for swift, but what about something like:

let a:UInt16 = UInt16(bytes[0]) * 256 + UInt16(bytes[1])
pqnet
  • 6,070
  • 1
  • 30
  • 51
3

If the bytes are in an NSData object you may do (assume data:NSData):

var number: UInt16 = 0
data.getBytes(&number, length: sizeof(UInt16))

The getBytes method writes up to two bytes in the memory location of number (similar to C's memcpy. This won't crash your app if data hasn't enough bytes.

(edit: no need to use range if starting from beginning of buffer)

pqnet
  • 6,070
  • 1
  • 30
  • 51
  • I would hope that Swift would allow operations such as the that are safe and direct, can you say Optional? This still leaves one to wonder if there were bytes there that were 0 or if there were only one or none. So a length check is still needed. Just do the length check first and then let `u16 = UnsafePointer(data.bytes)[index]` is also safe. Once having done a length check one can index further into the data simply by the index. – zaph Aug 13 '14 at 20:30
  • @Zaph whatever man, it is your code. I'm not the one responsible for it – pqnet Aug 13 '14 at 20:32
  • @Zaph misunderstanding is two people's fault. Sorry I was really thinking you were fooling me there – pqnet Aug 13 '14 at 21:18
3

Martin R's answer is great and nicely updated for beta 6. However, if you need to get at bytes that are not at the start of your buffer the proposed withMemoryRebound method does not offer a range to rebind from. My solution to this, eg. pick out the second UInt16 from an array was:

var val: UInt16 = 0
let buf = UnsafeMutableBufferPointer(start: &val, count: 1)
_ = dat.copyBytes(to: buf, from: Range(2...3))
Teo Sartori
  • 1,082
  • 14
  • 24
2

Assuming little endian encoding.

To convert to UInt16 from [UInt8], you can do something like

var x: [UInt8] = [0x01, 0x02]
var y: UInt16 = 0
y += UInt16(x[1]) << 0o10
y += UInt16(x[0]) << 0o00

For conversion to UInt32, this pattern extends to

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
var y: UInt32 = 0
y += UInt32(x[3]) << 0o30
y += UInt32(x[2]) << 0o20
y += UInt32(x[1]) << 0o10
y += UInt32(x[0]) << 0o00

Octal representation of the shift amount gives a nice indication on how many full bytes are shifted (8 becomes 0o10, 16 becomes 0o20 etc).

This can be reduced to the following for UInt16:

var x: [UInt8] = [0x01, 0x02]
let y: UInt16 = reverse(x).reduce(UInt16(0)) {
    $0 << 0o10 + UInt16($1)
}

and to the following for UInt32:

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
let y: UInt32 = reverse(x).reduce(UInt32(0)) {
    $0 << 0o10 + UInt32($1)
}

The reduced version also works for UInt64, and also handles values where the byte encoding does not use all bytes, like [0x01, 0x02, 0x03]

Etan
  • 17,014
  • 17
  • 89
  • 148
  • This produces 2049 but the correct result should be 513. Changing 010 to 8 produces the current answer. 010 is base 10, 0o10 is base 8. Also while excessively clever it is also is expensive and takes a while to see what is happening. – zaph Mar 15 '15 at 11:26
  • Corrected the answer so it's now correctly using octal representation. Thanks for pointing out that I missed the o. – Etan Mar 15 '15 at 11:30
  • Octal? Why octal, just to be obscure? isn't 8 more sensible/understandable? The only ting more obscure is octal-ascii as seen in Tar. – zaph Mar 15 '15 at 11:37
  • Updated the answer to explain why octal. – Etan Mar 15 '15 at 11:45
  • If you have to go through all that to justify using octal it is probably to clever. The best code is obvious to the average reader. – zaph Mar 15 '15 at 11:54
  • Well, you asked, I answered. Not sure why the hate. – Etan Mar 15 '15 at 12:09
  • It is not hate, it is a response to lack of clarity. Clever is almost always the wrong solution. I posit that the average developer seeing this who have a WTF moment. See: using "posit" is just being cleaver. ;-) – zaph Mar 15 '15 at 12:22
  • in c etc, the prefix is btw just 0 which is kind of confusing. swift introduced 0o. btw: unix file systems also use octal for their permission scheme - eg chmod 600 is actually 0o600. – Etan Mar 15 '15 at 15:55
  • It is interesting that the example values are in hex, why not octal: [0o01, 0o02, 0o03, 0o04]? – zaph Mar 15 '15 at 17:11
  • because you used hex in your question, and because byte values are commonly specified as hex values? – Etan Mar 15 '15 at 23:57
0

i putting here for helper. Very simple converter UINT16 to [UINT8].

var value: UInt16 = 2802

func uint16ToUint8Array(value: UInt16) -> [UInt8]{
    var uint8Array = [UInt8]()
    let firstByte = UInt8(value >> 8)
    uint8Array.append(firstByte)
    let secondBtye = UInt8(value.bigEndian >> 8)
    uint8Array.append(secondBtye)
    return uint8Array
}
  print(uint16ToUint8Array(value: value))

Output: [10, 242]

Okan TEL
  • 26
  • 3