Skip to content

Commit 15cd858

Browse files
committed
[BigInt] Implement division (Knuth).
1 parent 1b6cca7 commit 15cd858

File tree

3 files changed

+175
-38
lines changed

3 files changed

+175
-38
lines changed

Sources/BigIntModule/BigInt._Significand.swift

+96-8
Original file line numberDiff line numberDiff line change
@@ -661,14 +661,102 @@ extension BigInt._Significand {
661661

662662
// @inlinable
663663
@discardableResult
664-
internal func quotientAndRemainder(
665-
dividingBy other: Self
666-
) -> (quotient: Self, remainder: Self) {
667-
if other.count == 1 {
668-
var x = self
669-
let remainder = x.divide(by: other._low)
670-
return (quotient: x, remainder: remainder)
664+
internal mutating func divide(by other: Self) -> /* remainder: */ Self {
665+
func shift(_ lhs: inout Self, left rhs: Int) {
666+
var carry = 0 as UInt
667+
guard rhs != 0 else { return }
668+
for i in 0..<lhs.count {
669+
let temporary = lhs[i]
670+
lhs[i] = temporary &<< rhs | carry
671+
carry = temporary &>> (UInt.bitWidth &- rhs)
672+
}
673+
lhs.append(carry)
674+
}
675+
676+
func shift(_ lhs: inout Self, right rhs: Int) {
677+
var carry = 0 as UInt
678+
guard rhs != 0 else { return }
679+
for i in (0..<lhs.count).reversed() {
680+
let temporary = lhs[i]
681+
lhs[i] = temporary &>> rhs | carry
682+
carry = temporary &<< (UInt.bitWidth &- rhs)
683+
}
684+
}
685+
// Based on Knuth's Algorithm D.
686+
// For details see <https://skanthak.homepage.t-online.de/division.html>.
687+
688+
// We'll remove any extraneous leading zero words while determining by how
689+
// much to shift our operands.
690+
var other = other
691+
var n = other.count
692+
guard let i = other.lastIndex(where: { $0 != 0 }) else {
693+
fatalError("Divide by zero")
671694
}
672-
fatalError("Not implemented")
695+
if n > i &+ 1 {
696+
other.removeLast(n &- (i &+ 1))
697+
n = i &+ 1
698+
}
699+
guard n > 1 else { return divide(by: other._low) }
700+
let clz = other[n &- 1].leadingZeroBitCount
701+
702+
var m = count - n
703+
guard let j = lastIndex(where: { $0 != 0 }) else { return Self() }
704+
if m > j &+ 1 {
705+
removeLast(m &- (j &+ 1))
706+
m = j &+ 1
707+
}
708+
precondition(m >= 0)
709+
710+
// 1. Normalize operands.
711+
shift(&other, left: clz)
712+
shift(&self, left: clz)
713+
let word = other[n &- 1]
714+
715+
// 2. Initialize loop.
716+
var result = Self(repeating: 0, count: m &+ 1)
717+
for idx in (n...(n &+ m)).reversed() {
718+
let (high, low) = (self[idx], self[idx &- 1])
719+
720+
// 3. Calculate trial quotient and remainder.
721+
var overflow = false
722+
var (,): (UInt, UInt)
723+
if word > high {
724+
(,) = word.dividingFullWidth((high, low))
725+
} else {
726+
(,) = word < high
727+
? word.dividingFullWidth((high &- word, low))
728+
: low.quotientAndRemainder(dividingBy: word)
729+
overflow = true
730+
}
731+
while overflow || q̂.multipliedFullWidth(by: other[n &- 2]) > (, low) {
732+
&-= 1
733+
&+= word
734+
if< word { break }
735+
if== UInt.max { overflow = false }
736+
}
737+
738+
// 4. Multiply and subtract.
739+
let slice = self[(idx &- n)...idx]
740+
var x = Self(slice), y = other
741+
y.multiply(by:)
742+
overflow = x.subtract(y)
743+
744+
// 5. Test remainder.
745+
if overflow {
746+
// 6. Add back.
747+
-= 1
748+
x.add(y)
749+
}
750+
751+
replaceSubrange((idx &- n)...idx, with: x[0...n])
752+
result[idx &- n] =
753+
} // 7. Loop.
754+
755+
// 8. Unnormalize.
756+
removeLast(m &+ 1)
757+
shift(&self, right: clz)
758+
759+
swap(&self, &result)
760+
return result
673761
}
674762
}

Sources/BigIntModule/BigInt.swift

+52-30
Original file line numberDiff line numberDiff line change
@@ -390,48 +390,70 @@ extension BigInt: BinaryInteger {
390390
@inlinable
391391
public static var isSigned: Bool { true }
392392

393-
// @inlinable
393+
@inlinable
394394
public static func / (lhs: BigInt, rhs: BigInt) -> BigInt {
395-
guard rhs._combination != 0 else { fatalError("Division by zero") }
396-
guard abs(rhs) <= abs(lhs) else { return 0 }
397-
398-
//TODO: Implement division. For now, this is a skeleton placeholder.
399-
if lhs._exponent == rhs._exponent {
400-
let combination = lhs._signum * rhs._signum
401-
let (significand, _) =
402-
lhs._significand.quotientAndRemainder(dividingBy: rhs._significand)
403-
var result = BigInt(_combination: combination, significand: significand)
404-
result._normalize()
405-
return result
406-
}
407-
fatalError("Not implemented")
395+
var result = lhs
396+
result /= rhs
397+
return result
408398
}
409399

410400
// @inlinable
411401
public static func /= (lhs: inout BigInt, rhs: BigInt) {
412-
lhs = lhs / rhs
413-
}
414-
415-
// @inlinable
416-
public static func % (lhs: BigInt, rhs: BigInt) -> BigInt {
417402
guard rhs._combination != 0 else { fatalError("Division by zero") }
418-
guard abs(rhs) <= abs(lhs) else { return lhs }
403+
guard lhs._combination != 0 && abs(lhs) >= abs(rhs) else {
404+
lhs = 0
405+
return
406+
}
407+
408+
var rhs = rhs
409+
let exponents = (lhs._exponent, rhs._exponent)
410+
if exponents.0 < exponents.1 {
411+
rhs._significand.insert(
412+
contentsOf: repeatElement(0, count: exponents.1 &- exponents.0), at: 0)
413+
} else if exponents.0 > exponents.1 {
414+
lhs._significand.insert(
415+
contentsOf: repeatElement(0, count: exponents.0 &- exponents.1), at: 0)
416+
}
417+
lhs._combination = lhs._signum * rhs._signum
419418

420-
//TODO: Implement remainder. For now, this is a skeleton placeholder.
421-
if lhs._exponent == rhs._exponent {
422-
let combination = lhs._combination * rhs._signum
423-
let (_, significand) =
424-
lhs._significand.quotientAndRemainder(dividingBy: rhs._significand)
425-
var result = BigInt(_combination: combination, significand: significand)
426-
result._normalize()
427-
return result
419+
if lhs._significand != rhs._significand {
420+
lhs._significand.divide(by: rhs._significand)
421+
} else {
422+
lhs._significand = _Significand(1)
428423
}
429-
fatalError("Not implemented")
424+
lhs._normalize()
425+
}
426+
427+
@inlinable
428+
public static func % (lhs: BigInt, rhs: BigInt) -> BigInt {
429+
var result = lhs
430+
result %= rhs
431+
return result
430432
}
431433

432434
// @inlinable
433435
public static func %= (lhs: inout BigInt, rhs: BigInt) {
434-
lhs = lhs % rhs
436+
guard rhs._combination != 0 else { fatalError("Division by zero") }
437+
guard lhs._combination != 0 && abs(lhs) >= abs(rhs) else { return }
438+
439+
var rhs = rhs
440+
let exponents = (lhs._exponent, rhs._exponent)
441+
if exponents.0 < exponents.1 {
442+
rhs._significand.insert(
443+
contentsOf: repeatElement(0, count: exponents.1 &- exponents.0), at: 0)
444+
} else if exponents.0 > exponents.1 {
445+
lhs._significand.insert(
446+
contentsOf: repeatElement(0, count: exponents.0 &- exponents.1), at: 0)
447+
}
448+
lhs._combination = lhs._signum
449+
450+
if lhs._significand != rhs._significand {
451+
var result = lhs._significand.divide(by: rhs._significand)
452+
swap(&result, &lhs._significand)
453+
} else {
454+
lhs._significand = _Significand()
455+
}
456+
lhs._normalize()
435457
}
436458

437459
// @inlinable

Tests/BigIntTests/BigIntTests.swift

+27
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,33 @@ final class BigIntModuleTests: XCTestCase {
165165
}
166166
}
167167

168+
func testDivision() {
169+
let x = BigInt("327441402998268901582239630362983195839")!
170+
let y = BigInt("279240677930711642307518656231691197860")!
171+
XCTAssertEqual(x / y, 1)
172+
173+
for _ in 0..<50 {
174+
let a = _randomWords(count: 6)
175+
let b = _randomWords(count: 4)
176+
177+
XCTAssertEqual(a.0 / b.0, BigInt(a.1 / b.1))
178+
XCTAssertEqual(a.0 % b.0, BigInt(a.1 % b.1))
179+
XCTAssertEqual(
180+
(a.0 << 128) / (b.0 << 42),
181+
BigInt((a.1 << 128) / (b.1 << 42)))
182+
XCTAssertEqual(
183+
(a.0 << 128) % (b.0 << 42),
184+
BigInt((a.1 << 128) % (b.1 << 42)))
185+
XCTAssertEqual(
186+
(a.0 << 42) / (b.0 << 128),
187+
BigInt((a.1 << 42) / (b.1 << 128)))
188+
XCTAssertEqual(
189+
(a.0 << 42) % (b.0 << 128),
190+
BigInt((a.1 << 42) % (b.1 << 128)))
191+
XCTAssertEqual((a.0 << 128) / (b.0 << 128), a.0 / b.0)
192+
}
193+
}
194+
168195
func testBitwiseOperators() {
169196
for _ in 0..<100 {
170197
let a = _randomWords(count: 8)

0 commit comments

Comments
 (0)