Reputation: 16430
Is there a way to do what we can easily achieve with to_bytes with python, in Swift?
In a few words I need to define the length of a byte sequence, filling the leading elements with 0s.
As an example, if I have to describe the number 5 with a sequence of 4 bytes I should get it as \x00\x00\x00\x05
.
with the python to_bytes
function I can easily use the first parameter to define the sequence length and I could write it like (5).to_bytes(4, byteorder='big')
I can't find a way to easily obtain the same result with swift.
Upvotes: 2
Views: 429
Reputation: 154641
Note: Python's
to_bytes
can take any arbitrarylength
. If you want to convert a value to its native size (eg. 1 byte forInt8
, 2 bytes forInt16
, 4 bytes forInt32
, etc.), check out round trip Swift number types to/from Data and How can I convert data into types like Doubles, Ints and Strings in Swift?
For any arbitrary length with sign extension (for negative numbers) you can use & 255
to extract the lowest order byte and >> 8
to shift the value right 8 bits repeatedly in a loop to compute the bytes. (Here I used map
to generate the array of bytes). Then use reversed()
to put them in the desired big endian order:
var i = 5
let len = 4
let arr: [UInt8] = (0..<len).map { _ in
let byte = UInt8(i & 255)
i >>= 8
return byte }
.reversed()
print(arr)
Output:
[0, 0, 0, 5]
Notes:
>>=
will sign extend a negative number, so -5
would give the expected 2's complement result: [255, 255, 255, 251]
.arr
as [UInt8]
ensures that arr
is [UInt8]
and not ReversedCollection<Array<UInt8>>
and it aids Swift in the determination of the return type of map
.reversed()
and just use the result of the map
.toBytes(length:bigEndian:)
as an extension
of Int
:This can be added as an extension to Int
to further mimic the behavior of Python's to_bytes
method:
extension Int {
func toBytes(length: Int, bigEndian: Bool = true) -> [UInt8] {
var i = self
let bytes: [UInt8] = (0..<length).map { _ in
let byte = UInt8(i & 255)
i >>= 8
return byte
}
return bigEndian ? bytes.reversed() : bytes
}
}
Examples:
print(5.toBytes(length: 4))
[0, 0, 0, 5]
print((-5).toBytes(length: 4))
[255, 255, 255, 251]
print(5.toBytes(length: 8))
[0, 0, 0, 0, 0, 0, 0, 5]
print(5.toBytes(length: 8, bigEndian: false))
[5, 0, 0, 0, 0, 0, 0, 0]
toBytes
to work with any Int
typeSimply extending FixedWidthInteger
instead of Int
makes this work for all Int
and UInt
types except for Int8
which doesn't handle the sign extension correctly. Checking for that type explicitly and converting it to an Int
solves that problem.
extension FixedWidthInteger {
func toBytes(length: Int, bigEndian: Bool = true) -> [UInt8] {
if self is Int8 {
return Int(self).toBytes(length: length, bigEndian: bigEndian)
}
var i = self
let bytes: [UInt8] = (0..<length).map { _ in
let byte = UInt8(i & 255)
i >>= 8
return byte
}
return bigEndian ? bytes.reversed() : bytes
}
}
Examples:
print(Int8(-5).toBytes(length: 10))
print(Int16(-5).toBytes(length: 10))
print(Int32(-5).toBytes(length: 10))
print(Int64(-5).toBytes(length: 10))
[255, 255, 255, 255, 255, 255, 255, 255, 255, 251] [255, 255, 255, 255, 255, 255, 255, 255, 255, 251] [255, 255, 255, 255, 255, 255, 255, 255, 255, 251] [255, 255, 255, 255, 255, 255, 255, 255, 255, 251]
print(Int8(5).toBytes(length: 10))
print(Int16(5).toBytes(length: 10))
print(Int32(5).toBytes(length: 10))
print(Int64(5).toBytes(length: 10))
print(UInt8(5).toBytes(length: 10))
print(UInt16(5).toBytes(length: 10))
print(UInt32(5).toBytes(length: 10))
print(UInt64(5).toBytes(length: 10))
[0, 0, 0, 0, 0, 0, 0, 0, 0, 5] [0, 0, 0, 0, 0, 0, 0, 0, 0, 5] [0, 0, 0, 0, 0, 0, 0, 0, 0, 5] [0, 0, 0, 0, 0, 0, 0, 0, 0, 5] [0, 0, 0, 0, 0, 0, 0, 0, 0, 5] [0, 0, 0, 0, 0, 0, 0, 0, 0, 5] [0, 0, 0, 0, 0, 0, 0, 0, 0, 5] [0, 0, 0, 0, 0, 0, 0, 0, 0, 5]
print(UInt64.max.toBytes(length: 10))
[0, 0, 255, 255, 255, 255, 255, 255, 255, 255]
Upvotes: 2