Skip to content

Commit

Permalink
[BigInt] Disable custom _Significand and Karatsuba multiplication, ad…
Browse files Browse the repository at this point in the history
…d "pidigits" performance test.

[BigInt] Disable custom _Significand and Karatsuba multiplication.

[BigIntModule] Fix up tests and add "pidigits" performance test.
  • Loading branch information
xwu committed Jun 10, 2020
1 parent 4c0d382 commit 5976abc
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 24 deletions.
45 changes: 30 additions & 15 deletions Sources/BigIntModule/BigInt._Significand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@
//
//===----------------------------------------------------------------------===//

#if true
extension BigInt {
/// The significand of a `BigInt` value, a nonempty collection of the
/// significant digits of that value's magnitude stored in words of type
/// `UInt`.
///
/// The first element of the collection is the lowest word.
@usableFromInline
internal typealias _Significand = [UInt]
}

extension BigInt._Significand {
/// Creates a new significand with the given words.
@inlinable
internal init(_ low: UInt, _ rest: [UInt] = []) {
self = [low]
reserveCapacity(rest.count + 1)
insert(contentsOf: rest, at: 1)
}
}
#else
extension BigInt {
/// The significand of a `BigInt` value, a nonempty collection of the
/// significant digits of that value's magnitude stored in words of type
Expand All @@ -26,13 +47,6 @@ extension BigInt {
/// highest.
@usableFromInline
internal var _rest: [UInt]

/// Creates a new significand with a single low word equal to zero.
@inlinable
internal init() {
_low = 0
_rest = []
}

/// Creates a new significand with the given words.
@inlinable
Expand Down Expand Up @@ -417,6 +431,7 @@ extension BigInt._Significand {
}

extension BigInt._Significand: Hashable { }
#endif

/// Nota bene:
/// While operations implemented on `BigInt` expect inputs that are normalized
Expand Down Expand Up @@ -595,7 +610,7 @@ extension BigInt._Significand {

// @inlinable
internal func multiplying(by other: Self) -> Self {
var result = Self()
var result = Self(0)
result.reserveCapacity(count + other.count)
for i in 0..<other.count {
var temporary = self
Expand All @@ -609,25 +624,25 @@ extension BigInt._Significand {
// words) of the lower half of each operand.
// @inlinable
internal func multiplying(by other: Self, karatsubaThreshold: Int) -> Self {
func add(_ lhs: Slice<Self>, _ rhs: Slice<Self>) -> Self {
func add(_ lhs: SubSequence, _ rhs: SubSequence) -> Self {
// Recall that we have a precondition for `Self<C: Collection>(_: C)` that
// the argument not be empty.
if lhs.isEmpty { return rhs.isEmpty ? Self() : Self(rhs) }
if lhs.isEmpty { return rhs.isEmpty ? Self(0) : Self(rhs) }
if rhs.isEmpty { return Self(lhs) }

var result = Self(lhs)
result.add(Self(rhs))
return result
}

func multiply(_ lhs: Slice<Self>, _ rhs: Slice<Self>) -> Self {
func multiply(_ lhs: SubSequence, _ rhs: SubSequence) -> Self {

// Based on Karatsuba's method. For details see:
// <https://mathworld.wolfram.com/KaratsubaMultiplication.html>.

let m = (Swift.max(lhs.count, rhs.count) + 1) / 2
guard m >= karatsubaThreshold else {
if lhs.isEmpty || rhs.isEmpty { return Self() }
if lhs.isEmpty || rhs.isEmpty { return Self(0) }
return Self(lhs).multiplying(by: Self(rhs))
}

Expand Down Expand Up @@ -655,7 +670,7 @@ extension BigInt._Significand {
@inlinable
@discardableResult
internal mutating func divide(by other: UInt) -> /* remainder: */ Self {
if other == 1 { return Self() }
if other == 1 { return Self(0) }
var remainder = 0 as UInt
for i in (0..<count).reversed() {
(self[i], remainder) = other.dividingFullWidth((remainder, self[i]))
Expand Down Expand Up @@ -706,11 +721,11 @@ extension BigInt._Significand {
other.removeLast(n &- (i &+ 1))
n = i &+ 1
}
guard n > 1 else { return divide(by: other._low) }
guard n > 1 else { return divide(by: other[0]) }
let clz = other[n &- 1].leadingZeroBitCount

var m = count - n
guard let j = lastIndex(where: { $0 != 0 }) else { return Self() }
guard let j = lastIndex(where: { $0 != 0 }) else { return Self(0) }
if m > j &+ 1 {
removeLast(m &- (j &+ 1))
m = j &+ 1
Expand Down
10 changes: 5 additions & 5 deletions Sources/BigIntModule/BigInt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public struct BigInt {
internal mutating func _normalize() {
guard let i = _significand.firstIndex(where: { $0 != 0 }) else {
_combination = 0
_significand = _Significand()
_significand = _Significand(0)
return
}
assert(_combination != 0)
Expand Down Expand Up @@ -163,7 +163,7 @@ extension BigInt: AdditiveArithmetic {
@inlinable
public init() {
_combination = 0
_significand = _Significand()
_significand = _Significand(0)
}

@inlinable
Expand Down Expand Up @@ -275,7 +275,7 @@ extension BigInt: SignedNumeric {
let combination =
lhs._signum * rhs._signum * (lhs._exponent + rhs._exponent + 1)
let significand =
lhs._significand.multiplying(by: rhs._significand, karatsubaThreshold: 8)
lhs._significand.multiplying(by: rhs._significand /*, karatsubaThreshold: 8 */)
var result = BigInt(_combination: combination, significand: significand)
result._normalize()
return result
Expand Down Expand Up @@ -451,7 +451,7 @@ extension BigInt: BinaryInteger {
var result = lhs._significand.divide(by: rhs._significand)
swap(&result, &lhs._significand)
} else {
lhs._significand = _Significand()
lhs._significand = _Significand(0)
}
lhs._normalize()
}
Expand Down Expand Up @@ -682,7 +682,7 @@ extension BigInt {
let signum = Int(bitPattern: words.last!) < 0 ? -1 : 1
guard let exponent = words.firstIndex(where: { $0 != 0 }) else {
_combination = 0
_significand = _Significand()
_significand = _Significand(0)
return
}
_combination = signum * (exponent + 1)
Expand Down
62 changes: 58 additions & 4 deletions Tests/BigIntTests/BigIntTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ internal func _randomWords(count: Int) -> (BigInt, AttaswiftBigInt) {

final class BigIntModuleTests: XCTestCase {
func testLayout() {
#if false
XCTAssertEqual(MemoryLayout<BigInt>.size, MemoryLayout<Int>.size * 3)
XCTAssertEqual(MemoryLayout<BigInt>.stride, MemoryLayout<Int>.size * 3)
#endif
}

func testSignificand() {
let x = BigInt._Significand([1, 2, 3])
XCTAssertEqual(x._low, 1)
XCTAssertEqual(x._rest[0], 2)
XCTAssertEqual(x._rest[1], 3)
XCTAssertEqual(x[0], 1)
XCTAssertEqual(x[1], 2)
XCTAssertEqual(x[2], 3)
}

func testWords() {
Expand All @@ -48,7 +50,7 @@ final class BigIntModuleTests: XCTestCase {
for i in (-42..<42) {
let x = BigInt(i)
XCTAssertEqual(x._combination, i.signum())
XCTAssertEqual(x._significand._low, i.magnitude)
XCTAssertEqual(x._significand[0], i.magnitude)
XCTAssertEqual(x.description, i.description)
let j = Int(x)
XCTAssertEqual(i, j)
Expand Down Expand Up @@ -281,4 +283,56 @@ final class BigIntModuleTests: XCTestCase {
(-a.1 << 12345).trailingZeroBitCount)
}
}

func testPerformancePiDigits() {
var acc = 0 as BigInt, num = 1 as BigInt, den = 1 as BigInt

func extractDigit(_ n: UInt) -> UInt {
var tmp = num * BigInt(n)
tmp += acc
tmp /= den
return tmp.words[0]
}

func eliminateDigit(_ d: UInt) {
acc -= den * BigInt(d)
acc *= 10
num *= 10
}

func nextTerm(_ k: UInt) {
let k2 = BigInt(k * 2 + 1)
acc += num * 2
acc *= k2
den *= k2
num *= BigInt(k)
}

func piDigits(_ n: Int) {
acc = 0
den = 1
num = 1

var i = 0
var k = 0 as UInt
var string = ""
while i < n {
k += 1
nextTerm(k)
if num > acc { continue }
let d = extractDigit(3)
if d != extractDigit(4) { continue }
string.append("\(d)")
i += 1
if i.isMultiple(of: 10) {
print("\(string)\t:\(i)")
string = ""
}
eliminateDigit(d)
}
}
measure {
piDigits(100)
}
}
}

0 comments on commit 5976abc

Please sign in to comment.