i’ve got a value type, let’s call it UInt8x20, that i am using as storage for a 160-bit SHA1 git hash. the UInt8x20 stores the hash in big-endian order.
now, i am trying to add an ExpressibleByIntegerLiteral conformance for this git hash type. after shooting myself in the foot several times, i finally got to this working implementation:
extension GitRevision:ExpressibleByIntegerLiteral
{
@inlinable public
init(integerLiteral:StaticBigInt)
{
precondition(integerLiteral.signum() >= 0,
"revision literal cannot be negative")
precondition(integerLiteral.bitWidth <= 161,
"revision literal overflows UInt8x20")
var hash:UInt8x20 = .init()
var byte:Int = hash.endIndex
var word:Int = 0
while byte != hash.startIndex
{
withUnsafeBytes(of: integerLiteral[word].bigEndian)
{
for value:UInt8 in $0.reversed()
{
byte = hash.index(before: byte)
hash[byte] = value
if byte == hash.startIndex
{
break
}
}
}
word += 1
}
self.init(hash: hash)
}
}
but this feels like a really weird implementation:
-
i’m iterating byte-by-byte, while StaticBigInt wants to be iterated by word (UInt),
-
i’m not using an integral number of StaticBigInt words (on a 64-bit system, i’m using 2.5 words),
-
i’m initializing the memory from back to front, and
-
i’m checking the byte index bounds twice on exit.
am i holding StaticBigInt wrong?
oscbyspro
(Oscar Byström Ericsson)
2
If you want to initialize front to back, maybe you can do something like:
func bigEndianBytes(of integer: StaticBigInt, count: Int) -> [UInt8] {
[UInt8](unsafeUninitializedCapacity: count) { bytes, byteIndex in
precondition(count >= 0)
precondition(integer.signum() >= 0)
precondition(integer.bitWidth <= 1 + 8 * count)
let remainder: Int = (count & (MemoryLayout<UInt>.size &- 1))
let remainderIndex = (count &>> MemoryLayout<UInt>.size.trailingZeroBitCount)
withUnsafeBytes(of: integer[remainderIndex].bigEndian) {
byteIndex = bytes.initialize(fromContentsOf: $0.suffix(remainder))
}
for index in (0 ..< remainderIndex).reversed() {
withUnsafeBytes(of: integer[index].bigEndian) {
byteIndex = bytes.suffix(from: byteIndex).initialize(fromContentsOf: $0)
}
}
}
}
print(bigEndianBytes(of: 0x0102030405060708090A0B0C0D0E0F1011121314, count: 20))
oscbyspro
(Oscar Byström Ericsson)
3
The StaticBigInt interaction I've settled on is chunking it with a generic ChunkedInt<Base, Element> collection, after turning StaticBigInt into a RandomAccessCollection<UInt>. I think this approach works well because chunking is useful beyond StaticBigInt, and many binary-integer-like things "just work" when StaticBigInt is a random access collection.