Created
February 22, 2020 13:58
-
-
Save karwa/86a867ce3904a512ec837a304b799916 to your computer and use it in GitHub Desktop.
String(binary:)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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