Note: Python's to_bytes can take any arbitrary length. If you want to convert a value to its native size (eg. 1 byte for Int8, 2 bytes for Int16, 4 bytes for Int32, 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].
- Explicitly typing
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.
- For little endian order, remove
reversed() and just use the result of the map.
Implementing 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]
Extending toBytes to work with any Int type
Simply 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]