Skip to content

[swift-4.0-branch][stdlib] Implement BinaryInteger.words #11156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 26, 2017
135 changes: 70 additions & 65 deletions stdlib/public/core/Integers.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -1426,27 +1426,22 @@ public protocol BinaryInteger :
/// - Parameter source: An integer to convert to this type.
init<T : BinaryInteger>(clamping source: T)

/// Returns the n-th word, counting from the least significant to most
/// significant, of this value's binary representation.
///
/// The `_word(at:)` method returns negative values in two's complement
/// representation, regardless of a type's underlying implementation. If `n`
/// is greater than the number of words in this value's current
/// representation, the result is `0` for positive numbers and `~0` for
/// negative numbers.
///
/// - Parameter n: The word to return, counting from the least significant to
/// most significant. `n` must be greater than or equal to zero.
/// - Returns: An word-sized, unsigned integer with the bit pattern of the
/// n-th word of this value.
func _word(at n: Int) -> UInt

// FIXME(integers): add doc comments
// FIXME: Should be `Words : Collection where Words.Iterator.Element == UInt`
// FIXME: Should be `Words : Collection where Words.Element == UInt`
// See <rdar://problem/31798916> for why it isn't.
associatedtype Words
/// A type that represents the words of a binary integer.
/// Must implement the `Collection` protocol with an `UInt` element type.
associatedtype Words : Sequence where Words.Element == UInt

/// Returns a collection containing the words of this value's binary
/// representation, in order from the least significant to most significant.
///
/// Negative values are returned in two's complement representation,
/// regardless of the type's underlying implementation.
var words: Words { get }

/// The least significant word in this value's binary representation.
var _lowWord: UInt { get }

/// The number of bits in the current binary representation of this value.
///
/// This property is a constant for instances of fixed-width integer
Expand Down Expand Up @@ -1542,13 +1537,10 @@ extension BinaryInteger {
return (self > (0 as Self) ? 1 : 0) - (self < (0 as Self) ? 1 : 0)
}

/// The number of words used for the current binary representation of this
/// value.
///
/// This property is a constant for instances of fixed-width integer types.
@_transparent
public var _countRepresentedWords: Int {
return (self.bitWidth + ${word_bits} - 1) / ${word_bits}
public var _lowWord: UInt {
var it = words.makeIterator()
return it.next() ?? 0
}

public func quotientAndRemainder(dividingBy rhs: Self)
Expand Down Expand Up @@ -1886,19 +1878,6 @@ extension BinaryInteger {
}
#endif

extension BinaryInteger {
// FIXME(integers): inefficient. Should get rid of _word(at:) and
// _countRepresentedWords, and make `words` the basic operation.
public var words: [UInt] {
var result = [UInt]()
result.reserveCapacity(_countRepresentedWords)
for i in 0..<self._countRepresentedWords {
result.append(_word(at: i))
}
return result
}
}

//===----------------------------------------------------------------------===//
//===--- FixedWidthInteger ------------------------------------------------===//
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -2282,21 +2261,21 @@ ${unsafeOperationComment(x.operator)}
@inline(__always)
public init<T : BinaryInteger>(extendingOrTruncating source: T) {
if Self.bitWidth <= ${word_bits} {
self = Self.init(_truncatingBits: source._word(at: 0))
self = Self.init(_truncatingBits: source._lowWord)
}
else {
var result: Self = source < (0 as T) ? ~0 : 0
// start with the most significant word
var n = source._countRepresentedWords
while n >= 0 {
// masking is OK here because this we have already ensured
// that Self.bitWidth > ${word_bits}. Not masking results in
let neg = source < (0 as T)
var result: Self = neg ? ~0 : 0
var shift: Self = 0
let width = Self(_truncatingBits: Self.bitWidth._lowWord)
for word in source.words {
guard shift < width else { break }
// masking shift is OK here because we have already ensured
// that shift < Self.bitWidth. Not masking results in
// infinite recursion.
result &<<= (${word_bits} as Self)
result |= Self(_truncatingBits: source._word(at: n))
n -= 1
result ^= Self(_truncatingBits: neg ? ~word : word) &<< shift
shift += ${word_bits}
}

self = result
}
}
Expand Down Expand Up @@ -2740,45 +2719,71 @@ ${assignmentOperatorComment(x.operator, True)}
return Int(
${Self}(
Builtin.int_ctlz_Int${bits}(self._value, false._value)
)._lowUWord._value)
)._lowWord._value)
}

@_transparent
public var trailingZeroBitCount: Int {
return Int(
${Self}(
Builtin.int_cttz_Int${bits}(self._value, false._value)
)._lowUWord._value)
)._lowWord._value)
}

@_transparent
public var nonzeroBitCount: Int {
return Int(
${Self}(
Builtin.int_ctpop_Int${bits}(self._value)
)._lowUWord._value)
)._lowWord._value)
}

