diff --git a/Tests/BigIntTests/BinaryShiftLeftTests.swift b/Tests/BigIntTests/BinaryShiftLeftTests.swift
new file mode 100644
index 00000000..cd685ce0
--- /dev/null
+++ b/Tests/BigIntTests/BinaryShiftLeftTests.swift
@@ -0,0 +1,143 @@
+//===--- BinaryShiftLeftTests.swift ---------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+@testable import BigIntModule
+
+// A lot of those bit shenanigans are based on the following observation:
+// 7<<5 -> 224
+// -7<<5 -> -224
+// Shifting values with the same magnitude gives us result with the same
+// magnitude (224 vs -224). Later you just have to do sign correction.
+
+private typealias Word = BigIntPrototype.Word
+
+private let intWidth = Int.bitWidth
+private let intShifts = [
+ 0, 1, 5, 8,
+ intWidth / 2,
+ intWidth - 3,
+ intWidth
+]
+
+class BinaryShiftLeftTests: XCTestCase {
+
+ // MARK: - Int
+
+ func test_int_byPositive() {
+ for int in generateInts(approximateCount: 100) {
+ let big = BigInt(int)
+
+ for s in intShifts {
+ let expected = int.shiftLeftFullWidth(by: s)
+ XCTAssertEqual(big << s, expected, "\(int) << \(s)")
+ }
+ }
+ }
+
+ func test_int_byNegative() {
+ for int in generateInts(approximateCount: 100) {
+ let big = BigInt(int)
+
+ for s in intShifts {
+ let expected = BigInt(int << -s)
+ XCTAssertEqual(big << -s, expected, "\(int) << -\(s)")
+ }
+ }
+ }
+
+ // MARK: - Big
+
+ func test_big_byZero() {
+ for p in generateBigInts(approximateCount: 100) {
+ let big = p.create()
+ let expected = p.create()
+ XCTAssertEqual(big << 0, expected, "\(big)")
+ }
+ }
+
+ func test_big_byWholeWord() {
+ for p in generateBigInts(approximateCount: 35) {
+ // Shifting '0' obeys a bit different rules
+ if p.isZero {
+ continue
+ }
+
+ for wordCount in 1...3 {
+ let big = p.create()
+
+ let prefix = [Word](repeating: 0, count: wordCount)
+ let expected = BigInt(p.sign, magnitude: prefix + p.magnitude)
+
+ let bitShift = wordCount * Word.bitWidth
+ XCTAssertEqual(big << bitShift, expected, "\(big) << \(bitShift)")
+ }
+ }
+ }
+
+ func test_big_byBits_noOverflow() {
+ for p in generateBigInts(approximateCount: 50) {
+ // Shifting '0' obeys a bit different rules
+ if p.isZero {
+ continue
+ }
+
+ for s in 1...3 {
+ // Clear high bits, so we can shift without overflow
+ let lowBitsMask: Word = (1 << s) - 1
+ let highBitsMask = lowBitsMask << (Word.bitWidth - s)
+ let remainingBitsMask = ~highBitsMask
+
+ let bigMagnitude = p.magnitude.map { $0 & remainingBitsMask }
+ let big = BigInt(p.sign, magnitude: bigMagnitude)
+
+ let expectedMagnitude = p.magnitude.map { $0 << s }
+ let expected = BigInt(p.sign, magnitude: expectedMagnitude)
+ XCTAssertEqual(big << s, expected, "\(big) << \(s)")
+ }
+ }
+ }
+
+ /// `1011 << 5 = 1_0110_0000` (assuming that our Word has 4 bits)
+ func test_big_exampleFromCode() {
+ // 1000_0000…0011
+ let word = Word(bitPattern: 1 << (Word.bitWidth - 1) | 0b0011)
+ let big = BigInt(.positive, magnitude: word)
+ let shift = Word.bitWidth + 1
+ let expected = BigInt(.positive, magnitude: [0b0000, 0b0110, 0b0001])
+ XCTAssertEqual(big << shift, expected, "\(big) << \(shift)")
+ }
+
+ func test_big_right() {
+ let wordShift = 1
+ let bitShift = -wordShift * Word.bitWidth
+
+ for p in generateBigInts(approximateCount: 50) {
+ // We just want to test if we call 'shiftRight',
+ // we do not care about edge cases.
+ guard p.magnitude.count > wordShift else {
+ continue
+ }
+
+ // Set lowest word to '0' to avoid floor rounding case:
+ // -5 / 2 = -3 not -2
+ var words = p.magnitude
+ words[0] = 0
+
+ let big = BigInt(p.sign, magnitude: words)
+
+ let expectedMagnitude = Array(words.dropFirst(wordShift))
+ let expectedIsPositive = expectedMagnitude.isEmpty || p.isPositive
+ let expected = BigInt(isPositive: expectedIsPositive, magnitude: expectedMagnitude)
+ XCTAssertEqual(big << bitShift, expected, "\(big) << \(bitShift)")
+ }
+ }
+}
diff --git a/Tests/BigIntTests/BinaryShiftRightTests.swift b/Tests/BigIntTests/BinaryShiftRightTests.swift
new file mode 100644
index 00000000..3eeaa83c
--- /dev/null
+++ b/Tests/BigIntTests/BinaryShiftRightTests.swift
@@ -0,0 +1,219 @@
+//===--- BinaryShiftRightTests.swift --------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+@testable import BigIntModule
+
+// A lot of those bit shenanigans are based on the following observation:
+// 12345 >> 3 -> 1543
+// -12345 >> 3 -> -1543
+// Shifting values with the same magnitude gives us result with the same
+// magnitude (1543 vs -1543). Later you just have to do sign correction.
+
+private typealias Word = BigIntPrototype.Word
+
+private let intWidth = Int.bitWidth
+private let intShifts = [
+ 0, 1, 5, 8,
+ intWidth / 2,
+ intWidth - 3,
+ intWidth
+]
+
+class BinaryShiftRightTests: XCTestCase {
+
+ // MARK: - Int
+
+ func test_int_byPositive() {
+ for int in generateInts(approximateCount: 100) {
+ for s in intShifts {
+ let big = BigInt(int)
+ let expected = BigInt(int >> s)
+ XCTAssertEqual(big >> s, expected, "\(int) >> \(s)")
+ }
+ }
+ }
+
+ func test_int_byMoreThanBitWidth() {
+ let zero = BigInt()
+ let minus1 = BigInt(-1)
+ let moreThanBitWidth = 3 * intWidth
+
+ for int in generateInts(approximateCount: 100) {
+ let big = BigInt(int)
+ let expected = int >= 0 ? zero : minus1
+ XCTAssertEqual(big >> moreThanBitWidth, expected, "\(int) >> \(moreThanBitWidth)")
+ }
+ }
+
+ func test_int_byNegative() {
+ for int in generateInts(approximateCount: 100) {
+ let big = BigInt(int)
+
+ for s in intShifts {
+ let expected = int.shiftLeftFullWidth(by: s)
+ XCTAssertEqual(big >> -s, expected, "\(int) << \(s)")
+ }
+ }
+ }
+
+ // MARK: - Big
+
+ func test_big_byZero() {
+ for p in generateBigInts(approximateCount: 100) {
+ let big = p.create()
+ let expected = p.create()
+ XCTAssertEqual(big >> 0, expected, "\(big)")
+ }
+ }
+
+ /// `1011_0000_0000 >> 5 = 0101_1000` (assuming that our Word has 4 bits):
+ func test_big_exampleFromCode() {
+ let big = BigInt(.positive, magnitude: [0b0000, 0b0000, 0b1011])
+ let shift = Word.bitWidth + 1
+ let expected = BigInt(.positive, magnitude: [1 << (Word.bitWidth - 1), 0b0101])
+ XCTAssertEqual(big >> shift, expected)
+ }
+
+ func test_big_byMoreThanBitWidth() {
+ let zero = BigInt()
+ let minus1 = BigInt(-1)
+
+ for p in generateBigInts(approximateCount: 35) {
+ let big = p.create()
+ let moreThanBitWidth = p.magnitude.count * Word.bitWidth + 7
+ let expected = p.isPositive ? zero : minus1
+ XCTAssertEqual(big >> moreThanBitWidth, expected, "\(big) >> \(moreThanBitWidth)")
+ }
+ }
+
+ func test_big_positive_byWholeWord() {
+ for p in generateBigInts(approximateCount: 50) {
+ // No point in shifting '0'
+ if p.isZero {
+ continue
+ }
+
+ for wordShift in 1..
> bitShift, expected, "\(p) >> \(bitShift)")
+ }
+ }
+ }
+
+ func test_big_negative_byWholeWord_withoutAdjustment() {
+ for p in generateBigInts(approximateCount: 50) {
+ // No point in shifting '0'
+ if p.isZero {
+ continue
+ }
+
+ for wordShift in 1..> bitShift, expected, "\(p) >> \(bitShift)")
+ }
+ }
+ }
+
+ func test_big_negative_byWholeWord_withAdjustment() {
+ for p in generateBigInts(approximateCount: 50) {
+ // No point in shifting '0'
+ if p.isZero {
+ continue
+ }
+
+ for wordShift in 1..> bitShift, expected, "\(p) >> \(bitShift)")
+ }
+ }
+ }
+
+ func test_big_byBits_noOverflow() {
+ for p in generateBigInts(approximateCount: 50) {
+ // There is a different test for this
+ if p.isZero {
+ continue
+ }
+
+ for s in 1...3 {
+ // Clean low bits, so we can shift without overflow
+ let lowBitsMask: Word = (1 << s) - 1
+ let remainingBitsMask = ~lowBitsMask
+
+ let bigMagnitude = p.magnitude.map { $0 & remainingBitsMask }
+ let big = BigInt(p.sign, magnitude: bigMagnitude)
+
+ let expectedMagnitude = p.magnitude.map { $0 >> s }
+ let expected = BigInt(p.sign, magnitude: expectedMagnitude)
+ XCTAssertEqual(big >> s, expected, "\(big) >> \(s)")
+ }
+ }
+ }
+
+ func test_big_left() {
+ let wordShift = 1
+ let bitShift = -wordShift * Word.bitWidth
+
+ for p in generateBigInts(approximateCount: 50) {
+ // We just want to test if we call 'shiftLeft',
+ // we do not care about edge cases.
+ guard p.magnitude.count > wordShift else {
+ continue
+ }
+
+ let big = p.create()
+
+ let expectedMagnitude = [0] + p.magnitude
+ let expectedIsPositive = expectedMagnitude.isEmpty || p.isPositive
+ let expected = BigInt(isPositive: expectedIsPositive, magnitude: expectedMagnitude)
+ XCTAssertEqual(big >> bitShift, expected, "\(big) >> \(bitShift)")
+ }
+ }
+}
diff --git a/Tests/BigIntTests/Helpers/BigInt+Extensions.swift b/Tests/BigIntTests/Helpers/BigInt+Extensions.swift
new file mode 100644
index 00000000..4be41225
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/BigInt+Extensions.swift
@@ -0,0 +1,69 @@
+//===--- BigInt+Extensions.swift ------------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import BigIntModule
+
+extension BigInt {
+
+ internal typealias Word = Words.Element
+
+ internal init(isPositive: Bool, magnitude: [BigIntPrototype.Word]) {
+ let p = BigIntPrototype(isPositive: isPositive, magnitude: magnitude)
+ self = p.create()
+ }
+
+ internal init(_ sign: BigIntPrototype.Sign, magnitude: BigIntPrototype.Word) {
+ let p = BigIntPrototype(sign, magnitude: magnitude)
+ self = p.create()
+ }
+
+ internal init(_ sign: BigIntPrototype.Sign, magnitude: [BigIntPrototype.Word]) {
+ let p = BigIntPrototype(sign, magnitude: magnitude)
+ self = p.create()
+ }
+
+ internal func power(exponent: BigInt) -> BigInt {
+ precondition(exponent >= 0, "Exponent must be positive")
+
+ if exponent == 0 {
+ return BigInt(1)
+ }
+
+ if exponent == 1 {
+ return self
+ }
+
+ // This has to be after 'exp == 0', because 'pow(0, 0) -> 1'
+ if self == 0 {
+ return 0
+ }
+
+ var base = self
+ var exponent = exponent
+ var result = BigInt(1)
+
+ // Eventually we will arrive to most significant '1'
+ while exponent != 1 {
+ let exponentIsOdd = exponent & 0b1 == 1
+
+ if exponentIsOdd {
+ result *= base
+ }
+
+ base *= base
+ exponent >>= 1 // Basically divided by 2, but faster
+ }
+
+ // Most significant '1' is odd:
+ result *= base
+ return result
+ }
+}
diff --git a/Tests/BigIntTests/Helpers/BigIntPrototype.swift b/Tests/BigIntTests/Helpers/BigIntPrototype.swift
new file mode 100644
index 00000000..6995a8bd
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/BigIntPrototype.swift
@@ -0,0 +1,293 @@
+//===--- BigIntPrototype.swift --------------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+import BigIntModule
+
+// MARK: - BigIntPrototype
+
+/// Abstract way of representing `BigInt` without assuming internal representation.
+/// Basically an immutable `Word` collection with a sign.
+///
+/// It can be used to create multiple independent `BigInt` instances with
+/// the same value (used during equality tests etc).
+internal struct BigIntPrototype {
+
+ internal typealias Word = UInt64
+
+ internal enum Sign {
+ case positive
+ case negative
+ }
+
+ /// Internally `Int1`.
+ internal let sign: Sign
+ /// Least significant word is at index `0`.
+ /// Empty means `0`.
+ internal let magnitude: [Word]
+
+ internal var isPositive: Bool {
+ return self.sign == .positive
+ }
+
+ internal var isNegative: Bool {
+ return self.sign == .negative
+ }
+
+ internal var isZero: Bool {
+ return self.magnitude.isEmpty
+ }
+
+ /// Is `magnitude == 1`?` It may be `+1` or `-1` depending on the `self.sign`.
+ internal var isMagnitude1: Bool {
+ return self.magnitude.count == 1 && self.magnitude[0] == 1
+ }
+
+ internal init(_ sign: Sign, magnitude: Word) {
+ self.init(sign, magnitude: [magnitude])
+ }
+
+ internal init(_ sign: Sign, magnitude: [Word]) {
+ self.sign = sign
+ self.magnitude = magnitude
+
+ let isZero = self.magnitude.isEmpty
+ let zeroHasPositiveSign = !isZero || sign == .positive
+ assert(zeroHasPositiveSign, "[BigIntPrototype] Negative zero")
+ }
+
+ internal init(isPositive: Bool, magnitude: [Word]) {
+ let sign: Sign = isPositive ? .positive : .negative
+ self.init(sign, magnitude: magnitude)
+ }
+
+ /// `BigInt` -> `BigIntPrototype`.
+ @available(
+ *,
+ deprecated,
+ message: "Use only when writing test cases to convert BigInt -> Prototype."
+ )
+ internal init(_ big: BigInt) {
+ var n = abs(big)
+
+ let power = BigInt(Word.max) + 1
+ var magnitude = [Word]()
+
+ while n != 0 {
+ let rem = n % power
+ magnitude.append(Word(rem))
+ n /= power
+ }
+
+ let sign = big < 0 ? Sign.negative : Sign.positive
+ self = BigIntPrototype(sign, magnitude: magnitude)
+ }
+
+ internal var withOppositeSign: BigIntPrototype {
+ assert(!self.isZero, "[BigIntPrototype] Negative zero: (0).withOppositeSign")
+ return BigIntPrototype(isPositive: !self.isPositive, magnitude: self.magnitude)
+ }
+
+ internal func withAddedWord(word: Word) -> BigIntPrototype {
+ var magnitude = self.magnitude
+ magnitude.append(word)
+ return BigIntPrototype(isPositive: self.isPositive, magnitude: magnitude)
+ }
+
+ internal var withRemovedWord: BigIntPrototype {
+ assert(!self.isZero, "[BigIntPrototype] Removing word from zero")
+
+ var magnitude = self.magnitude
+ magnitude.removeLast()
+
+ // Removing word may have made the whole value '0', which could change sign!
+ let isZero = magnitude.isEmpty
+ let isPositive = self.isPositive || isZero
+ return BigIntPrototype(isPositive: isPositive, magnitude: magnitude)
+ }
+
+ /// Collection where each element is a `BigIntPrototype` with one of the words
+ /// modified by provided `wordChange`.
+ ///
+ /// Used to easily create prototypes with smaller/bigger magnitude.
+ /// Useful for testing `==`, `<`, `>`, `<=` and `>=`.
+ internal func withEachMagnitudeWordModified(
+ byAdding wordChange: Int
+ ) -> WithEachMagnitudeWordModified {
+ return WithEachMagnitudeWordModified(base: self, by: wordChange)
+ }
+
+ internal func create() -> BigInt {
+ return BigIntPrototype.create(isPositive: self.isPositive, magnitude: self.magnitude)
+ }
+
+ internal static func create(
+ isPositive: Bool,
+ magnitude: [T]
+ ) -> BigInt {
+ assert(!T.isSigned)
+ var result = BigInt()
+ var power = BigInt(1)
+
+ for word in magnitude {
+ // As for the magic '+1' in power:
+ // Without it (example for UInt8):
+ // [255] -> 255*1 = 255
+ // [0, 1] -> 0 *1 + 1*255 = 255
+ // So, we have 2 ways of representing '255' (aka. T.max)
+ // With it:
+ // [255] -> 255*1 = 255
+ // [0, 1] -> 0 *1 + 1*(255+1) = 256
+ result += BigInt(word) * power
+ power *= BigInt(T.max) + 1
+ }
+
+ if !isPositive {
+ result *= -1
+ }
+
+ return result
+ }
+
+ internal enum CompareResult {
+ case equal
+ case less
+ case greater
+ }
+
+ internal static func compare(_ lhs: BigIntPrototype,
+ _ rhs: BigIntPrototype) -> CompareResult {
+ let lhsM = lhs.magnitude
+ let rhsM = rhs.magnitude
+
+ guard lhsM.count == rhsM.count else {
+ return lhsM.count > rhsM.count ? .greater : .less
+ }
+
+ for (l, r) in zip(lhsM, rhsM).reversed() {
+ if l > r {
+ return .greater
+ }
+
+ if l < r {
+ return .less
+ }
+ }
+
+ return .equal
+ }
+}
+
+// MARK: - Modify magnitude words
+
+internal struct WithEachMagnitudeWordModified: Sequence {
+
+ internal typealias Element = BigIntPrototype
+
+ internal struct Iterator: IteratorProtocol {
+
+ private let base: BigIntPrototype
+ private let wordChange: Int
+ private var wordIndex = 0
+
+ internal init(base: BigIntPrototype, wordChange: Int) {
+ self.base = base
+ self.wordChange = wordChange
+ }
+
+ internal mutating func next() -> Element? {
+ var magnitude = self.base.magnitude
+ let wordChangeMagnitude = BigIntPrototype.Word(self.wordChange.magnitude)
+
+ while self.wordIndex < magnitude.count {
+ let word = magnitude[self.wordIndex]
+ defer { self.wordIndex += 1 }
+
+ let (newWord, hasOverflow) = self.wordChange >= 0 ?
+ word.addingReportingOverflow(wordChangeMagnitude) :
+ word.subtractingReportingOverflow(wordChangeMagnitude)
+
+ if !hasOverflow {
+ magnitude[self.wordIndex] = newWord
+ let isPositive = self.base.isPositive
+ return BigIntPrototype(isPositive: isPositive, magnitude: magnitude)
+ }
+ }
+
+ return nil
+ }
+ }
+
+ private let base: BigIntPrototype
+ private let wordChange: Int
+
+ internal init(base: BigIntPrototype, by wordChange: Int) {
+ self.base = base
+ self.wordChange = wordChange
+ }
+
+ internal func makeIterator() -> Iterator {
+ return Iterator(base: self.base, wordChange: self.wordChange)
+ }
+}
+
+// MARK: - Tests
+
+/// Tests for `BigIntPrototype`.
+/// Yep… we are testing our test code…
+class BigIntPrototypeTests: XCTestCase {
+
+ func test_create() {
+ let p1 = BigIntPrototype(.positive, magnitude: [.max])
+ let big1 = p1.create()
+ let expected1 = BigInt(BigIntPrototype.Word.max)
+ XCTAssertEqual(big1, expected1)
+
+ let p2 = BigIntPrototype(.positive, magnitude: [0, 1])
+ let big2 = p2.create()
+ let expected2 = BigInt(BigIntPrototype.Word.max) + 1
+ XCTAssertEqual(big2, expected2)
+ }
+
+ func test_magnitudeWordModified_positive() {
+ let p = BigIntPrototype(.positive, magnitude: [3, .max, 5])
+ let modified = p.withEachMagnitudeWordModified(byAdding: 7)
+ var iter = modified.makeIterator()
+
+ let p1 = iter.next()
+ XCTAssertEqual(p1?.magnitude, [10, .max, 5])
+
+ // '.max' should be skipped, because it overflows
+
+ let p2 = iter.next()
+ XCTAssertEqual(p2?.magnitude, [3, .max, 12])
+
+ let p3 = iter.next()
+ XCTAssertNil(p3)
+ }
+
+ func test_magnitudeWordModified_negative() {
+ let p = BigIntPrototype(.positive, magnitude: [7, 3, 11])
+ let modified = p.withEachMagnitudeWordModified(byAdding: -5)
+ var iter = modified.makeIterator()
+
+ let p1 = iter.next()
+ XCTAssertEqual(p1?.magnitude, [2, 3, 11])
+
+ // '3' should be skipped, because it overflows
+
+ let p2 = iter.next()
+ XCTAssertEqual(p2?.magnitude, [7, 3, 6])
+
+ let p3 = iter.next()
+ XCTAssertNil(p3)
+ }
+}
diff --git a/Tests/BigIntTests/Helpers/CartesianProduct.swift b/Tests/BigIntTests/Helpers/CartesianProduct.swift
new file mode 100644
index 00000000..cbbb6b1b
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/CartesianProduct.swift
@@ -0,0 +1,67 @@
+//===--- CartesianProduct.swift -------------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+/// `[1, 2], [A, B] -> [(1,A), (1,B), (2,A), (2,B)]`
+internal struct CartesianProduct: Sequence {
+
+ internal typealias Element = (T, V)
+
+ internal struct Iterator: IteratorProtocol {
+
+ private let lhsValues: [T]
+ private let rhsValues: [V]
+
+ // Index of the next emitted element
+ private var lhsIndex = 0
+ private var rhsIndex = 0
+
+ fileprivate init(lhs: [T], rhs: [V]) {
+ self.lhsValues = lhs
+ self.rhsValues = rhs
+ }
+
+ internal mutating func next() -> Element? {
+ if self.lhsIndex == self.lhsValues.count {
+ return nil
+ }
+
+ let lhs = self.lhsValues[self.lhsIndex]
+ let rhs = self.rhsValues[self.rhsIndex]
+
+ self.rhsIndex += 1
+ if self.rhsIndex == self.rhsValues.count {
+ self.lhsIndex += 1
+ self.rhsIndex = 0
+ }
+
+ return (lhs, rhs)
+ }
+ }
+
+ private let lhsValues: [T]
+ private let rhsValues: [V]
+
+ /// `[1, 2] -> [(1,1), (1,2), (2,1), (2,2)]`
+ internal init(_ values: [T]) where T == V {
+ self.lhsValues = values
+ self.rhsValues = values
+ }
+
+ /// `[1, 2], [A, B] -> [(1,A), (1,B), (2,A), (2,B)]`
+ internal init(_ lhs: [T], _ rhs: [V]) {
+ self.lhsValues = lhs
+ self.rhsValues = rhs
+ }
+
+ internal func makeIterator() -> Iterator {
+ return Iterator(lhs: self.lhsValues, rhs: self.rhsValues)
+ }
+}
diff --git a/Tests/BigIntTests/Helpers/GenerateNumbers.swift b/Tests/BigIntTests/Helpers/GenerateNumbers.swift
new file mode 100644
index 00000000..2f547fa9
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/GenerateNumbers.swift
@@ -0,0 +1,77 @@
+//===--- GenerateNumbers.swift --------------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+@testable import BigIntModule
+
+internal func generateInts(approximateCount: Int) -> [Int] {
+ assert(approximateCount > 0, "[generateInts] Negative 'approximateCount'.")
+
+ var result = [Int]()
+ result.append(0)
+ result.append(1)
+ result.append(-1)
+
+ // 'Int' has smaller range on the positive side, so we will use it to calculate 'step'.
+ let approximateCountHalf = approximateCount / 2
+ let step = Int.max / approximateCountHalf
+
+ // 1st iteration will append 'Int.min' and 'Int.max'
+ for i in 0.. [BigIntPrototype] {
+ assert(approximateCount > 0, "[generateBigInts] Negative 'approximateCount'.")
+ assert(maxWordCount > 0, "[generateBigInts] Negative 'maxWordCount'.")
+
+ typealias Word = BigIntPrototype.Word
+ var result = [BigIntPrototype]()
+
+ result.append(BigIntPrototype(.positive, magnitude: [])) // 0
+ result.append(BigIntPrototype(.positive, magnitude: [1])) // 1
+ result.append(BigIntPrototype(.negative, magnitude: [1])) // -1
+
+ // All words = Word.max
+ for count in 1...maxWordCount {
+ let magnitude = Array(repeating: Word.max, count: count)
+ result.append(BigIntPrototype(.positive, magnitude: magnitude))
+ result.append(BigIntPrototype(.negative, magnitude: magnitude))
+ }
+
+ let approximateCountHalf = approximateCount / 2
+ var word = Word.max / 2 // Start from half and go up by 1
+
+ for i in 0..= .zero ? .positive : .negative
+ }
+}
+
+extension Int {
+ internal func shiftLeftFullWidth(by n: Int) -> BigInt {
+ // A lot of those bit shenanigans are based on the following observation:
+ // 7<<5 -> 224
+ // -7<<5 -> -224
+ // Shifting values with the same magnitude gives us result with the same
+ // magnitude (224 vs -224). Later you just have to do sign correction.
+ let magnitude = self.magnitude
+ let width = Int.bitWidth
+
+ let low = magnitude << n
+ let high = magnitude >> (width - n) // Will sign extend
+ let big = (BigInt(high) << width) | BigInt(low)
+ return self < 0 ? -big : big
+ }
+}
diff --git a/Tests/BigIntTests/Helpers/PowersOf2.swift b/Tests/BigIntTests/Helpers/PowersOf2.swift
new file mode 100644
index 00000000..a7360b35
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/PowersOf2.swift
@@ -0,0 +1,60 @@
+//===--- PowersOf2.swift --------------------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+internal typealias PowerOf2 = (power: Int, value: T)
+
+/// `1, 2, 4, 8, 16, 32, 64, 128, 256, 512, etc…`
+internal func PositivePowersOf2(
+ type: T.Type
+) -> [PowerOf2] {
+ var result = [PowerOf2]()
+ result.reserveCapacity(T.bitWidth)
+
+ var value = T(1)
+ var power = 0
+ result.append(PowerOf2(power: power, value: value))
+
+ while true {
+ let (newValue, overflow) = value.multipliedReportingOverflow(by: 2)
+ if overflow {
+ return result
+ }
+
+ value = newValue
+ power += 1
+ result.append(PowerOf2(power: power, value: value))
+ }
+}
+
+/// `-1, -2, -4, -8, -16, -32, -64, -128, -256, -512, etc…`
+internal func NegativePowersOf2(
+ type: T.Type
+) -> [PowerOf2] {
+ assert(T.isSigned)
+
+ var result = [PowerOf2]()
+ result.reserveCapacity(T.bitWidth)
+
+ var value = T(-1)
+ var power = 0
+ result.append(PowerOf2(power: power, value: value))
+
+ while true {
+ let (newValue, overflow) = value.multipliedReportingOverflow(by: 2)
+ if overflow {
+ return result
+ }
+
+ value = newValue
+ power += 1
+ result.append(PowerOf2(power: power, value: value))
+ }
+}
diff --git a/Tests/BigIntTests/Helpers/StringTestCases.generated.py b/Tests/BigIntTests/Helpers/StringTestCases.generated.py
new file mode 100644
index 00000000..74ad9cf6
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/StringTestCases.generated.py
@@ -0,0 +1,295 @@
+#===--- StringTestCases.generated.py ---------------------------*- swift -*-===#
+#
+# This source file is part of the Swift Numerics open source project
+#
+# Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+# Licensed under Apache License v2.0 with Runtime Library Exception
+#
+# See https://swift.org/LICENSE.txt for license information
+#
+#===------------------------------------------------------------------------===#
+
+from dataclasses import dataclass
+
+@dataclass
+class Radix:
+ name: str
+ num: int
+ groups: 'list[Group]'
+
+@dataclass
+class Group:
+ name: str
+ strings: 'list[str]'
+
+TEST_SUITES = [
+ Radix('Binary', 2, [
+ Group('singleWord', [
+ "1100111011101101011000001101001100000111010111010001001011011111",
+ "1111101001111100000100011110010100110111000111000010000010101011",
+ "1010101000000000010110000110101100010000101011011101000000011111",
+ "100111011011000011001000001000101100001010001100110001001100000",
+ "100101110001011110101110001100011101011110100111010111010010011",
+ "1100100111110100011110000101010101101010011111000110100010101000",
+ "1001110110010011111100000111111010000100110010011001101111101",
+ "101000001100111110010110000001111001100010100101011010000101011",
+ "1011001011100101000101001101111111000110011111110010100110011101",
+ "110001001001100000011000010001100001100101111100111100111011111"
+ ]),
+ Group('twoWords', [
+ "1001010111000000011001110010011010011001010101000010000001111100110101011111111001110000111010011100001101001010100101010001011",
+ "10110101001011101011011100010010110100001011100101100010110100101000101110111011101011110000110100110010000011110001111101011011",
+ "10100100110001100010101010111111010100101000000101100000010100001000011011000101000101010100110111100100011001101110111010100111",
+ "101001000110111101111011100010100111000011101101100101000011010000010000011101101011101000101011101111101011101110011001000110",
+ "1010110101111011110001001010101111011010100101110010111000001100011110001110001010001001101001101010110101001000101100010111000",
+ "10011100000010010010000000100011000011101010110110001111010111110111100111100100111001010000110000000010111101101000110101001",
+ "1110010111010011100100010110001000000100000000000011100101010011000101011010001001100011000010110110111101111111111101111",
+ "11011000000110100101000110000001110110000000011110011000111011100000011000001110011001110111101010100000010101000011011011010000",
+ "10111010111010100010000000011010111011111101001100000111110001111001111001110000010111010010001100010000101011001101000100101100",
+ "11111110000010110011011111011111000010011110101011101010010010001110011111010100000100001110111100010011100100110111001100110100"
+ ]),
+ ]),
+
+ Radix('Quinary', 5, [
+ Group('singleWord', [
+ "1141011330110244430443234403",
+ "1222004313120111310102442141",
+ "2103043303342222322321322331",
+ "112432412234101120414313034",
+ "303003222112001403223431323",
+ "344142232044113412301430224",
+ "1220020343312232444212101300",
+ "2142220433422310201433143031",
+ "1100240414331322213333313314",
+ "1142232034423230304021344224"
+ ]),
+ Group('twoWords', [
+ "203123203032243200414100331344240044242020031202313304",
+ "2241012313012133231212400333342433431343403034400222232",
+ "4203331403433210032231444444420301011232202040320244034",
+ "10232010021201310022422112231324444321204423440331141040",
+ "11001404212423031440100214040300432233323022042011441003",
+ "31000242443014011010113041320310341223011340044321112",
+ "104440411024104113410312042432323043144001330034323023",
+ "4313032440022022004204011201231102003140212144013012024",
+ "222241220112410000313023011200201140300201034000223104",
+ "4142443213143020430040201142133120302113431131300414310"
+ ]),
+ ]),
+
+ Radix('Octal', 8, [
+ Group('singleWord', [
+ "765107426714576726434",
+ "267013363740675530517",
+ "1116473563031361443420",
+ "402115621515715443232",
+ "763120023606053712451",
+ "1331662240274670615110",
+ "61361450215541537600",
+ "667611024411512075216",
+ "1766576732635234733764",
+ "24762136610221532221"
+ ]),
+ Group('twoWords', [
+ "405353062163251224075541120406576603642133",
+ "1155525567215754355762261022040102502663535",
+ "473760700275426615762334030650135006412004",
+ "3631321221020124075140070266326054733736654",
+ "2301713511046076774644163027526344262614750",
+ "1416756055467752004624014151506657745444701",
+ "277372133117223126207604631572452705305761",
+ "3233035510177647760346424273160270510130153",
+ "3603463414404503570230716440162341562104627",
+ "52174705323046362171603350231267240463067"
+ ]),
+ Group('threeWords', [
+ "752710076366767021311145241064414721036644045634530560410662164",
+ "2351074425357060771500511210531201334130757470561023603752203522",
+ "2026330410062602113214720620652354122635242532243542521246347130",
+ "2374670546314622762117042710735422651021224342402213330677717022",
+ "2516462603442110716460774444162701130544323471604701667527612160",
+ "4752321765021165454357330136132660667377344246225716247045110530",
+ "2207017273376155030021416376730413255440672604251274423333345737",
+ "6012155132310337104403010016271520303605661047423036543777774653",
+ "2405264541731765272641476323011120172731141622224604547111014030",
+ "102016446227413552760304443460703260141047363565146174776151646"
+ ]),
+ ]),
+
+ Radix('Decimal', 10, [
+ Group('singleWord', [
+ "7718190171501264284",
+ "10916363490721922425",
+ "7933533405371913824",
+ "10480426996613498135",
+ "2095192256445644812",
+ "7419235996356813804",
+ "1781771517166335135",
+ "11133038279461172192",
+ "2130720192200721827",
+ "14853271410540786435",
+ "6950267042901794576",
+ "10411748895426429475",
+ "9833709291961056769",
+ "5999039672382756712",
+ "16110142630232532658",
+ "12607569496212494176",
+ "1675868323700977277",
+ "16806170715214457379",
+ "16940169654426845777",
+ "8827990282256005918"
+ ]),
+ Group('twoWords', [
+ "174279629237543296687673032485957064212",
+ "47412561600764236150769686558222116091",
+ "10395912425457665851645833014443244462",
+ "164315376478873129818157066650996676197",
+ "10602886535953881315042562817407645178",
+ "8248650871275789350502376241754844651",
+ "34524867949202500042981821345963565272",
+ "134216757748210966888150667727713411016",
+ "171102533986768447955502501639763888523",
+ "54844876601597866882605545088807789686",
+ "56893583067640428051926870614216611763",
+ "324345033888321898323997479933055678710",
+ "303929611043690622871586457595241643110",
+ "247198033769360767204907027173829796027",
+ "21778317495144550468861398478632800064",
+ "84588039840439783849569873830235438676",
+ "311126277149403728470285334436356936983",
+ "139898191933164899225423319256353529614",
+ "2043258753110477277143778428409140808",
+ "337382537729550367819433505076096015588"
+ ]),
+ Group('threeWords', [
+ "6083614195465063921457756617643704987757901496806223861094",
+ "4559809898618458323618774365881241097705852866053722576068",
+ "6000655493235750363443630214517832475022981384493522723977",
+ "3448761127815156638054418593304414317156905140903945500758",
+ "4031646778810151685478902878630817277976717194269043459535",
+ "5187043450362884349943980736394397527883291975412376418584",
+ "867137008351148227772945110512985612059866264001066314234",
+ "405425063163426737085717989265456363407329145867582794766",
+ "516394380123300269594485907074283812975608688889426642145",
+ "5812603356047926610460197272765392220238610608713665689986",
+ "984508521516077779720769747676107292251302380633744113615",
+ "3607214625740907391200152863690596886095271299895459353996",
+ "3555336686841570490688168198501982767988360618443302183344",
+ "5421257579835065546517323313625099317184145652987724078671",
+ "5289825533547636872288114877966109957241807144779629060472",
+ "2220442274754652930479991837181424586345958361124409139160",
+ "2503918262582474917700425110083118534477438840011330691707",
+ "2932737070354268352744296521741629050767038012966002878856",
+ "5936703106691826260722357215905339148900071080037029998472",
+ "638165476008693250974186539568945174625645764897016299466"
+ ]),
+ Group('fourWords', [
+ "98329738845668996570124208357521780017272355350396828224707284828351881091866",
+ "105623193693730296505637022908493828321474575998233295842297319498067956265586",
+ "27744159210101084003408741123228345882260348087436638008210479865903937724447",
+ "43975332751786641545687151785881018379208099070772924031466259723893919847420",
+ "7291043565309214047592216113421685977429724781349367031290578029129539586602",
+ "83988462562950544098864456303214580453611103336990060118099235083777904234247",
+ "32980242605770942006188369357622369959610697780125045082756386640312378695240",
+ "22417645777855749287001476980921879614161082692816351875309530936088143706194",
+ "7263649581484524992809489869886295226321246688450694700744863589918010440354",
+ "24728838211123196082450064688238894776920371466824252419807721449583553632242"
+ ]),
+ ]),
+
+ Radix('Hex', 16, [
+ Group('singleWord', [
+ "7d48f16e65fbad1c",
+ "2dc16f3f06f6b14f",
+ "93a77730cbc64710",
+ "4089b91a6f36469a",
+ "7cca013c30af9529",
+ "b6764a05e6e31a48",
+ "c5e32846d86bf80",
+ "6df121484d287a8e",
+ "fdafddacea73b7f4",
+ "53e45ec4246b491"
+ ]),
+ Group('twoWords', [
+ "d84a106bf60f445b20aeb191cd52941e",
+ "2c424202150b675d4db55bba37d8edf9",
+ "37031a82e81a1404277f0e02f62d8df9",
+ "e16cd61676fbdacf32d148840a83d30",
+ "1cc2f56722cb19e8983cba48987dfcd2",
+ "30d346d7f9649c161dee16cdfd404ca",
+ "e13337a957158bf117efa2d93d265643",
+ "45176705c520b06bd361da41ff4ff073",
+ "73a407270dc88997f07338641287784c",
+ "e0dd0995ba8266370547ce2b4c4cf23c"
+ ]),
+ Group('threeWords', [
+ "1eae40f9edf708b24caa11a433a21ed20973958b84236474",
+ "4e91e455de30fcd029288aca05b858f7ce2e213c1fa90752",
+ "4166c420658225a33a190d53b0a59d515694762a8a99ce58",
+ "4fcdc5999992f913c45c8eec4b52114a38a048b6c6ff9e12",
+ "54e9960e4448e74c3f92439704b16469ce709c1dbd5f1470",
+ "9ea68fd42275963bdb05e2d6c36eff722992bce538949158",
+ "48707aedfc6d0c0461cfeec42d5b20dd61152bc89b6dcbdf",
+ "c0a3696990df2240c100e5cd418785d889e261eb1ffff9ab",
+ "5055a587b3f55d6867cd304940f5d930e492984b39241818",
+ "42074992f0bb57c189239870d606113bceea663e7f8d3a6"
+ ]),
+ ]),
+]
+
+# UInt64.max + 1
+# See the comment in 'BigIntPrototype.create' for '+1' explanation
+POWER = 18446744073709551615 + 1
+
+def main():
+ print('''\
+//===--- StringTestCases.generated.swift ----------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+// Automatically generated. DO NOT EDIT!
+// To regenerate:
+// python3 StringTestCases.generated.py > StringTestCases.generated.swift
+//===----------------------------------------------------------------------===//
+
+// swiftlint:disable line_length
+// swiftlint:disable trailing_comma
+// swiftformat:disable numberFormatting
+
+extension StringTestCases {\
+''')
+
+ for radix in TEST_SUITES:
+ print()
+ print(f' // MARK: - {radix.name}')
+ print()
+ print(f' internal enum {radix.name} {{')
+
+ for group in radix.groups:
+ print()
+ print(f' internal static let {group.name} = TestSuite(radix: {radix.num}, cases: [')
+
+ for s in group.strings:
+ words = []
+ i = int(s, radix.num)
+
+ while i != 0:
+ d, m = divmod(i, POWER)
+ words.append(m)
+ i = d
+
+ print(f' TestCase({words}, "{s}"),')
+ print(' ])')
+
+ print(' }')
+
+ print('}')
+
+if __name__ == '__main__':
+ main()
diff --git a/Tests/BigIntTests/Helpers/StringTestCases.generated.swift b/Tests/BigIntTests/Helpers/StringTestCases.generated.swift
new file mode 100644
index 00000000..d40bb648
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/StringTestCases.generated.swift
@@ -0,0 +1,258 @@
+//===--- StringTestCases.generated.swift ----------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+// Automatically generated. DO NOT EDIT!
+// To regenerate:
+// python3 StringTestCases.generated.py > StringTestCases.generated.swift
+//===----------------------------------------------------------------------===//
+
+// swiftlint:disable line_length
+// swiftlint:disable trailing_comma
+// swiftformat:disable numberFormatting
+
+extension StringTestCases {
+
+ // MARK: - Binary
+
+ internal enum Binary {
+
+ internal static let singleWord = TestSuite(radix: 2, cases: [
+ TestCase([14910680400771486431], "1100111011101101011000001101001100000111010111010001001011011111"),
+ TestCase([18049321082763878571], "1111101001111100000100011110010100110111000111000010000010101011"),
+ TestCase([12249888203312320543], "1010101000000000010110000110101100010000101011011101000000011111"),
+ TestCase([5681400955737104992], "100111011011000011001000001000101100001010001100110001001100000"),
+ TestCase([5443681076643081875], "100101110001011110101110001100011101011110100111010111010010011"),
+ TestCase([14552388604195006632], "1100100111110100011110000101010101101010011111000110100010101000"),
+ TestCase([1419335438964437885], "1001110110010011111100000111111010000100110010011001101111101"),
+ TestCase([5793822662808745003], "101000001100111110010110000001111001100010100101011010000101011"),
+ TestCase([12890732459758397853], "1011001011100101000101001101111111000110011111110010100110011101"),
+ TestCase([7083049658624145887], "110001001001100000011000010001100001100101111100111100111011111"),
+ ])
+
+ internal static let twoWords = TestSuite(radix: 2, cases: [
+ TestCase([7709943161734646411, 5395369061329276990], "1001010111000000011001110010011010011001010101000010000001111100110101011111111001110000111010011100001101001010100101010001011"),
+ TestCase([10068833863126163291, 13055573661232751314], "10110101001011101011011100010010110100001011100101100010110100101000101110111011101011110000110100110010000011110001111101011011"),
+ TestCase([9711191595782958759, 11873224468820222032], "10100100110001100010101010111111010100101000000101100000010100001000011011000101000101010100110111100100011001101110111010100111"),
+ TestCase([296585062226257478, 2962206244791346445], "101001000110111101111011100010100111000011101101100101000011010000010000011101101011101000101011101111101011101110011001000110"),
+ TestCase([4355337989126379704, 6250400716541368070], "1010110101111011110001001010101111011010100101110010111000001100011110001110001010001001101001101010110101001000101100010111000"),
+ TestCase([17238825691124781481, 1405444159956169195], "10011100000010010010000000100011000011101010110110001111010111110111100111100100111001010000110000000010111101101000110101001"),
+ TestCase([11973739651872522223, 129380782069776498], "1110010111010011100100010110001000000100000000000011100101010011000101011010001001100011000010110110111101111111111101111"),
+ TestCase([436399990275061456, 15571848279703918830], "11011000000110100101000110000001110110000000011110011000111011100000011000001110011001110111101010100000010101000011011011010000"),
+ TestCase([11416727460569207084, 13468612935669712839], "10111010111010100010000000011010111011111101001100000111110001111001111001110000010111010010001100010000101011001101000100101100"),
+ TestCase([16704995536835670836, 18305786541461137992], "11111110000010110011011111011111000010011110101011101010010010001110011111010100000100001110111100010011100100110111001100110100"),
+ ])
+ }
+
+ // MARK: - Quinary
+
+ internal enum Quinary {
+
+ internal static let singleWord = TestSuite(radix: 5, cases: [
+ TestCase([10195599536115211853], "1141011330110244430443234403"),
+ TestCase([11148293617344187171], "1222004313120111310102442141"),
+ TestCase([16581359024097057841], "2103043303342222322321322331"),
+ TestCase([1963548865060307269], "112432412234101120414313034"),
+ TestCase([4650830292194358338], "303003222112001403223431323"),
+ TestCase([5923527504604717564], "344142232044113412301430224"),
+ TestCase([11032004014403237700], "1220020343312232444212101300"),
+ TestCase([17731643299662006016], "2142220433422310201433143031"),
+ TestCase([8974493917839354209], "1100240414331322213333313314"),
+ TestCase([10284022934724793689], "1142232034423230304021344224"),
+ ])
+
+ internal static let twoWords = TestSuite(radix: 5, cases: [
+ TestCase([7166946866828356326, 1283328998154523208], "203123203032243200414100331344240044242020031202313304"),
+ TestCase([17746844892252647709, 7729270025137380738], "2241012313012133231212400333342433431343403034400222232"),
+ TestCase([3117040153726440296, 13330676859597655378], "4203331403433210032231444444420301011232202040320244034"),
+ TestCase([9389283096438931568, 16660274568627612047], "10232010021201310022422112231324444321204423440331141040"),
+ TestCase([10151668648408986613, 18099786241267349540], "11001404212423031440100214040300432233323022042011441003"),
+ TestCase([1888399307037804872, 385298439426531010], "31000242443014011010113041320310341223011340044321112"),
+ TestCase([6144037938277486548, 721424258481946865], "104440411024104113410312042432323043144001330034323023"),
+ TestCase([14314271871241051703, 14038673589000308396], "4313032440022022004204011201231102003140212144013012024"),
+ TestCase([1610709691090591847, 1506362658927364202], "222241220112410000313023011200201140300201034000223104"),
+ TestCase([1877780960810668148, 13192324888935710577], "4142443213143020430040201142133120302113431131300414310"),
+ ])
+ }
+
+ // MARK: - Octal
+
+ internal enum Octal {
+
+ internal static let singleWord = TestSuite(radix: 8, cases: [
+ TestCase([9027730909523848476], "765107426714576726434"),
+ TestCase([3297038718702367055], "267013363740675530517"),
+ TestCase([10639603696146990864], "1116473563031361443420"),
+ TestCase([4650451613422864026], "402115621515715443232"),
+ TestCase([8992000964025095465], "763120023606053712451"),
+ TestCase([13147777551363676744], "1331662240274670615110"),
+ TestCase([891205320620556160], "61361450215541537600"),
+ TestCase([7922149813937273486], "667611024411512075216"),
+ TestCase([18280073147257698292], "1766576732635234733764"),
+ TestCase([377816299772228753], "24762136610221532221"),
+ ])
+
+ internal static let twoWords = TestSuite(radix: 8, cases: [
+ TestCase([15585287516344763483, 2355014894934463518], "405353062163251224075541120406576603642133"),
+ TestCase([3189184062842169181, 5599482567064088057], "1155525567215754355762261022040102502663535"),
+ TestCase([3964041246558262276, 2846008895404346873], "473760700275426615762334030650135006412004"),
+ TestCase([1015224584249523628, 17522684300601343280], "3631321221020124075140070266326054733736654"),
+ TestCase([2072488601858021864, 10969847613326490834], "2301713511046076774644163027526344262614750"),
+ TestCase([219889601707657665, 7052321924236707018], "1416756055467752004624014151506657745444701"),
+ TestCase([16227375082796059633, 1724776236223714883], "277372133117223126207604631572452705305761"),
+ TestCase([4978561187561123947, 15231695391734886515], "3233035510177647760346424273160270510130153"),
+ TestCase([8332793074858625431, 17326254193883183180], "3603463414404503570230716440162341562104627"),
+ TestCase([16203117573032797751, 380499378895123004], "52174705323046362171603350231267240463067"),
+ ])
+
+ internal static let threeWords = TestSuite(radix: 8, cases: [
+ TestCase([681052395112981620, 5524247289861906130, 2210775909268916402], "752710076366767021311145241064414721036644045634530560410662164"),
+ TestCase([14856848762854770514, 2965772954907465975, 5661557264032529616], "2351074425357060771500511210531201334130757470561023603752203522"),
+ TestCase([6238741308901019224, 4186391981714677073, 4712669703510828451], "2026330410062602113214720620652354122635242532243542521246347130"),
+ TestCase([4080341212257558034, 14149341274818351434, 5750469562719205651], "2374670546314622762117042710735422651021224342402213330677717022"),
+ TestCase([14875561220749857904, 4580798086887072873, 6118586556778866508], "2516462603442110716460774444162701130544323471604701667527612160"),
+ TestCase([2995664394837594456, 15782269881219481458, 11431982845400553019], "4752321765021165454357330136132660667377344246225716247045110530"),
+ TestCase([6995545736791051231, 7048114468200063197, 5219807130683247620], "2207017273376155030021416376730413255440672604251274423333345737"),
+ TestCase([9935611390414813611, 13907368319050548696, 13881054378609025600], "6012155132310337104403010016271520303605661047423036543777774653"),
+ TestCase([16470394236095961112, 7479687647312861488, 5788714898313010536], "2405264541731765272641476323011120172731141622224604547111014030"),
+ TestCase([13614001671611405222, 1770540855717814547, 297365776674567548], "102016446227413552760304443460703260141047363565146174776151646"),
+ ])
+ }
+
+ // MARK: - Decimal
+
+ internal enum Decimal {
+
+ internal static let singleWord = TestSuite(radix: 10, cases: [
+ TestCase([7718190171501264284], "7718190171501264284"),
+ TestCase([10916363490721922425], "10916363490721922425"),
+ TestCase([7933533405371913824], "7933533405371913824"),
+ TestCase([10480426996613498135], "10480426996613498135"),
+ TestCase([2095192256445644812], "2095192256445644812"),
+ TestCase([7419235996356813804], "7419235996356813804"),
+ TestCase([1781771517166335135], "1781771517166335135"),
+ TestCase([11133038279461172192], "11133038279461172192"),
+ TestCase([2130720192200721827], "2130720192200721827"),
+ TestCase([14853271410540786435], "14853271410540786435"),
+ TestCase([6950267042901794576], "6950267042901794576"),
+ TestCase([10411748895426429475], "10411748895426429475"),
+ TestCase([9833709291961056769], "9833709291961056769"),
+ TestCase([5999039672382756712], "5999039672382756712"),
+ TestCase([16110142630232532658], "16110142630232532658"),
+ TestCase([12607569496212494176], "12607569496212494176"),
+ TestCase([1675868323700977277], "1675868323700977277"),
+ TestCase([16806170715214457379], "16806170715214457379"),
+ TestCase([16940169654426845777], "16940169654426845777"),
+ TestCase([8827990282256005918], "8827990282256005918"),
+ ])
+
+ internal static let twoWords = TestSuite(radix: 10, cases: [
+ TestCase([4443533457689204244, 9447717631965633948], "174279629237543296687673032485957064212"),
+ TestCase([17900669220997358843, 2570240114532569528], "47412561600764236150769686558222116091"),
+ TestCase([7856018056960015278, 563563541832512549], "10395912425457665851645833014443244462"),
+ TestCase([16030846250062419557, 8907554407558390165], "164315376478873129818157066650996676197"),
+ TestCase([76456108598031866, 574783630844925132], "10602886535953881315042562817407645178"),
+ TestCase([16060639402207784427, 447160259735582989], "8248650871275789350502376241754844651"),
+ TestCase([6724383833077440728, 1871596841765025634], "34524867949202500042981821345963565272"),
+ TestCase([17721423422461386696, 7275905016728549520], "134216757748210966888150667727713411016"),
+ TestCase([13753655854536165771, 9275486953311460472], "171102533986768447955502501639763888523"),
+ TestCase([16007314175766750326, 2973146718057590835], "54844876601597866882605545088807789686"),
+ TestCase([13668675975230855091, 3084207318121013092], "56893583067640428051926870614216611763"),
+ TestCase([17634210073973176566, 17582779518830157984], "324345033888321898323997479933055678710"),
+ TestCase([1179859661762935910, 16476057228812186700], "303929611043690622871586457595241643110"),
+ TestCase([7466570045805584571, 13400632262344301616], "247198033769360767204907027173829796027"),
+ TestCase([1307790023500255040, 1180604957065739539], "21778317495144550468861398478632800064"),
+ TestCase([10557776168390327892, 4585526828064760774], "84588039840439783849569873830235438676"),
+ TestCase([4287714958589154583, 16866189280135533900], "311126277149403728470285334436356936983"),
+ TestCase([6956547535360810766, 7583896181036572753], "139898191933164899225423319256353529614"),
+ TestCase([3961997723213026888, 110765278953620120], "2043258753110477277143778428409140808"),
+ TestCase([16244342368417094884, 18289544018252558769], "337382537729550367819433505076096015588"),
+ ])
+
+ internal static let threeWords = TestSuite(radix: 10, cases: [
+ TestCase([3788030118483678566, 13587601199963990513, 17878135298378645545], "6083614195465063921457756617643704987757901496806223861094"),
+ TestCase([3556988877394908356, 12474154662934588154, 13400076941623863208], "4559809898618458323618774365881241097705852866053722576068"),
+ TestCase([6943250440187782281, 16148677006591030242, 17634341583823379554], "6000655493235750363443630214517832475022981384493522723977"),
+ TestCase([12051381132750026838, 7772465072843729846, 10134998057705544164], "3448761127815156638054418593304414317156905140903945500758"),
+ TestCase([11057770507354506703, 3754418486115532988, 11847945032505514529], "4031646778810151685478902878630817277976717194269043459535"),
+ TestCase([4058671830152788248, 17848382429627053213, 15243350683428292588], "5187043450362884349943980736394397527883291975412376418584"),
+ TestCase([9506519811871484410, 10336689296818807801, 2548286636764283718], "867137008351148227772945110512985612059866264001066314234"),
+ TestCase([8153835003846552590, 6452612927418895754, 1191437178575943052], "405425063163426737085717989265456363407329145867582794766"),
+ TestCase([11092524183389504737, 10258419301515066693, 1517546691578291045], "516394380123300269594485907074283812975608688889426642145"),
+ TestCase([17450711516373662082, 12266023495873027824, 17081706021512517970], "5812603356047926610460197272765392220238610608713665689986"),
+ TestCase([10493740789275983823, 7090478780156208175, 2893210513446379807], "984508521516077779720769747676107292251302380633744113615"),
+ TestCase([2584946677711410572, 15582369744544450926, 10600651036904921818], "3607214625740907391200152863690596886095271299895459353996"),
+ TestCase([8191223326464221616, 15838770264786859451, 10448195476633736002], "3555336686841570490688168198501982767988360618443302183344"),
+ TestCase([9330481725652115023, 17984447776108471806, 15931644148621564667], "5421257579835065546517323313625099317184145652987724078671"),
+ TestCase([5834919825408647544, 18291287390831708357, 15545400078801850136], "5289825533547636872288114877966109957241807144779629060472"),
+ TestCase([7725628935030398936, 13217523222545559873, 6525293375752710251], "2220442274754652930479991837181424586345958361124409139160"),
+ TestCase([11153747151801819771, 12447701429598628384, 7358354431466140957], "2503918262582474917700425110083118534477438840011330691707"),
+ TestCase([1305957527465355656, 6634926787110467165, 8618539646621370010], "2932737070354268352744296521741629050767038012966002878856"),
+ TestCase([5697551272666427272, 9806098653662596381, 17446402411063414409], "5936703106691826260722357215905339148900071080037029998472"),
+ TestCase([13461627091841105866, 15779306612146539460, 1875399779845087415], "638165476008693250974186539568945174625645764897016299466"),
+ ])
+
+ internal static let fourWords = TestSuite(radix: 10, cases: [
+ TestCase([8069127371757787930, 18298415571011048517, 16815374448153862577, 15664831157880175362], "98329738845668996570124208357521780017272355350396828224707284828351881091866"),
+ TestCase([5470742250373313138, 17521113983887669137, 1031109059281010587, 16826745550145797929], "105623193693730296505637022908493828321474575998233295842297319498067956265586"),
+ TestCase([7821963600184975391, 1696593353817084268, 18062377089319569726, 4419899561878296347], "27744159210101084003408741123228345882260348087436638008210479865903937724447"),
+ TestCase([5034162668176282620, 13810266618081868282, 678065491460384283, 7005674689622930240], "43975332751786641545687151785881018379208099070772924031466259723893919847420"),
+ TestCase([948109800916453930, 13254873860379351332, 9460782306108757222, 1161530252760842443], "7291043565309214047592216113421685977429724781349367031290578029129539586602"),
+ TestCase([15835724493698649863, 17125118148463518722, 13959435657126725002, 13380134033748730320], "83988462562950544098864456303214580453611103336990060118099235083777904234247"),
+ TestCase([4071443539966139976, 11664926414955986211, 16616938295452084138, 5254055772243955785], "32980242605770942006188369357622369959610697780125045082756386640312378695240"),
+ TestCase([13537182919290894418, 9915062231487163470, 5294088489907226107, 3571337015533456534], "22417645777855749287001476980921879614161082692816351875309530936088143706194"),
+ TestCase([9724782435949804194, 5610697598620232897, 7986759389249900697, 1157166139356361906], "7263649581484524992809489869886295226321246688450694700744863589918010440354"),
+ TestCase([3131625851484723186, 8251872111016498371, 5091559339788432642, 3939531212584346483], "24728838211123196082450064688238894776920371466824252419807721449583553632242"),
+ ])
+ }
+
+ // MARK: - Hex
+
+ internal enum Hex {
+
+ internal static let singleWord = TestSuite(radix: 16, cases: [
+ TestCase([9027730909523848476], "7d48f16e65fbad1c"),
+ TestCase([3297038718702367055], "2dc16f3f06f6b14f"),
+ TestCase([10639603696146990864], "93a77730cbc64710"),
+ TestCase([4650451613422864026], "4089b91a6f36469a"),
+ TestCase([8992000964025095465], "7cca013c30af9529"),
+ TestCase([13147777551363676744], "b6764a05e6e31a48"),
+ TestCase([891205320620556160], "c5e32846d86bf80"),
+ TestCase([7922149813937273486], "6df121484d287a8e"),
+ TestCase([18280073147257698292], "fdafddacea73b7f4"),
+ TestCase([377816299772228753], "53e45ec4246b491"),
+ ])
+
+ internal static let twoWords = TestSuite(radix: 16, cases: [
+ TestCase([2355014894934463518, 15585287516344763483], "d84a106bf60f445b20aeb191cd52941e"),
+ TestCase([5599482567064088057, 3189184062842169181], "2c424202150b675d4db55bba37d8edf9"),
+ TestCase([2846008895404346873, 3964041246558262276], "37031a82e81a1404277f0e02f62d8df9"),
+ TestCase([17522684300601343280, 1015224584249523628], "e16cd61676fbdacf32d148840a83d30"),
+ TestCase([10969847613326490834, 2072488601858021864], "1cc2f56722cb19e8983cba48987dfcd2"),
+ TestCase([7052321924236707018, 219889601707657665], "30d346d7f9649c161dee16cdfd404ca"),
+ TestCase([1724776236223714883, 16227375082796059633], "e13337a957158bf117efa2d93d265643"),
+ TestCase([15231695391734886515, 4978561187561123947], "45176705c520b06bd361da41ff4ff073"),
+ TestCase([17326254193883183180, 8332793074858625431], "73a407270dc88997f07338641287784c"),
+ TestCase([380499378895123004, 16203117573032797751], "e0dd0995ba8266370547ce2b4c4cf23c"),
+ ])
+
+ internal static let threeWords = TestSuite(radix: 16, cases: [
+ TestCase([681052395112981620, 5524247289861906130, 2210775909268916402], "1eae40f9edf708b24caa11a433a21ed20973958b84236474"),
+ TestCase([14856848762854770514, 2965772954907465975, 5661557264032529616], "4e91e455de30fcd029288aca05b858f7ce2e213c1fa90752"),
+ TestCase([6238741308901019224, 4186391981714677073, 4712669703510828451], "4166c420658225a33a190d53b0a59d515694762a8a99ce58"),
+ TestCase([4080341212257558034, 14149341274818351434, 5750469562719205651], "4fcdc5999992f913c45c8eec4b52114a38a048b6c6ff9e12"),
+ TestCase([14875561220749857904, 4580798086887072873, 6118586556778866508], "54e9960e4448e74c3f92439704b16469ce709c1dbd5f1470"),
+ TestCase([2995664394837594456, 15782269881219481458, 11431982845400553019], "9ea68fd42275963bdb05e2d6c36eff722992bce538949158"),
+ TestCase([6995545736791051231, 7048114468200063197, 5219807130683247620], "48707aedfc6d0c0461cfeec42d5b20dd61152bc89b6dcbdf"),
+ TestCase([9935611390414813611, 13907368319050548696, 13881054378609025600], "c0a3696990df2240c100e5cd418785d889e261eb1ffff9ab"),
+ TestCase([16470394236095961112, 7479687647312861488, 5788714898313010536], "5055a587b3f55d6867cd304940f5d930e492984b39241818"),
+ TestCase([13614001671611405222, 1770540855717814547, 297365776674567548], "42074992f0bb57c189239870d606113bceea663e7f8d3a6"),
+ ])
+ }
+}
diff --git a/Tests/BigIntTests/Helpers/StringTestCases.swift b/Tests/BigIntTests/Helpers/StringTestCases.swift
new file mode 100644
index 00000000..a303ffd7
--- /dev/null
+++ b/Tests/BigIntTests/Helpers/StringTestCases.swift
@@ -0,0 +1,77 @@
+//===--- StringTestCases.swift --------------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Numerics open source project
+//
+// Copyright (c) 2023 Apple Inc. and the Swift Numerics project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import BigIntModule
+
+// swiftlint:disable nesting
+// swiftlint:disable number_separator
+// swiftformat:disable numberFormatting
+
+// The actual test cases are generated by Python script.
+internal enum StringTestCases {
+
+ internal struct TestSuite {
+ internal let radix: Int
+ internal let cases: [TestCase]
+ }
+
+ /// Single test case, always positive.
+ internal struct TestCase {
+
+ internal typealias Word = BigIntPrototype.Word
+
+ /// Least significant word is at index `0`.
+ /// Empty means `0`.
+ private let magnitude: [Word]
+ /// String representation with a certain base.
+ internal let string: String
+
+ internal var isZero: Bool {
+ return self.magnitude.isEmpty
+ }
+
+ /// Insert `_` into `self.string`.
+ internal var stringWithUnderscores: String {
+ // We could create a pseudo-random algorithm to select underscore location.
+ // Or we could just insert underscore after every 3rd digit.
+ let underscoreAfterEvery = 3
+ let s = self.string
+
+ var result = ""
+ result.reserveCapacity(s.count + s.count / underscoreAfterEvery)
+
+ for (index, char) in s.enumerated() {
+ assert(char != "_")
+ result.append(char)
+
+ // Suffix underscore is prohibited.
+ let shouldHaveUnderscore = index.isMultiple(of: underscoreAfterEvery)
+ let isLast = index == s.count - 1
+
+ if shouldHaveUnderscore && !isLast {
+ result.append("_")
+ }
+ }
+
+ return result
+ }
+
+ internal init(_ magnitude: [Word], _ string: String) {
+ self.magnitude = magnitude
+ self.string = string
+ }
+
+ internal func create(sign: BigIntPrototype.Sign = .positive) -> BigInt {
+ let proto = BigIntPrototype(sign, magnitude: magnitude)
+ return proto.create()
+ }
+ }
+}