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() + } + } +} diff --git a/Tests/BigIntTests/InitFromIntTests.swift b/Tests/BigIntTests/InitFromIntTests.swift new file mode 100644 index 00000000..ebc817ff --- /dev/null +++ b/Tests/BigIntTests/InitFromIntTests.swift @@ -0,0 +1,438 @@ +//===--- InitFromIntTests.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 + +private func generateTestCases(type: T.Type) -> [T] { + var result: [T] = [0, 1, 42, T.max, T.max - 1, T.min, T.min + 1] + result.append(contentsOf: PositivePowersOf2(type: T.self).map { $0.value }) + + if T.isSigned { + result.append(-1) + result.append(-42) + result.append(contentsOf: NegativePowersOf2(type: T.self).map { $0.value }) + } + + return result +} + +class InitFromIntTests: XCTestCase { + + // MARK: - Raw - BigInt(🐰) + + func test_raw_signed() { + self.raw_inRange(type: Int8.self) + self.raw_inRange(type: Int16.self) + self.raw_inRange(type: Int32.self) + self.raw_inRange(type: Int64.self) + self.raw_inRange(type: Int.self) + } + + func test_raw_unsigned() { + self.raw_inRange(type: UInt8.self) + self.raw_inRange(type: UInt16.self) + self.raw_inRange(type: UInt32.self) + self.raw_inRange(type: UInt64.self) + self.raw_inRange(type: UInt.self) + } + + private func raw_inRange( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + for int in generateTestCases(type: T.self) { + let big = BigInt(int) + + // String representation should be equal - trivial test for value + let intString = String(int, radix: 10, uppercase: false) + let bigString = String(big, radix: 10, uppercase: false) + XCTAssertEqual(bigString, intString, "BigInt(\(int))", file: file, line: line) + + // T -> BigInt -> T + let revert = T(big) + XCTAssertEqual(int, revert, "Revert \(int)", file: file, line: line) + } + } + + func test_raw_big() { + // Init from a very big number, may overflow if we assumed that 'UInt64' + // is the best we can do. (bless your pure soul 🐰) + for p in generateBigInts(approximateCount: 50, maxWordCount: 20) { + let big = p.create() + let result = BigInt(big) + XCTAssertEqual(big, result, "\(big)") + } + } + + // MARK: - Exactly + + func test_exactly_signed() { + self.exactly_inRange(type: Int8.self) + self.exactly_inRange(type: Int16.self) + self.exactly_inRange(type: Int32.self) + self.exactly_inRange(type: Int64.self) + self.exactly_inRange(type: Int.self) + } + + func test_exactly_unsigned() { + self.exactly_inRange(type: UInt8.self) + self.exactly_inRange(type: UInt16.self) + self.exactly_inRange(type: UInt32.self) + self.exactly_inRange(type: UInt64.self) + self.exactly_inRange(type: UInt.self) + } + + private func exactly_inRange( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + for int in generateTestCases(type: T.self) { + guard let big = BigInt(exactly: int) else { + XCTFail("BigInt(exactly: \(int))", file: file, line: line) + continue + } + + // String representation should be equal - trivial test for value + let intString = String(int, radix: 10, uppercase: false) + let bigString = String(big, radix: 10, uppercase: false) + XCTAssertEqual(bigString, intString, "BigInt(exactly: \(int))", file: file, line: line) + + // T -> BigInt -> T + let revert = T(exactly: big) + XCTAssertEqual(int, revert, "Revert \(int)", file: file, line: line) + } + } + + func test_exactly_signed_moreThanMax() { + self.exactly_moreThanMax(type: Int8.self) + self.exactly_moreThanMax(type: Int16.self) + self.exactly_moreThanMax(type: Int32.self) + self.exactly_moreThanMax(type: Int64.self) + self.exactly_moreThanMax(type: Int.self) + } + + func test_exactly_unsigned_moreThanMax() { + self.exactly_moreThanMax(type: UInt8.self) + self.exactly_moreThanMax(type: UInt16.self) + self.exactly_moreThanMax(type: UInt32.self) + self.exactly_moreThanMax(type: UInt64.self) + self.exactly_moreThanMax(type: UInt.self) + } + + private func exactly_moreThanMax( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + let max = BigInt(type.max) + let cases = [1, 3, 7, 23, max, 2 * max - 3] + + for n in cases { + let moreThanMax = max + n + let result = T(exactly: moreThanMax) + XCTAssertNil(result, "max + \(n)", file: file, line: line) + } + } + + func test_exactly_signed_lessThanMin() { + self.exactly_lessThanMin(type: Int8.self) + self.exactly_lessThanMin(type: Int16.self) + self.exactly_lessThanMin(type: Int32.self) + self.exactly_lessThanMin(type: Int64.self) + self.exactly_lessThanMin(type: Int.self) + } + + func test_exactly_unsigned_lessThanMin() { + self.exactly_lessThanMin(type: UInt8.self) + self.exactly_lessThanMin(type: UInt16.self) + self.exactly_lessThanMin(type: UInt32.self) + self.exactly_lessThanMin(type: UInt64.self) + self.exactly_lessThanMin(type: UInt.self) + } + + private func exactly_lessThanMin( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + let min = BigInt(type.min) + let max = BigInt(type.max) + let cases = [1, 3, 7, 23, max, 2 * max - 3] + + for n in cases { + let lessThanMin = min - n + let result = T(exactly: lessThanMin) + XCTAssertNil(result, "min - \(n)", file: file, line: line) + } + } + + func test_exactly_big() { + // Init from a very big number, may overflow if we assumed that 'UInt64' + // is the best we can do. (bless your pure soul 🐰) + for p in generateBigInts(approximateCount: 50, maxWordCount: 20) { + let big = p.create() + let result = BigInt(exactly: big) + XCTAssertEqual(big, result, "\(big)") + } + } + + // MARK: - Clamping + + func test_clamping_signed() { + self.clamping_inRange(type: Int8.self) + self.clamping_inRange(type: Int16.self) + self.clamping_inRange(type: Int32.self) + self.clamping_inRange(type: Int64.self) + self.clamping_inRange(type: Int.self) + } + + func test_clamping_unsigned() { + self.clamping_inRange(type: UInt8.self) + self.clamping_inRange(type: UInt16.self) + self.clamping_inRange(type: UInt32.self) + self.clamping_inRange(type: UInt64.self) + self.clamping_inRange(type: UInt.self) + } + + private func clamping_inRange( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + for int in generateTestCases(type: T.self) { + let big = BigInt(clamping: int) + + // String representation should be equal - trivial test for value + let intString = String(int, radix: 10, uppercase: false) + let bigString = String(big, radix: 10, uppercase: false) + XCTAssertEqual(bigString, intString, "BigInt(clamping: \(int))", file: file, line: line) + + // T -> BigInt -> T + let revert = T(clamping: big) + XCTAssertEqual(int, revert, "Revert \(int)", file: file, line: line) + } + } + + func test_clamping_signed_moreThanMax() { + self.clamping_moreThanMax(type: Int8.self) + self.clamping_moreThanMax(type: Int16.self) + self.clamping_moreThanMax(type: Int32.self) + self.clamping_moreThanMax(type: Int64.self) + self.clamping_moreThanMax(type: Int.self) + } + + func test_clamping_unsigned_moreThanMax() { + self.clamping_moreThanMax(type: UInt8.self) + self.clamping_moreThanMax(type: UInt16.self) + self.clamping_moreThanMax(type: UInt32.self) + self.clamping_moreThanMax(type: UInt64.self) + self.clamping_moreThanMax(type: UInt.self) + } + + private func clamping_moreThanMax( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + let maxT = type.max + let max = BigInt(maxT) + let cases = [1, 3, 7, 23, max, 2 * max - 3] + + for n in cases { + let moreThanMax = max + n + let result = T(clamping: moreThanMax) + XCTAssertEqual(result, maxT, "max + \(n)", file: file, line: line) + } + } + + func test_clamping_signed_lessThanMin() { + self.clamping_lessThanMin(type: Int8.self) + self.clamping_lessThanMin(type: Int16.self) + self.clamping_lessThanMin(type: Int32.self) + self.clamping_lessThanMin(type: Int64.self) + self.clamping_lessThanMin(type: Int.self) + } + + func test_clamping_unsigned_lessThanMin() { + self.clamping_lessThanMin(type: UInt8.self) + self.clamping_lessThanMin(type: UInt16.self) + self.clamping_lessThanMin(type: UInt32.self) + self.clamping_lessThanMin(type: UInt64.self) + self.clamping_lessThanMin(type: UInt.self) + } + + private func clamping_lessThanMin( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + let minT = type.min + let min = BigInt(minT) + let max = BigInt(type.max) + let cases = [1, 3, 7, 23, max, 2 * max - 3] + + for n in cases { + let lessThanMin = min - n + let result = T(clamping: lessThanMin) + XCTAssertEqual(result, minT, "min - \(n)", file: file, line: line) + } + } + + func test_clamping_big() { + // Init from a very big number, may overflow if we assumed that 'UInt64' + // is the best we can do. (bless your pure soul 🐰) + for p in generateBigInts(approximateCount: 50, maxWordCount: 20) { + let big = p.create() + let result = BigInt(clamping: big) + XCTAssertEqual(big, result, "\(big)") + } + } + + // MARK: - Truncating if needed + + func test_truncatingIfNeeded_signed() { + self.truncatingIfNeeded_inRange(type: Int8.self) + self.truncatingIfNeeded_inRange(type: Int16.self) + self.truncatingIfNeeded_inRange(type: Int32.self) + self.truncatingIfNeeded_inRange(type: Int64.self) + self.truncatingIfNeeded_inRange(type: Int.self) + } + + func test_truncatingIfNeeded_unsigned() { + self.truncatingIfNeeded_inRange(type: UInt8.self) + self.truncatingIfNeeded_inRange(type: UInt16.self) + self.truncatingIfNeeded_inRange(type: UInt32.self) + self.truncatingIfNeeded_inRange(type: UInt64.self) + self.truncatingIfNeeded_inRange(type: UInt.self) + } + + private func truncatingIfNeeded_inRange( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + for int in generateTestCases(type: T.self) { + let big = BigInt(truncatingIfNeeded: int) + + // String representation should be equal - trivial test for value + let intString = String(int, radix: 10, uppercase: false) + let bigString = String(big, radix: 10, uppercase: false) + XCTAssertEqual(bigString, intString, "BigInt(truncatingIfNeeded: \(int))", file: file, line: line) + + // T -> BigInt -> T + let revert = T(truncatingIfNeeded: big) + XCTAssertEqual(int, revert, "Revert \(int)", file: file, line: line) + } + } + + func test_truncatingIfNeeded_signed_moreThanMax() { + self.truncatingIfNeeded_moreThanMax(type: Int8.self) + self.truncatingIfNeeded_moreThanMax(type: Int16.self) + self.truncatingIfNeeded_moreThanMax(type: Int32.self) + self.truncatingIfNeeded_moreThanMax(type: Int64.self) + self.truncatingIfNeeded_moreThanMax(type: Int.self) + } + + func test_truncatingIfNeeded_unsigned_moreThanMax() { + self.truncatingIfNeeded_moreThanMax(type: UInt8.self) + self.truncatingIfNeeded_moreThanMax(type: UInt16.self) + self.truncatingIfNeeded_moreThanMax(type: UInt32.self) + self.truncatingIfNeeded_moreThanMax(type: UInt64.self) + self.truncatingIfNeeded_moreThanMax(type: UInt.self) + } + + private func truncatingIfNeeded_moreThanMax( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + do { + // signed: 0111 + 1 = 1000 -> min + // unsigned: 1111 + 1 = 1 0000 -> 0 + let value = BigInt(type.max) + 1 + let truncated = T(truncatingIfNeeded: value) + let expected = T.isSigned ? type.min : type.zero + XCTAssertEqual(truncated, expected, "max + 1", file: file, line: line) + } + + let max = BigInt(type.max) + let cases = [1, 3, 7, 23, max - 3, max] + let discarded = BigInt(1) << T.bitWidth + + for n in cases { + let moreThanMax = discarded + n + let truncated = T(truncatingIfNeeded: moreThanMax) + let truncatedBig = BigInt(truncated) + XCTAssertEqual(truncatedBig, n, "1 << \(T.bitWidth) + \(n)", file: file, line: line) + } + } + + func test_truncatingIfNeeded_signed_lessThanMin() { + self.truncatingIfNeeded_lessThanMin(type: Int8.self) + self.truncatingIfNeeded_lessThanMin(type: Int16.self) + self.truncatingIfNeeded_lessThanMin(type: Int32.self) + self.truncatingIfNeeded_lessThanMin(type: Int64.self) + self.truncatingIfNeeded_lessThanMin(type: Int.self) + } + + func test_truncatingIfNeeded_unsigned_lessThanMin() { + self.truncatingIfNeeded_lessThanMin(type: UInt8.self) + self.truncatingIfNeeded_lessThanMin(type: UInt16.self) + self.truncatingIfNeeded_lessThanMin(type: UInt32.self) + self.truncatingIfNeeded_lessThanMin(type: UInt64.self) + self.truncatingIfNeeded_lessThanMin(type: UInt.self) + } + + private func truncatingIfNeeded_lessThanMin( + type: T.Type, + file: StaticString = #file, + line: UInt = #line + ) { + let min = BigInt(type.min) + + do { + // signed: 1000 - 1 = 0111 = max + // unsigned: 0000 - 1 = 1111 = max + let value = min - 1 + let result = T(truncatingIfNeeded: value) + XCTAssertEqual(result, type.max, "min - 1", file: file, line: line) + } + + // signed: -12 = (1111) 0100 = -12 + // unsigned: -12 = (1111) 0100 = ? + let max = BigInt(type.max) + let cases = [1, 3, 7, 23, max - 3, max] + let discarded = BigInt(1) << T.bitWidth + + for n in cases { + let lessThanMin = -(discarded + n) + assert(lessThanMin < min) + + let truncated = T(truncatingIfNeeded: lessThanMin) + let complement = ~truncated + 1 // no overflow possible + let truncatedBig = BigInt(complement) + XCTAssertEqual(truncatedBig, n, "1 << \(T.bitWidth) + \(n)", file: file, line: line) + } + } + + func test_truncatingIfNeeded_big() { + // Init from a very big number, may overflow if in 'init' we assumed that + // 'UInt64' is the best we can do. (bless your pure soul 🐰) + for p in generateBigInts(approximateCount: 50, maxWordCount: 20) { + let big = p.create() + let result = BigInt(truncatingIfNeeded: big) + XCTAssertEqual(big, result, "\(big)") + } + } +}