Skip to content

Commit 796cfdf

Browse files
authored
Merge pull request #11156 from moiseev/binary-integer-words-4
[swift-4.0-branch][stdlib] Implement BinaryInteger.words
2 parents 707b416 + 64de151 commit 796cfdf

File tree

3 files changed

+150
-128
lines changed

3 files changed

+150
-128
lines changed

stdlib/public/core/Integers.swift.gyb

Lines changed: 70 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,27 +1426,22 @@ public protocol BinaryInteger :
14261426
/// - Parameter source: An integer to convert to this type.
14271427
init<T : BinaryInteger>(clamping source: T)
14281428

1429-
/// Returns the n-th word, counting from the least significant to most
1430-
/// significant, of this value's binary representation.
1431-
///
1432-
/// The `_word(at:)` method returns negative values in two's complement
1433-
/// representation, regardless of a type's underlying implementation. If `n`
1434-
/// is greater than the number of words in this value's current
1435-
/// representation, the result is `0` for positive numbers and `~0` for
1436-
/// negative numbers.
1437-
///
1438-
/// - Parameter n: The word to return, counting from the least significant to
1439-
/// most significant. `n` must be greater than or equal to zero.
1440-
/// - Returns: An word-sized, unsigned integer with the bit pattern of the
1441-
/// n-th word of this value.
1442-
func _word(at n: Int) -> UInt
1443-
1444-
// FIXME(integers): add doc comments
1445-
// FIXME: Should be `Words : Collection where Words.Iterator.Element == UInt`
1429+
// FIXME: Should be `Words : Collection where Words.Element == UInt`
14461430
// See <rdar://problem/31798916> for why it isn't.
1447-
associatedtype Words
1431+
/// A type that represents the words of a binary integer.
1432+
/// Must implement the `Collection` protocol with an `UInt` element type.
1433+
associatedtype Words : Sequence where Words.Element == UInt
1434+
1435+
/// Returns a collection containing the words of this value's binary
1436+
/// representation, in order from the least significant to most significant.
1437+
///
1438+
/// Negative values are returned in two's complement representation,
1439+
/// regardless of the type's underlying implementation.
14481440
var words: Words { get }
14491441

1442+
/// The least significant word in this value's binary representation.
1443+
var _lowWord: UInt { get }
1444+
14501445
/// The number of bits in the current binary representation of this value.
14511446
///
14521447
/// This property is a constant for instances of fixed-width integer
@@ -1542,13 +1537,10 @@ extension BinaryInteger {
15421537
return (self > (0 as Self) ? 1 : 0) - (self < (0 as Self) ? 1 : 0)
15431538
}
15441539

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

15541546
public func quotientAndRemainder(dividingBy rhs: Self)
@@ -1886,19 +1878,6 @@ extension BinaryInteger {
18861878
}
18871879
#endif
18881880

1889-
extension BinaryInteger {
1890-
// FIXME(integers): inefficient. Should get rid of _word(at:) and
1891-
// _countRepresentedWords, and make `words` the basic operation.
1892-
public var words: [UInt] {
1893-
var result = [UInt]()
1894-
result.reserveCapacity(_countRepresentedWords)
1895-
for i in 0..<self._countRepresentedWords {
1896-
result.append(_word(at: i))
1897-
}
1898-
return result
1899-
}
1900-
}
1901-
19021881
//===----------------------------------------------------------------------===//
19031882
//===--- FixedWidthInteger ------------------------------------------------===//
19041883
//===----------------------------------------------------------------------===//
@@ -2282,21 +2261,21 @@ ${unsafeOperationComment(x.operator)}
22822261
@inline(__always)
22832262
public init<T : BinaryInteger>(extendingOrTruncating source: T) {
22842263
if Self.bitWidth <= ${word_bits} {
2285-
self = Self.init(_truncatingBits: source._word(at: 0))
2264+
self = Self.init(_truncatingBits: source._lowWord)
22862265
}
22872266
else {
2288-
var result: Self = source < (0 as T) ? ~0 : 0
2289-
// start with the most significant word
2290-
var n = source._countRepresentedWords
2291-
while n >= 0 {
2292-
// masking is OK here because this we have already ensured
2293-
// that Self.bitWidth > ${word_bits}. Not masking results in
2267+
let neg = source < (0 as T)
2268+
var result: Self = neg ? ~0 : 0
2269+
var shift: Self = 0
2270+
let width = Self(_truncatingBits: Self.bitWidth._lowWord)
2271+
for word in source.words {
2272+
guard shift < width else { break }
2273+
// masking shift is OK here because we have already ensured
2274+
// that shift < Self.bitWidth. Not masking results in
22942275
// infinite recursion.
2295-
result &<<= (${word_bits} as Self)
2296-
result |= Self(_truncatingBits: source._word(at: n))
2297-
n -= 1
2276+
result ^= Self(_truncatingBits: neg ? ~word : word) &<< shift
2277+
shift += ${word_bits}
22982278
}
2299-
23002279
self = result
23012280
}
23022281
}
@@ -2740,45 +2719,71 @@ ${assignmentOperatorComment(x.operator, True)}
27402719
return Int(
27412720
${Self}(
27422721
Builtin.int_ctlz_Int${bits}(self._value, false._value)
2743-
)._lowUWord._value)
2722+
)._lowWord._value)
27442723
}
27452724

