Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
String(binary:)
extension String {
/// Creates a string of ones and zeros representing the given value in binary.
///
/// Unlike `String.init<T: BinaryInteger>(_:radix:uppercase:)`, negative values
/// are represented as their true, two's-complement bitpattern.
///
@inlinable
public init<T: BinaryInteger>(binary value: T) {
let bitWidth = value.bitWidth
if bitWidth <= 32 {
self = String(_binary_32: value, bitWidth: bitWidth)
} else if bitWidth <= 64 {
self = String(_binary_64: value, bitWidth: bitWidth)
} else {
self = String(_binary_large: value, bitWidth: bitWidth)
}
}
@inlinable // 32-byte stack buffer.
internal init<T: BinaryInteger>(_binary_32 value: T, bitWidth: Int) {
var bitChars_storage: (Int64, Int64, Int64, Int64) = (0,0,0,0)
self = withUnsafeMutableBytes(of: &bitChars_storage) { ptr in
return ptr.baseAddress.unsafelyUnwrapped
.assumingMemoryBound(to: UInt64.self)
.withMemoryRebound(to: UInt8.self, capacity: 8) { base in
let buffer = UnsafeMutableBufferPointer(start: base, count: 32)
writeBitsAsAscii(of: value, bitWidth: bitWidth, into: buffer)
return String(decoding: buffer.prefix(bitWidth), as: UTF8.self)
}
}
}
@inlinable // 64-byte stack buffer.
internal init<T: BinaryInteger>(_binary_64 value: T, bitWidth: Int) {
var bitChars_storage: (Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64) = (0,0,0,0,0,0,0,0)
self = withUnsafeMutableBytes(of: &bitChars_storage) { ptr in
return ptr.baseAddress.unsafelyUnwrapped
.assumingMemoryBound(to: UInt64.self)
.withMemoryRebound(to: UInt8.self, capacity: 8) { base in
let buffer = UnsafeMutableBufferPointer(start: base, count: 64)
writeBitsAsAscii(of: value, bitWidth: bitWidth, into: buffer)
return String(decoding: buffer.prefix(bitWidth), as: UTF8.self)
}
}
}
@inlinable // >64-byte heap buffer.
internal init<T: BinaryInteger>(_binary_large value: T, bitWidth: Int) {
let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: bitWidth)
writeBitsAsAscii(of: value, bitWidth: bitWidth, into: buffer)
self = String(decoding: buffer, as: UTF8.self)
buffer.deallocate()
}
}
@inlinable @inline(__always)
internal func writeBitsAsAscii<T: BinaryInteger>(
of value: T, bitWidth: Int, into buffer: UnsafeMutableBufferPointer<UInt8>
) {
let ASCII_zero: UInt8 = 48
let ASCII_one: UInt8 = 49
let buffer = UnsafeMutableBufferPointer(rebasing: buffer.prefix(bitWidth))
guard value != 0 else { buffer.assign(repeating: ASCII_zero); return }
let trailingZeroes = value.trailingZeroBitCount
var value = value >> trailingZeroes
for idx in (trailingZeroes + 1)...(bitWidth) {
buffer[bitWidth - idx] = (value & 0x01 == 0 ? ASCII_zero : ASCII_one)
value >>= 1
}
UnsafeMutableBufferPointer(rebasing: buffer.suffix(trailingZeroes))
.assign(repeating: ASCII_zero)
// Note: even though this commented-out implementation looks simpler,
// the version which skips trailing zeroes looks like it might generate
// assembly which performs just as well (if not better).
// Keeping it here until benchmarks decide which to keep.
//
// guard value != 0 else { buffer.assign(repeating: ASCII_zero); return }
// var value = value
// for idx in 1...bitWidth {
// buffer[bitWidth - idx] = (value & 0x01 == 0 ? ASCII_zero : ASCII_one)
// value >>= 1
// }
}
extension BinaryInteger {
/// Parses the given string of ones and zeroes as an integer.
///
/// If the String contains more characters than this integer type has bits, the result
/// will represent the trailing characters. Invalid characters are treated as zeroes.
///
init(binary: String) {
let ASCII_one: UInt8 = 49
self = 0
for bit in binary.utf8 {
self <<= 1
if bit == ASCII_one { self |= 0x1 }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment