MatterGoal
MatterGoal

Reputation: 16430

Uint8 sequence padding with Swift

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

Answers (1)

vacawama
vacawama

Reputation: 154641

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]

Upvotes: 2

Related Questions