From 244fd7fe33e86239f0cab7145a5aa020b3c16d52 Mon Sep 17 00:00:00 2001 From: LiarPrincess <4982138+LiarPrincess@users.noreply.github.com> Date: Wed, 18 Jan 2023 16:16:21 +0100 Subject: [PATCH 1/4] Added --- .../Helpers/BigInt+Extensions.swift | 69 ++++ .../BigIntTests/Helpers/BigIntPrototype.swift | 293 +++++++++++++++++ .../Helpers/CartesianProduct.swift | 67 ++++ .../BigIntTests/Helpers/GenerateNumbers.swift | 77 +++++ .../BigIntTests/Helpers/Int+Extensions.swift | 44 +++ Tests/BigIntTests/Helpers/PowersOf2.swift | 60 ++++ .../Helpers/StringTestCases.generated.py | 295 ++++++++++++++++++ .../Helpers/StringTestCases.generated.swift | 258 +++++++++++++++ .../BigIntTests/Helpers/StringTestCases.swift | 77 +++++ 9 files changed, 1240 insertions(+) create mode 100644 Tests/BigIntTests/Helpers/BigInt+Extensions.swift create mode 100644 Tests/BigIntTests/Helpers/BigIntPrototype.swift create mode 100644 Tests/BigIntTests/Helpers/CartesianProduct.swift create mode 100644 Tests/BigIntTests/Helpers/GenerateNumbers.swift create mode 100644 Tests/BigIntTests/Helpers/Int+Extensions.swift create mode 100644 Tests/BigIntTests/Helpers/PowersOf2.swift create mode 100644 Tests/BigIntTests/Helpers/StringTestCases.generated.py create mode 100644 Tests/BigIntTests/Helpers/StringTestCases.generated.swift create mode 100644 Tests/BigIntTests/Helpers/StringTestCases.swift 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() + } + } +} From 8ee7bd785f369944b14e999c707a3a5198dcf157 Mon Sep 17 00:00:00 2001 From: LiarPrincess <4982138+LiarPrincess@users.noreply.github.com> Date: Wed, 18 Jan 2023 16:26:54 +0100 Subject: [PATCH 2/4] Added --- Tests/BigIntTests/BinaryDivRemTests.swift | 568 ++++++++++++++++++++++ Tests/BigIntTests/BinaryMulTests.swift | 372 ++++++++++++++ Tests/BigIntTests/PowerTests.swift | 154 ++++++ 3 files changed, 1094 insertions(+) create mode 100644 Tests/BigIntTests/BinaryDivRemTests.swift create mode 100644 Tests/BigIntTests/BinaryMulTests.swift create mode 100644 Tests/BigIntTests/PowerTests.swift diff --git a/Tests/BigIntTests/BinaryDivRemTests.swift b/Tests/BigIntTests/BinaryDivRemTests.swift new file mode 100644 index 00000000..e96a8dfd --- /dev/null +++ b/Tests/BigIntTests/BinaryDivRemTests.swift @@ -0,0 +1,568 @@ +//===--- BinaryDivRemTests.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 + +// swiftlint:disable number_separator +// swiftlint:disable file_length +// swiftformat:disable numberFormatting + +private typealias Word = BigIntPrototype.Word + +private let intZero = Int.zero +private let intMax = Int.max +private let intMaxAsWord = Word(intMax.magnitude) + +/// `2^n = value` +private typealias Pow2 = (value: Int, n: Int) + +private let powersOf2: [Pow2] = [ + (value: 2, n: 1), + (value: 4, n: 2), + (value: 16, n: 4) +] + +private func assertDiv(_ lhs: BigInt, + _ rhs: BigInt, + quotient _quotient: Q, + remainder _remainder: R, + file: StaticString = #file, + line: UInt = #line) { + assert(rhs != 0, "[BinaryDivTests] Oooo… div by 0? 🐰") + let quotient = BigInt(_quotient) + let remainder = BigInt(_remainder) + + // We could add the following check, but the current (2023.01) implementation + // would not survive it: + // guard (quotient * rhs + remainder) == lhs else { + // XCTFail("=== [\(lhs)/\(rhs)] INVALID TEST? ===", file: file, line: line) + // return + // } + + let q = lhs / rhs + let r = lhs % rhs + XCTAssertEqual(q, quotient, "[\(lhs)/\(rhs)] Quotient", file: file, line: line) + XCTAssertEqual(r, remainder, "[\(lhs)/\(rhs)] Remainder", file: file, line: line) + + // lhs == q * rhs + r + let restored = q * rhs + r + XCTAssertEqual(lhs, restored, "[\(lhs)/\(rhs)] lhs == q * rhs + r", file: file, line: line) + + // There are multiple solutions for 'lhs = q * rhs + r': + // | -5//4 | -5%4 | + // Python 3.7.4 | -2 | 3 | + // Swift 5.3.2 | -1 | -1 | <- we want this (round toward 0) + // + // Check: |q * rhs| <= |lhs| + // In Python: |-2*4| <= |-5| -> 8 <= 5 -> FALSE + // In Swift: |-1*4| <= |-5| -> 4 <= 5 -> TRUE + let qRhs = q * rhs + XCTAssertLessThanOrEqual(qRhs.magnitude, lhs.magnitude, "[\(lhs)/\(rhs)] |q * rhs| <= |lhs|", file: file, line: line) + + let (qq, rr) = lhs.quotientAndRemainder(dividingBy: rhs) + XCTAssertEqual(qq, quotient, "[\(lhs)/\(rhs)] quotientAndRemainder-Quotient", file: file, line: line) + XCTAssertEqual(rr, remainder, "[\(lhs)/\(rhs)] quotientAndRemainder-Remainder", file: file, line: line) +} + +class BinaryDivRemTests: XCTestCase { + + // MARK: - Int + + func test_int_zero() { + let zero = BigInt() + + for int in generateInts(approximateCount: 100) { + if int == 0 { + continue + } + + // 0 / x = 0 rem 0 + let big = BigInt(int) + assertDiv(zero, big, quotient: 0, remainder: 0) + // For obvious reasons we will not have 'big / zero' test + } + } + + func test_int_plus1() { + let one = BigInt(1) + + for int in generateInts(approximateCount: 100) { + let big = BigInt(int) + + // x / 1 = x rem 0 + assertDiv(big, one, quotient: big, remainder: 0) + + // 1 / x = 0 rem 1 (except for '1/1 = 1 rem 0' and '1/(-1) = -1 rem 0') + if int != 0 { + let (q, r) = (1).quotientAndRemainder(dividingBy: int) + assertDiv(one, big, quotient: q, remainder: r) + } + } + } + + func test_int_minus1() { + let minusOne = BigInt(-1) + + for int in generateInts(approximateCount: 100) { + let big = BigInt(int) + + // x / (-1) = -x + assertDiv(big, minusOne, quotient: -big, remainder: 0) + + // (-1) / x = 0 rem -1 (mostly) + if int != 0 { + let (q, r) = (-1).quotientAndRemainder(dividingBy: int) + assertDiv(minusOne, big, quotient: q, remainder: r) + } + } + } + + func test_int() { + let ints = generateInts(approximateCount: 20) + + for (lhsInt, rhsInt) in CartesianProduct(ints) { + let isOverflow = lhsInt == .min && rhsInt == -1 + if rhsInt == 0 || isOverflow { + continue + } + + let lhsBig = BigInt(lhsInt) + let rhsBig = BigInt(rhsInt) + let (q, r) = lhsInt.quotientAndRemainder(dividingBy: rhsInt) + assertDiv(lhsBig, rhsBig, quotient: q, remainder: r) + } + } + + /// Div by `n^2` (2, 4, 8) is the same as shift right by `n` + func test_int_powerOf2() { + for p in generateBigInts(approximateCount: 100) { + if p.isZero { + continue + } + + for power in powersOf2 { + guard let p = self.cleanBitsSoItCanBeDividedWithoutOverflow( + value: p, + power: power + ) else { continue } + + let big = p.create() + let rhs = BigInt(power.value) + + let expectedWords = p.magnitude.map { $0 >> power.n } + let isZero = expectedWords.allSatisfy { $0 == 0 } + let expectedIsPositive = p.isPositive || isZero + let expected = BigInt(isPositive: expectedIsPositive, magnitude: expectedWords) + + // Rem is '0' because we cleaned those bits + assertDiv(big, rhs, quotient: expected, remainder: 0) + } + } + } + + private func cleanBitsSoItCanBeDividedWithoutOverflow( + value: BigIntPrototype, + power: Pow2 + ) -> BigIntPrototype? { + // 1111 << 1 = 1110 + let mask = Word.max << power.n + let magnitude = value.magnitude.map { $0 & mask } + + // Zero may behave differently than other numbers + let allWordsZero = magnitude.allSatisfy { $0 == 0 } + if allWordsZero { + return nil + } + + return BigIntPrototype(value.sign, magnitude: magnitude) + } + + /// x / x = 1 rem 0 + func test_int_equalMagnitude() { + for int in generateInts(approximateCount: 100) { + if int == 0 { + continue + } + + let plus = int == .min ? nil : abs(int) + let minus = int < 0 ? int : -int + + let cases = [ + (plus, plus), // a / a + (plus, minus), // a / (-a) + (minus, plus), // -a / a + (minus, minus) // -a / (-a) + ] + + for (aInt, bInt) in cases { + guard let aInt = aInt, let bInt = bInt else { continue } + + let aBig = BigInt(aInt) + let bBig = BigInt(bInt) + let sameSign = (aInt < 0) == (bInt < 0) + let expectedQuotient = sameSign ? 1 : -1 + + // a/b + if bInt != 0 { + assertDiv(aBig, bBig, quotient: expectedQuotient, remainder: 0) + } + + // b/a + if aInt != 0 { + assertDiv(bBig, aBig, quotient: expectedQuotient, remainder: 0) + } + } + } + } + + /// (x+n) / x = 1 rem n + func test_int_lhs_hasGreaterMagnitude() { + let ints = generateInts(approximateCount: 20) + + for (a, b) in CartesianProduct(ints) { + // We have separate test for equal magnitude + if a.magnitude == b.magnitude { + continue + } + + let (biggerInt, smolInt) = a.magnitude > b.magnitude ? + (a, b) : (b, a) + + if smolInt == 0 { + continue + } + + let biggerBig = BigInt(biggerInt) + let smolBig = BigInt(smolInt) + + let isOverflow = biggerInt == Int.min && smolInt == -1 + let q = isOverflow ? -BigInt(Int.min) : BigInt(biggerInt / smolInt) + let r = isOverflow ? 0 : BigInt(biggerInt % smolInt) + assertDiv(biggerBig, smolBig, quotient: q, remainder: r) + } + } + + /// x / (x + n) = 0 rem x + func test_int_rhs_hasGreaterMagnitude() { + let ints = generateInts(approximateCount: 20) + + for (a, b) in CartesianProduct(ints) { + // We have separate test for equal magnitude + if a.magnitude == b.magnitude { + continue + } + + let (biggerInt, smallerInt) = a.magnitude > b.magnitude ? + (a, b) : (b, a) + + if biggerInt == 0 { + continue + } + + let biggerBig = BigInt(biggerInt) + let smallerBig = BigInt(smallerInt) + assertDiv(smallerBig, biggerBig, quotient: 0, remainder: smallerInt) + } + } + + func test_int_multipleWords() { + let bigWords: [Word] = [3689348814741910327, 2459565876494606880] + let int = BigInt(370955168) + let intNegative = -int + + let q = BigInt(.positive, magnitude: [10690820303666397895, 6630358837]) + let r = 237957591 + + // plus, plus + var big = BigInt(.positive, magnitude: bigWords) + assertDiv(big, int, quotient: q, remainder: r) + + // minus, plus + big = BigInt(.negative, magnitude: bigWords) + assertDiv(big, int, quotient: -q, remainder: -r) + + // plus, minus + big = BigInt(.positive, magnitude: bigWords) + assertDiv(big, intNegative, quotient: -q, remainder: r) + + // minus, minus + big = BigInt(.negative, magnitude: bigWords) + assertDiv(big, intNegative, quotient: q, remainder: -r) + } + + // MARK: - Big + + func test_big_zero() { + let zero = BigInt() + + for p in generateBigInts(approximateCount: 100) { + if p.isZero { + continue + } + + // 0 / x = 0 rem 0 + let big = p.create() + assertDiv(zero, big, quotient: 0, remainder: 0) + // For obvious reasons we will not have 'big / zero' test + } + } + + func test_big_plus1() { + let one = BigInt(1) + + for p in generateBigInts(approximateCount: 100) { + let big = p.create() + + // x / 1 = x rem 0 + assertDiv(big, one, quotient: big, remainder: 0) + + // 1 / x = 0 rem 1 (mostly) + if !p.isZero { + // 1 / 1 = 1 rem 0 + if p.isPositive && p.isMagnitude1 { + assertDiv(one, big, quotient: 1, remainder: 0) + continue + } + + // 1 / (-1) = -1 rem 0 + if p.isNegative && p.isMagnitude1 { + assertDiv(one, big, quotient: -1, remainder: 0) + continue + } + + // Remainder is always positive! + assertDiv(one, big, quotient: 0, remainder: 1) + } + } + } + + func test_big_minus1() { + let minusOne = BigInt(-1) + + for p in generateBigInts(approximateCount: 100) { + let big = p.create() + + // x / (-1) = -x + assertDiv(big, minusOne, quotient: -big, remainder: 0) + + // (-1) / x = 0 rem -1 (mostly) + if !p.isZero { + // (-1) / 1 = -1 rem 0 + if p.isPositive && p.isMagnitude1 { + assertDiv(minusOne, big, quotient: -1, remainder: 0) + continue + } + + // (-1) / (-1) = 1 rem 0 + if p.isNegative && p.isMagnitude1 { + assertDiv(minusOne, big, quotient: 1, remainder: 0) + continue + } + + assertDiv(minusOne, big, quotient: 0, remainder: -1) + } + } + } + + /// Div by `n^2` (2, 4, 8) is the same as shift right by `n` + func test_big_powerOf2() { + for p in generateBigInts(approximateCount: 20) { + if p.isZero { + continue + } + + for power in powersOf2 { + guard let p = self.cleanBitsSoItCanBeDividedWithoutOverflow( + value: p, + power: power + ) else { continue } + + let big = p.create() + let rhs = BigInt(power.value) + + let expectedWords = p.magnitude.map { $0 >> power.n } + let isZero = expectedWords.allSatisfy { $0 == 0 } + let expectedSign: BigIntPrototype.Sign = p.isPositive || isZero ? .positive : .negative + let expected = BigInt(expectedSign, magnitude: expectedWords) + + // Rem is '0' because we cleaned those bits + assertDiv(big, rhs, quotient: expected, remainder: 0) + } + } + } + + /// x / x = 1 rem 0 + func test_big_equalMagnitude() { + for p in generateBigInts(approximateCount: 100) { + if p.isZero { + continue + } + + let plus = BigInt(.positive, magnitude: p.magnitude) + let minus = BigInt(.negative, magnitude: p.magnitude) + + // plus / plus = 1 rem 0 + assertDiv(plus, plus, quotient: 1, remainder: 0) + + // plus / minus = -1 rem 0 + assertDiv(plus, minus, quotient: -1, remainder: 0) + + // minus / plus = -1 rem 0 + assertDiv(minus, plus, quotient: -1, remainder: 0) + + // minus / minus = 1 rem 0 + assertDiv(minus, minus, quotient: 1, remainder: 0) + } + } + + /// (x+n) / x = 1 rem n + func test_big_lhs_hasGreaterMagnitude() { + let bigs = generateBigInts(approximateCount: 20) + + for (lhs, rhs) in CartesianProduct(bigs) { + let bigger: BigInt + let smol: BigInt + + switch BigIntPrototype.compare(lhs, rhs) { + case .equal: + // We have separate test for equal magnitude + continue + case .less: + bigger = rhs.create() + smol = lhs.create() + case .greater: + bigger = lhs.create() + smol = rhs.create() + } + + if smol == 0 { + continue + } + + // We don't know the exact values, just check that it did not crash + // (and trust me, overflow is strong with this one). + _ = bigger / smol + _ = bigger % smol + _ = bigger.quotientAndRemainder(dividingBy: smol) + } + } + + /// x / (x + n) = 0 rem x + func test_big_rhs_hasGreaterMagnitude() { + let bigs = generateBigInts(approximateCount: 20) + + for (lhs, rhs) in CartesianProduct(bigs) { + let bigger: BigInt + let smol: BigInt + + switch BigIntPrototype.compare(lhs, rhs) { + case .equal: + // We have separate test for equal magnitude + continue + case .less: + bigger = rhs.create() + smol = lhs.create() + case .greater: + bigger = lhs.create() + smol = rhs.create() + } + + if bigger == 0 { + continue + } + + assertDiv(smol, bigger, quotient: 0, remainder: smol) + } + } + + func test_big_lhsLonger() { + let lhsWords: [Word] = [3689348814741910327, 2459565876494606880] + let rhsWords: [Word] = [1844674407370955168] + + let quotient = BigInt(.positive, magnitude: [6148914691236517100, 1]) + let remainder = BigInt(.positive, magnitude: [1229782938247304119]) + + // plus, plus + var lhs = BigInt(.positive, magnitude: lhsWords) + var rhs = BigInt(.positive, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: quotient, remainder: remainder) + + // minus, plus + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.positive, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: -quotient, remainder: -remainder) + + // plus, minus + lhs = BigInt(.positive, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: -quotient, remainder: remainder) + + // minus, minus + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: quotient, remainder: -remainder) + } + + func test_big_rhsLonger() { + let lhsWords: [Word] = [1844674407370955168] + let rhsWords: [Word] = [3689348814741910327, 2459565876494606880] + let remainder = BigInt(.positive, magnitude: lhsWords) + + // plus, plus + var lhs = BigInt(.positive, magnitude: lhsWords) + var rhs = BigInt(.positive, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: 0, remainder: remainder) + + // minus, plus + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.positive, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: 0, remainder: -remainder) + + // plus, minus + lhs = BigInt(.positive, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: 0, remainder: remainder) + + // minus, minus + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: 0, remainder: -remainder) + } + + func test_big_bothMultipleWords() { + let lhsWords: [Word] = [1844674407370955168, 4304240283865562048] + let rhsWords: [Word] = [3689348814741910327, 2459565876494606880] + + let quotient = BigInt(.positive, magnitude: [1]) + let remainder = BigInt(.positive, magnitude: [16602069666338596457, 1844674407370955167]) + + // plus, plus + var lhs = BigInt(.positive, magnitude: lhsWords) + var rhs = BigInt(.positive, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: quotient, remainder: remainder) + + // minus, plus + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.positive, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: -quotient, remainder: -remainder) + + // plus, minus + lhs = BigInt(.positive, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: -quotient, remainder: remainder) + + // minus, minus + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + assertDiv(lhs, rhs, quotient: quotient, remainder: -remainder) + } +} diff --git a/Tests/BigIntTests/BinaryMulTests.swift b/Tests/BigIntTests/BinaryMulTests.swift new file mode 100644 index 00000000..6c13e764 --- /dev/null +++ b/Tests/BigIntTests/BinaryMulTests.swift @@ -0,0 +1,372 @@ +//===--- BinaryMulTests.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 +// +//===----------------------------------------------------------------------===// + +// BinaryMulTests +import XCTest +@testable import BigIntModule + +// swiftlint:disable line_length +// swiftlint:disable number_separator +// swiftformat:disable numberFormatting + +private typealias Word = BigIntPrototype.Word + +private let intZero = Int.zero +private let intMax = Int.max +private let intMaxAsWord = Word(intMax.magnitude) + +/// `2^n = value` +private typealias Pow2 = (value: Int, n: Int) + +private let powersOf2: [Pow2] = [ + (value: 2, n: 1), + (value: 4, n: 2), + (value: 16, n: 4) +] + +class BinaryMulTests: XCTestCase { + + // MARK: - Int + + func test_int_zero() { + let zero = BigInt() + let expected = BigInt() + + for int in generateInts(approximateCount: 100) { + let big = BigInt(int) + XCTAssertEqual(zero * big, expected, "\(big)") + XCTAssertEqual(big * zero, expected, "\(big)") + } + } + + func test_int_plus1() { + let one = BigInt(1) + + for int in generateInts(approximateCount: 100) { + let big = BigInt(int) + let expected = BigInt(int) + XCTAssertEqual(one * big, expected, "\(big)") + XCTAssertEqual(big * one, expected, "\(big)") + } + } + + func test_int_minus1() { + let minusOne = BigInt(-1) + + for int in generateInts(approximateCount: 100) { + // '-Smi.min' overflows + if int == .min { + continue + } + + let big = BigInt(int) + let expected = BigInt(-int) + XCTAssertEqual(minusOne * big, expected, "\(big)") + XCTAssertEqual(big * minusOne, expected, "\(big)") + } + } + + func test_int_singleWord() { + let intWidth = Int.bitWidth + let ints = generateInts(approximateCount: 15) + + for (a, b) in CartesianProduct(ints) { + let aPlus = a == .min ? nil : abs(a) + let bPlus = b == .min ? nil : abs(b) + let aMinus = a < 0 ? a : -a + let bMinus = b < 0 ? b : -b + + let cases = [ + (aPlus, bPlus), // a * b + (aPlus, bMinus), // a * (-b) + (aMinus, bPlus), // -a * b + (aMinus, bMinus) // -a * (-b) + ] + + for (aInt, bInt) in cases { + guard let aInt = aInt, let bInt = bInt else { continue } + + let aBig = BigInt(aInt) + let bBig = BigInt(bInt) + + let (high, low) = aInt.multipliedFullWidth(by: bInt) + let expected = (BigInt(high) << intWidth) | BigInt(low) + + XCTAssertEqual(aBig * bBig, expected, "\(aInt) * \(bInt)") + XCTAssertEqual(bBig * aBig, expected, "\(aInt) * \(bInt)") + } + } + } + + func test_int_multipleWords() { + let bigWords: [Word] = [3689348814741910327, 2459565876494606880] + let int = BigInt(370955168) + let intNegative = -int + + // Both positive + var big = BigInt(.positive, magnitude: bigWords) + var expected = BigInt(.positive, magnitude: [11068046445635360608, 1229782937530123449, 49460689]) + XCTAssertEqual(big * int, expected) + XCTAssertEqual(int * big, expected) + + // Self negative, other positive + big = BigInt(.negative, magnitude: bigWords) + expected = BigInt(.negative, magnitude: [11068046445635360608, 1229782937530123449, 49460689]) + XCTAssertEqual(big * int, expected) + XCTAssertEqual(int * big, expected) + + // Self positive, other negative + big = BigInt(.positive, magnitude: bigWords) + expected = BigInt(.negative, magnitude: [11068046445635360608, 1229782937530123449, 49460689]) + XCTAssertEqual(big * intNegative, expected) + XCTAssertEqual(intNegative * big, expected) + + // Both negative + big = BigInt(.negative, magnitude: bigWords) + expected = BigInt(.positive, magnitude: [11068046445635360608, 1229782937530123449, 49460689]) + XCTAssertEqual(big * intNegative, expected) + XCTAssertEqual(intNegative * big, expected) + } + + /// Multiply by a power of `2^n` (2, 4, 8) is the same as shift left by `n` + func test_int_powerOf2() { + for p in generateBigInts(approximateCount: 100) { + if p.isZero { + continue + } + + for power in powersOf2 { + guard let p = self.cleanBitsSoItCanBeMultipliedWithoutOverflow( + value: p, + power: power + ) else { continue } + + let big = p.create() + let powerBig = BigInt(power.value) + + let expectedMagnitude = p.magnitude.map { $0 << power.n } + let expected = BigInt(p.sign, magnitude: expectedMagnitude) + XCTAssertEqual(big * powerBig, expected, "\(p) * \(power.value) (shift: \(power.n)") + XCTAssertEqual(powerBig * big, expected, "\(p) * \(power.value) (shift: \(power.n)") + } + } + } + + private func cleanBitsSoItCanBeMultipliedWithoutOverflow( + value: BigIntPrototype, + power: Pow2 + ) -> BigIntPrototype? { + // 1111 >> 1 = 0111 + let mask = Word.max >> power.n + // Apply mask to every word + let magnitude = value.magnitude.map { $0 & mask } + + // Zero may behave differently than other numbers + let allWordsZero = magnitude.allSatisfy { $0 == 0 } + if allWordsZero { + return nil + } + + return BigIntPrototype(value.sign, magnitude: magnitude) + } + + // MARK: - Big + + func test_big_zero() { + let zero = BigInt() + let expected = BigInt() + + for p in generateBigInts(approximateCount: 100) { + let big = p.create() + XCTAssertEqual(zero * big, expected, "\(big)") + XCTAssertEqual(big * zero, expected, "\(big)") + } + } + + func test_big_plus1() { + let one = BigInt(1) + + for p in generateBigInts(approximateCount: 100) { + let big = p.create() + let expected = p.create() + XCTAssertEqual(one * big, expected, "\(big)") + XCTAssertEqual(big * one, expected, "\(big)") + } + } + + func test_big_minus1() { + let minusOne = BigInt(-1) + + for p in generateBigInts(approximateCount: 100) { + let big = p.create() + let expected = p.isZero ? BigInt() : p.withOppositeSign.create() + XCTAssertEqual(minusOne * big, expected, "\(big)") + XCTAssertEqual(big * minusOne, expected, "\(big)") + } + } + + /// Mul by `n^2` should shift left by `n` + func test_big_powerOf2() { + for p in generateBigInts(approximateCount: 100) { + if p.isZero { + continue + } + + for power in powersOf2 { + guard let p = self.cleanBitsSoItCanBeMultipliedWithoutOverflow(value: p, power: power) else { + continue + } + + let big = p.create() + let powerBig = BigInt(power.value) + + let expectedMagnitude = p.magnitude.map { $0 << power.n } + let expected = BigInt(p.sign, magnitude: expectedMagnitude) + XCTAssertEqual(big * powerBig, expected, "\(p) * \(power.value) (shift: \(power.n)") + XCTAssertEqual(powerBig * big, expected, "\(p) * \(power.value) (shift: \(power.n)") + } + } + } + + func test_big_singleWord_vs_multipleWords() { + let multiWords: [Word] = [3689348814741910327, 2459565876494606880] + let singleWords: [Word] = [1844674407370955168] + let expectedWords: [Word] = [ + 18077809192235360608, 16110156491039675065, 245956587649460688 + ] + + // Both positive + var multi = BigInt(.positive, magnitude: multiWords) + var single = BigInt(.positive, magnitude: singleWords) + var expected = BigInt(.positive, magnitude: expectedWords) + XCTAssertEqual(multi * single, expected) + XCTAssertEqual(single * multi, expected) + + // Self negative, other positive + multi = BigInt(.negative, magnitude: multiWords) + single = BigInt(.positive, magnitude: singleWords) + expected = BigInt(.negative, magnitude: expectedWords) + XCTAssertEqual(multi * single, expected) + XCTAssertEqual(single * multi, expected) + + // Self positive, other negative + multi = BigInt(.positive, magnitude: multiWords) + single = BigInt(.negative, magnitude: singleWords) + expected = BigInt(.negative, magnitude: expectedWords) + XCTAssertEqual(multi * single, expected) + XCTAssertEqual(single * multi, expected) + + // Both negative + multi = BigInt(.negative, magnitude: multiWords) + single = BigInt(.negative, magnitude: singleWords) + expected = BigInt(.positive, magnitude: expectedWords) + XCTAssertEqual(multi * single, expected) + XCTAssertEqual(single * multi, expected) + } + + func test_big_bothMultipleWords() { + let lhsWords: [Word] = [1844674407370955168, 4304240283865562048] + let rhsWords: [Word] = [3689348814741910327, 2459565876494606880] + let expectedWords: [Word] = [ + 18077809192235360608, 6640827866535438585, 11600952384132895787, 573898704515408272 + ] + + // Both positive + var lhs = BigInt(.positive, magnitude: lhsWords) + var rhs = BigInt(.positive, magnitude: rhsWords) + var expected = BigInt(.positive, magnitude: expectedWords) + XCTAssertEqual(lhs * rhs, expected) + XCTAssertEqual(rhs * lhs, expected) + + // Self negative, other positive + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.positive, magnitude: rhsWords) + expected = BigInt(.negative, magnitude: expectedWords) + XCTAssertEqual(lhs * rhs, expected) + XCTAssertEqual(rhs * lhs, expected) + + // Self positive, other negative + lhs = BigInt(.positive, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + expected = BigInt(.negative, magnitude: expectedWords) + XCTAssertEqual(lhs * rhs, expected) + XCTAssertEqual(rhs * lhs, expected) + + // Both negative + lhs = BigInt(.negative, magnitude: lhsWords) + rhs = BigInt(.negative, magnitude: rhsWords) + expected = BigInt(.positive, magnitude: expectedWords) + XCTAssertEqual(lhs * rhs, expected) + XCTAssertEqual(rhs * lhs, expected) + } + + // MARK: - Carry overflow + + /// This proves that naive school algorithm will never overflow on 'carry' + /// (in sign + magnitude representation). + /// + /// Basically, it will `Word.max * Word.max` to get max possible carry, + /// then it will add it to another `Word.max * Word.max` and so on... + func test_big_carryOverflow() { + typealias Word = BigIntPrototype.Word + + // Case 1 + // lowIndex 0 + // high, low = 18446744073709551615 * 18446744073709551615 = 18446744073709551614 1 + // carry, result[i] = current[i] + low + current carry = 0 1 0 = 0 1 + // next carry = carry + high = 0 + 18446744073709551614 = 18446744073709551614 + // + // lowIndex 1 + // high, low = 18446744073709551615 * 18446744073709551615 = 18446744073709551614 1 + // carry, result[i] = current[i] + low + current carry = 0 1 18446744073709551614 = 0 18446744073709551615 + // next carry = carry + high = 0 + 18446744073709551614 = 18446744073709551614 + // ^^^^^^^^^^ no overflow here! + do { + let lhs = BigInt(.positive, magnitude: [.max, .max, .max]) + let rhs = BigInt(.positive, magnitude: [.max, .max]) + XCTAssertNotNil(lhs * rhs) + } + + // Case 2 + // lowIndex 0 + // high, low = 18446744073709551615 * 18446744073709551614 = 18446744073709551613 2 + // carry, result[i] = current[i] + low + current carry = 0 2 0 = 0 2 + // next carry = carry + high = 0 + 18446744073709551613 = 18446744073709551613 + // + // lowIndex 1 + // high, low = 18446744073709551615 * 18446744073709551615 = 18446744073709551614 1 + // carry, result[i] = current[i] + low + current carry = 0 1 18446744073709551613 = 0 18446744073709551614 + // next carry = carry + high = 0 + 18446744073709551614 = 18446744073709551614 + // ^^^^^^^^^^ no overflow here! + do { + let lhs = BigInt(.positive, magnitude: [.max, .max, .max]) + let rhs = BigInt(.positive, magnitude: [.max - Word(1), .max]) + XCTAssertNotNil(lhs * rhs) + } + + // Case 3 + // lowIndex 0 + // high, low = 18446744073709551615 * 18446744073709551615 = 18446744073709551614 1 + // carry, result[i] = current[i] + low + current carry = 0 1 0 = 0 1 + // next carry = carry + high = 0 + 18446744073709551614 = 18446744073709551614 + // + // lowIndex 1 + // high, low = 18446744073709551615 * 18446744073709551614 = 18446744073709551613 2 + // carry, result[i] = current[i] + low + current carry = 0 2 18446744073709551614 = 1 0 + // next carry = carry + high = 1 + 18446744073709551613 = 18446744073709551614 + // ^^^^^^^^^^ no overflow here! + do { + let lhs = BigInt(.positive, magnitude: [.max, .max, .max]) + let rhs = BigInt(.positive, magnitude: [.max, .max, Word(1)]) + XCTAssertNotNil(lhs * rhs) + } + } +} diff --git a/Tests/BigIntTests/PowerTests.swift b/Tests/BigIntTests/PowerTests.swift new file mode 100644 index 00000000..f5c7952f --- /dev/null +++ b/Tests/BigIntTests/PowerTests.swift @@ -0,0 +1,154 @@ +//===--- PowerTests.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 + +class PowerTests: XCTestCase { + + // MARK: - Trivial base + + /// 0 ^ n = 0 (or sometimes 1) + func test_base_zero() { + let zero = BigInt(0) + let one = BigInt(1) + + for int in generateInts(approximateCount: 100) { + let exponentInt = int.magnitude + let exponent = BigInt(exponentInt) + let result = zero.power(exponent: exponent) + + // 0 ^ 0 = 1, otherwise 0 + let expected = exponentInt == 0 ? one : zero + XCTAssertEqual(result, expected, "0 ^ \(exponentInt)") + } + } + + /// 1 ^ n = 1 + func test_base_one() { + let one = BigInt(1) + + for int in generateInts(approximateCount: 100) { + let exponentInt = int.magnitude + let exponent = BigInt(exponentInt) + let result = one.power(exponent: exponent) + + let expected = one + XCTAssertEqual(result, expected, "1 ^ \(exponentInt)") + } + } + + /// (-1) ^ n = (-1) or 1 + func test_base_minusOne() { + let plusOne = BigInt(1) + let minusOne = BigInt(-1) + + for int in generateInts(approximateCount: 100) { + let exponentInt = int.magnitude + let exponent = BigInt(exponentInt) + let result = minusOne.power(exponent: exponent) + + let expected = exponentInt.isMultiple(of: 2) ? plusOne : minusOne + XCTAssertEqual(result, expected, "(-1) ^ \(exponentInt)") + } + } + + // MARK: - Trivial exponent + + /// n ^ 0 = 1 + func test_exponent_zero() { + let zero = BigInt(0) + let one = BigInt(1) + + for int in generateInts(approximateCount: 100) { + let base = BigInt(int) + let result = base.power(exponent: zero) + + let expected = one + XCTAssertEqual(result, expected, "\(int) ^ 1") + } + } + + /// n ^ 1 = n + func test_exponent_one() { + let one = BigInt(1) + + for int in generateInts(approximateCount: 100) { + let base = BigInt(int) + let result = base.power(exponent: one) + + let expected = base + XCTAssertEqual(result, expected, "\(int) ^ 1") + } + } + + func test_exponent_two() { + let two = BigInt(2) + + for p in generateBigInts(approximateCount: 100) { + let base = p.create() + let result = base.power(exponent: two) + + let expected = base * base + XCTAssertEqual(result, expected, "\(base) ^ 2") + } + } + + func test_exponent_three() { + let three = BigInt(3) + + for p in generateBigInts(approximateCount: 100) { + let base = p.create() + let result = base.power(exponent: three) + + let expected = base * base * base + XCTAssertEqual(result, expected, "\(base) ^ 3") + } + } + + // MARK: - Int + + func test_againstFoundationPow() { + // THIS IS NOT A PERFECT TEST! + // It is 'good enough' to be usable, but don't think about it too much! + let mantissaCount = Double.significandBitCount // well… technically '+1' + let maxExactlyRepresentable = UInt(pow(Double(2), Double(mantissaCount))) + + var values = generateInts(approximateCount: 20) + for i in -10...10 { + values.append(i) + } + + for (baseInt, expIntSigned) in CartesianProduct(values) { + let expInt = expIntSigned.magnitude + + guard let baseDouble = Double(exactly: baseInt), + let expDouble = Double(exactly: expInt) else { + continue + } + + let expectedDouble = pow(baseDouble, expDouble) + + guard let expectedInt = Int(exactly: expectedDouble), + expectedInt.magnitude < maxExactlyRepresentable else { + continue + } + + // Some tests will actually get here, not a lot, but some + let base = BigInt(baseInt) + let exp = BigInt(expInt) + let result = base.power(exponent: exp) + + let expected = BigInt(expectedInt) + XCTAssertEqual(result, expected, "\(baseInt) ^ \(expInt)") + } + } +} From 2a71705f7c02a1375f275cad4dab97591d42b2ee Mon Sep 17 00:00:00 2001 From: LiarPrincess <4982138+LiarPrincess@users.noreply.github.com> Date: Fri, 3 Feb 2023 13:29:53 +0100 Subject: [PATCH 3/4] Test `lhs == q * rhs + r` even if we do not know the expected `qr` values --- Tests/BigIntTests/BinaryDivRemTests.swift | 46 +++++++++++------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/Tests/BigIntTests/BinaryDivRemTests.swift b/Tests/BigIntTests/BinaryDivRemTests.swift index e96a8dfd..6e5522d7 100644 --- a/Tests/BigIntTests/BinaryDivRemTests.swift +++ b/Tests/BigIntTests/BinaryDivRemTests.swift @@ -33,44 +33,43 @@ private let powersOf2: [Pow2] = [ private func assertDiv(_ lhs: BigInt, _ rhs: BigInt, - quotient _quotient: Q, - remainder _remainder: R, + quotient _quotient: Q?, + remainder _remainder: R?, file: StaticString = #file, line: UInt = #line) { assert(rhs != 0, "[BinaryDivTests] Oooo… div by 0? 🐰") - let quotient = BigInt(_quotient) - let remainder = BigInt(_remainder) - - // We could add the following check, but the current (2023.01) implementation - // would not survive it: - // guard (quotient * rhs + remainder) == lhs else { - // XCTFail("=== [\(lhs)/\(rhs)] INVALID TEST? ===", file: file, line: line) - // return - // } let q = lhs / rhs let r = lhs % rhs - XCTAssertEqual(q, quotient, "[\(lhs)/\(rhs)] Quotient", file: file, line: line) - XCTAssertEqual(r, remainder, "[\(lhs)/\(rhs)] Remainder", file: file, line: line) + + if let quotient = _quotient { + let expected = BigInt(quotient) + XCTAssertEqual(q, expected, "[\(lhs)/\(rhs)] Quotient", file: file, line: line) + } + + if let remainder = _remainder { + let expected = BigInt(remainder) + XCTAssertEqual(r, expected, "[\(lhs)/\(rhs)] Remainder", file: file, line: line) + } // lhs == q * rhs + r let restored = q * rhs + r XCTAssertEqual(lhs, restored, "[\(lhs)/\(rhs)] lhs == q * rhs + r", file: file, line: line) // There are multiple solutions for 'lhs = q * rhs + r': - // | -5//4 | -5%4 | - // Python 3.7.4 | -2 | 3 | - // Swift 5.3.2 | -1 | -1 | <- we want this (round toward 0) + // | -5/4 | -5%4 | + // Python 3.7.4 | -2 | 3 | + // Swift 5.3.2 | -1 | -1 | <- we want this (round toward 0) // // Check: |q * rhs| <= |lhs| // In Python: |-2*4| <= |-5| -> 8 <= 5 -> FALSE // In Swift: |-1*4| <= |-5| -> 4 <= 5 -> TRUE let qRhs = q * rhs - XCTAssertLessThanOrEqual(qRhs.magnitude, lhs.magnitude, "[\(lhs)/\(rhs)] |q * rhs| <= |lhs|", file: file, line: line) + XCTAssertLessThanOrEqual(qRhs.magnitude, lhs.magnitude, "[\(lhs)/\(rhs)] Round toward 0", file: file, line: line) let (qq, rr) = lhs.quotientAndRemainder(dividingBy: rhs) - XCTAssertEqual(qq, quotient, "[\(lhs)/\(rhs)] quotientAndRemainder-Quotient", file: file, line: line) - XCTAssertEqual(rr, remainder, "[\(lhs)/\(rhs)] quotientAndRemainder-Remainder", file: file, line: line) + XCTAssertEqual(qq, q, "[\(lhs)/\(rhs)] quotientAndRemainder-Quotient", file: file, line: line) + XCTAssertEqual(rr, r, "[\(lhs)/\(rhs)] quotientAndRemainder-Remainder", file: file, line: line) } class BinaryDivRemTests: XCTestCase { @@ -448,11 +447,10 @@ class BinaryDivRemTests: XCTestCase { continue } - // We don't know the exact values, just check that it did not crash - // (and trust me, overflow is strong with this one). - _ = bigger / smol - _ = bigger % smol - _ = bigger.quotientAndRemainder(dividingBy: smol) + // We don't know the exact values, but we still can do some tests. + let quotient: Int? = nil + let remainder: Int? = nil + assertDiv(bigger, smol, quotient: quotient, remainder: remainder) } } From 7611234a92738347aa9e19a237bfe0b09bbf7d11 Mon Sep 17 00:00:00 2001 From: LiarPrincess <4982138+LiarPrincess@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:55:52 +0100 Subject: [PATCH 4/4] Swiftlint --- Tests/BigIntTests/BinaryDivRemTests.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Tests/BigIntTests/BinaryDivRemTests.swift b/Tests/BigIntTests/BinaryDivRemTests.swift index 6e5522d7..d68c0cf6 100644 --- a/Tests/BigIntTests/BinaryDivRemTests.swift +++ b/Tests/BigIntTests/BinaryDivRemTests.swift @@ -65,11 +65,23 @@ private func assertDiv(_ lhs: BigInt, // In Python: |-2*4| <= |-5| -> 8 <= 5 -> FALSE // In Swift: |-1*4| <= |-5| -> 4 <= 5 -> TRUE let qRhs = q * rhs - XCTAssertLessThanOrEqual(qRhs.magnitude, lhs.magnitude, "[\(lhs)/\(rhs)] Round toward 0", file: file, line: line) + XCTAssertLessThanOrEqual(qRhs.magnitude, + lhs.magnitude, + "[\(lhs)/\(rhs)] Round toward 0", + file: file, + line: line) let (qq, rr) = lhs.quotientAndRemainder(dividingBy: rhs) - XCTAssertEqual(qq, q, "[\(lhs)/\(rhs)] quotientAndRemainder-Quotient", file: file, line: line) - XCTAssertEqual(rr, r, "[\(lhs)/\(rhs)] quotientAndRemainder-Remainder", file: file, line: line) + XCTAssertEqual(qq, + q, + "[\(lhs)/\(rhs)] quotientAndRemainder-Quotient", + file: file, + line: line) + XCTAssertEqual(rr, + r, + "[\(lhs)/\(rhs)] quotientAndRemainder-Remainder", + file: file, + line: line) } class BinaryDivRemTests: XCTestCase {