@_transparent
public func _word(at n: Int) -> UInt {
_precondition(n >= 0, "Negative word index")
if _fastPath(n < _countRepresentedWords) {
let shift = UInt(n._value) &* ${word_bits}
let bitWidth = UInt(self.bitWidth._value)
_sanityCheck(shift < bitWidth)
return (self &>> ${Self}(_truncatingBits: shift))._lowUWord
// FIXME should be RandomAccessCollection
public struct Words : BidirectionalCollection {
public typealias Indices = CountableRange<Int>
public typealias SubSequence = BidirectionalSlice<${Self}.Words>

var _value: ${Self}

public init(_ value: ${Self}) {
self._value = value
}

public var count: Int {
return (${bits} + ${word_bits} - 1) / ${word_bits}
}

public var startIndex: Int { return 0 }

public var endIndex: Int { return count }

public var indices: Indices { return startIndex ..< endIndex }

@_transparent
public func index(after i: Int) -> Int { return i + 1 }

@_transparent
public func index(before i: Int) -> Int { return i - 1 }

public subscript(position: Int) -> UInt {
get {
_precondition(position >= 0, "Negative word index")
_precondition(position < endIndex, "Word index out of range")
let shift = UInt(position._value) &* ${word_bits}
_sanityCheck(shift < UInt(_value.bitWidth._value))
return (_value &>> ${Self}(_truncatingBits: shift))._lowWord
}
}
% if signed:
return self < (0 as ${Self}) ? ~0 : 0
% else:
return 0
% end
}

@_transparent
public var words: Words {
return Words(self)
}

@_transparent
public // transparent
var _lowUWord: UInt {
var _lowWord: UInt {
% truncOrExt = z + 'ext' if bits <= word_bits else 'trunc'
return UInt(
Builtin.${truncOrExt}OrBitCast_Int${bits}_Int${word_bits}(_value)
Expand Down
78 changes: 44 additions & 34 deletions test/Prototypes/BigInt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ public struct _BigInt<Word: FixedWidthInteger & UnsignedInteger> :
// FIXME: This is broken on 32-bit arch w/ Word = UInt64
let wordRatio = UInt.bitWidth / Word.bitWidth
_sanityCheck(wordRatio != 0)
for i in 0..<source._countRepresentedWords {
var sourceWord = source._word(at: i)
for var sourceWord in source.words {
for _ in 0..<wordRatio {
_data.append(Word(extendingOrTruncating: sourceWord))
sourceWord >>= Word.bitWidth
Expand Down Expand Up @@ -660,36 +659,30 @@ public struct _BigInt<Word: FixedWidthInteger & UnsignedInteger> :
}
}

public func _word(at n: Int) -> UInt {
let ratio = UInt.bitWidth / Word.bitWidth
_sanityCheck(ratio != 0)

var twosComplementData = _dataAsTwosComplement()

// Find beginning of range. If we're beyond the value, return 1s or 0s.
let start = n * ratio
if start >= twosComplementData.count {
return isNegative ? UInt.max : 0
}

// Find end of range. If the range extends beyond the representation,
// add bits to the end.
let end = (n + 1) * ratio
if end > twosComplementData.count {
twosComplementData.append(contentsOf:
repeatElement(isNegative ? Word.max : 0,
count: end - twosComplementData.count))
public var words: [UInt] {
_sanityCheck(UInt.bitWidth % Word.bitWidth == 0)
let twosComplementData = _dataAsTwosComplement()
var words: [UInt] = []
words.reserveCapacity((twosComplementData.count * Word.bitWidth
+ UInt.bitWidth - 1) / UInt.bitWidth)
var word: UInt = 0
var shift = 0
for w in twosComplementData {
word |= UInt(extendingOrTruncating: w) << shift
shift += Word.bitWidth
if shift == UInt.bitWidth {
words.append(word)
word = 0
shift = 0
}
}

// Build the correct word from the range determined above.
let wordSlice = twosComplementData[start..<end]
var result: UInt = 0
for v in wordSlice.reversed() {
result <<= Word.bitWidth
result |= UInt(extendingOrTruncating: v)
if shift != 0 {
if isNegative {
word |= ~((1 << shift) - 1)
}
words.append(word)
}

return result
return words
}

/// The number of bits used for storage of this value. Always a multiple of
Expand Down Expand Up @@ -1258,7 +1251,7 @@ struct Bit : FixedWidthInteger, UnsignedInteger {
}

var trailingZeroBitCount: Int {
return value.trailingZeroBitCount
return Int(~value & 1)
}

static var max: Bit {
Expand All @@ -1278,7 +1271,7 @@ struct Bit : FixedWidthInteger, UnsignedInteger {
}

var leadingZeroBitCount: Int {
return value.nonzeroBitCount - 7
return Int(~value & 1)
}

var bigEndian: Bit {
Expand All @@ -1293,8 +1286,8 @@ struct Bit : FixedWidthInteger, UnsignedInteger {
return self
}

func _word(at n: Int) -> UInt {
return UInt(value)
var words: UInt.Words {
return UInt(value).words
}

// Hashable, CustomStringConvertible
Expand Down Expand Up @@ -1499,6 +1492,15 @@ BitTests.test("Basics") {

expectEqual(x, x + y)
expectGT(x, x &+ x)

expectEqual(1, x.nonzeroBitCount)
expectEqual(0, y.nonzeroBitCount)

expectEqual(0, x.leadingZeroBitCount)
expectEqual(1, y.leadingZeroBitCount)

expectEqual(0, x.trailingZeroBitCount)
expectEqual(1, y.trailingZeroBitCount)
}

var BigIntTests = TestSuite("BigInt")
Expand Down Expand Up @@ -1865,4 +1867,12 @@ BigIntBitTests.test("Conformances") {
expectFalse(set.contains(-x))
}

BigIntBitTests.test("words") {
expectEqualSequence([1], (1 as BigIntBit).words)
expectEqualSequence([UInt.max, 0], BigIntBit(UInt.max).words)
expectEqualSequence([UInt.max >> 1], BigIntBit(UInt.max >> 1).words)
expectEqualSequence([0, 1], (BigIntBit(UInt.max) + 1).words)
expectEqualSequence([UInt.max], (-1 as BigIntBit).words)
}

runAllTests()
Loading