27462725
@_transparent
27472726
public var trailingZeroBitCount: Int {
27482727
return Int(
27492728
${Self}(
27502729
Builtin.int_cttz_Int${bits}(self._value, false._value)
2751-
)._lowUWord._value)
2730+
)._lowWord._value)
27522731
}
27532732

27542733
@_transparent
27552734
public var nonzeroBitCount: Int {
27562735
return Int(
27572736
${Self}(
27582737
Builtin.int_ctpop_Int${bits}(self._value)
2759-
)._lowUWord._value)
2738+
)._lowWord._value)
27602739
}
27612740

2762-
@_transparent
2763-
public func _word(at n: Int) -> UInt {
2764-
_precondition(n >= 0, "Negative word index")
2765-
if _fastPath(n < _countRepresentedWords) {
2766-
let shift = UInt(n._value) &* ${word_bits}
2767-
let bitWidth = UInt(self.bitWidth._value)
2768-
_sanityCheck(shift < bitWidth)
2769-
return (self &>> ${Self}(_truncatingBits: shift))._lowUWord
2741+
// FIXME should be RandomAccessCollection
2742+
public struct Words : BidirectionalCollection {
2743+
public typealias Indices = CountableRange<Int>
2744+
public typealias SubSequence = BidirectionalSlice<${Self}.Words>
2745+
2746+
var _value: ${Self}
2747+
2748+
public init(_ value: ${Self}) {
2749+
self._value = value
2750+
}
2751+
2752+
public var count: Int {
2753+
return (${bits} + ${word_bits} - 1) / ${word_bits}
2754+
}
2755+
2756+
public var startIndex: Int { return 0 }
2757+
2758+
public var endIndex: Int { return count }
2759+
2760+
public var indices: Indices { return startIndex ..< endIndex }
2761+
2762+
@_transparent
2763+
public func index(after i: Int) -> Int { return i + 1 }
2764+
2765+
@_transparent
2766+
public func index(before i: Int) -> Int { return i - 1 }
2767+
2768+
public subscript(position: Int) -> UInt {
2769+
get {
2770+
_precondition(position >= 0, "Negative word index")
2771+
_precondition(position < endIndex, "Word index out of range")
2772+
let shift = UInt(position._value) &* ${word_bits}
2773+
_sanityCheck(shift < UInt(_value.bitWidth._value))
2774+
return (_value &>> ${Self}(_truncatingBits: shift))._lowWord
2775+
}
27702776
}
2771-
% if signed:
2772-
return self < (0 as ${Self}) ? ~0 : 0
2773-
% else:
2774-
return 0
2775-
% end
27762777
}
27772778

2779+
@_transparent
2780+
public var words: Words {
2781+
return Words(self)
2782+
}
27782783

27792784
@_transparent
27802785
public // transparent
2781-
var _lowUWord: UInt {
2786+
var _lowWord: UInt {
27822787
% truncOrExt = z + 'ext' if bits <= word_bits else 'trunc'
27832788
return UInt(
27842789
Builtin.${truncOrExt}OrBitCast_Int${bits}_Int${word_bits}(_value)

test/Prototypes/BigInt.swift

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ public struct _BigInt<Word: FixedWidthInteger & UnsignedInteger> :
121121
// FIXME: This is broken on 32-bit arch w/ Word = UInt64
122122
let wordRatio = UInt.bitWidth / Word.bitWidth
123123
_sanityCheck(wordRatio != 0)
124-
for i in 0..<source._countRepresentedWords {
125-
var sourceWord = source._word(at: i)
124+
for var sourceWord in source.words {
126125
for _ in 0..<wordRatio {
127126
_data.append(Word(extendingOrTruncating: sourceWord))
128127
sourceWord >>= Word.bitWidth
@@ -660,36 +659,30 @@ public struct _BigInt<Word: FixedWidthInteger & UnsignedInteger> :
660659
}
661660
}
662661

663-
public func _word(at n: Int) -> UInt {
664-
let ratio = UInt.bitWidth / Word.bitWidth
665-
_sanityCheck(ratio != 0)
666-
667-
var twosComplementData = _dataAsTwosComplement()
668-
669-
// Find beginning of range. If we're beyond the value, return 1s or 0s.
670-
let start = n * ratio
671-
if start >= twosComplementData.count {
672-
return isNegative ? UInt.max : 0
673-
}
674-
675-
// Find end of range. If the range extends beyond the representation,
676-
// add bits to the end.
677-
let end = (n + 1) * ratio
678-
if end > twosComplementData.count {
679-
twosComplementData.append(contentsOf:
680-
repeatElement(isNegative ? Word.max : 0,
681-
count: end - twosComplementData.count))
662+
public var words: [UInt] {
663+
_sanityCheck(UInt.bitWidth % Word.bitWidth == 0)
664+
let twosComplementData = _dataAsTwosComplement()
665+
var words: [UInt] = []
666+
words.reserveCapacity((twosComplementData.count * Word.bitWidth
667+
+ UInt.bitWidth - 1) / UInt.bitWidth)
668+
var word: UInt = 0
669+
var shift = 0
670+
for w in twosComplementData {
671+
word |= UInt(extendingOrTruncating: w) << shift
672+
shift += Word.bitWidth
673+
if shift == UInt.bitWidth {
674+
words.append(word)
675+
word = 0
676+
shift = 0
677+
}
682678
}
683-
684-
// Build the correct word from the range determined above.
685-
let wordSlice = twosComplementData[start..<end]
686-
var result: UInt = 0
687-
for v in wordSlice.reversed() {
688-
result <<= Word.bitWidth
689-
result |= UInt(extendingOrTruncating: v)
679+
if shift != 0 {
680+
if isNegative {
681+
word |= ~((1 << shift) - 1)
682+
}
683+
words.append(word)
690684
}
691-
692-
return result
685+
return words
693686
}
694687

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

12601253
var trailingZeroBitCount: Int {
1261-
return value.trailingZeroBitCount
1254+
return Int(~value & 1)
12621255
}
12631256

12641257
static var max: Bit {
@@ -1278,7 +1271,7 @@ struct Bit : FixedWidthInteger, UnsignedInteger {
12781271
}
12791272

12801273
var leadingZeroBitCount: Int {
1281-
return value.nonzeroBitCount - 7
1274+
return Int(~value & 1)
12821275
}
12831276

12841277
var bigEndian: Bit {
@@ -1293,8 +1286,8 @@ struct Bit : FixedWidthInteger, UnsignedInteger {
12931286
return self
12941287
}
12951288

1296-
func _word(at n: Int) -> UInt {
1297-
return UInt(value)
1289+
var words: UInt.Words {
1290+
return UInt(value).words
12981291
}
12991292

13001293
// Hashable, CustomStringConvertible
@@ -1499,6 +1492,15 @@ BitTests.test("Basics") {
14991492

15001493
expectEqual(x, x + y)
15011494
expectGT(x, x &+ x)
1495+
1496+
expectEqual(1, x.nonzeroBitCount)
1497+
expectEqual(0, y.nonzeroBitCount)
1498+
1499+
expectEqual(0, x.leadingZeroBitCount)
1500+
expectEqual(1, y.leadingZeroBitCount)
1501+
1502+
expectEqual(0, x.trailingZeroBitCount)
1503+
expectEqual(1, y.trailingZeroBitCount)
15021504
}
15031505

15041506
var BigIntTests = TestSuite("BigInt")
@@ -1865,4 +1867,12 @@ BigIntBitTests.test("Conformances") {
18651867
expectFalse(set.contains(-x))
18661868
}
18671869

1870+
BigIntBitTests.test("words") {
1871+
expectEqualSequence([1], (1 as BigIntBit).words)
1872+
expectEqualSequence([UInt.max, 0], BigIntBit(UInt.max).words)
1873+
expectEqualSequence([UInt.max >> 1], BigIntBit(UInt.max >> 1).words)
1874+
expectEqualSequence([0, 1], (BigIntBit(UInt.max) + 1).words)
1875+
expectEqualSequence([UInt.max], (-1 as BigIntBit).words)
1876+
}
1877+
18681878
runAllTests()

0 commit comments

Comments
 (0)