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]