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 2e63790960533d928c1376b18d676bf014eca326 Mon Sep 17 00:00:00 2001 From: LiarPrincess <4982138+LiarPrincess@users.noreply.github.com> Date: Fri, 3 Feb 2023 14:00:45 +0100 Subject: [PATCH 2/4] Test `init` from binary floating point 754 --- .../BinaryFloatingPoint+Extensions.swift | 37 + .../InitFromBinaryFloatingPoint754.swift | 724 ++++++++++++++++++ 2 files changed, 761 insertions(+) create mode 100755 Tests/BigIntTests/Helpers/BinaryFloatingPoint+Extensions.swift create mode 100755 Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift diff --git a/Tests/BigIntTests/Helpers/BinaryFloatingPoint+Extensions.swift b/Tests/BigIntTests/Helpers/BinaryFloatingPoint+Extensions.swift new file mode 100755 index 00000000..c2ea32c8 --- /dev/null +++ b/Tests/BigIntTests/Helpers/BinaryFloatingPoint+Extensions.swift @@ -0,0 +1,37 @@ +//===--- BinaryFloatingPoint+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 +// +//===----------------------------------------------------------------------===// + +extension BinaryFloatingPoint { + + static var bias: Int { (1 << (exponentBitCount - 1)) - 1 } + + static var exponentMask: RawExponent { (1 << exponentBitCount) - 1 } + static var exponentAll1: RawExponent { (~0) & exponentMask } + + // Note that `Float80.significandBitCount` is `63`, even though `64 bits` + // are actually used (`Float80` explicitly stores the leading integral bit). + static var significandMask: RawSignificand { (1 << Self.significandBitCount) - 1 } + static var significandAll1: RawSignificand { (~0) & significandMask } + + var nextAwayFromZero: Self { + switch self.sign { + case .plus: return self.nextUp + case .minus: return self.nextDown + } + } + + var nextTowardZero: Self { + switch self.sign { + case .plus: return self.nextDown + case .minus: return self.nextUp + } + } +} diff --git a/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift b/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift new file mode 100755 index 00000000..096d34cb --- /dev/null +++ b/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift @@ -0,0 +1,724 @@ +//===--- InitFromBinaryFloatingPoint754.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 Foundation +@testable import BigIntModule + +// MARK: - Assertions + +/// BigInt(🐰) +private func assertBigInt(_ d: T, + _ expected: BigInt, + _ message: String? = nil, + file: StaticString, + line: UInt) { + let result = BigInt(d) + let message = message.map { ": \($0)" } ?? "" + XCTAssertEqual(result, + expected, + "BigInt(\(d))\(message)", + file: file, + line: line) +} + +/// BigInt(exactly: 🐰) +private func assertExactlyBigInt(_ d: T, + _ expected: BigInt, + _ message: String? = nil, + file: StaticString, + line: UInt) { + let result = BigInt(exactly: d) + let message = message.map { ": \($0)" } ?? "" + XCTAssertEqual(result, + expected, + "BigInt(exactly: \(d))\(message)", + file: file, + line: line) +} + +/// BigInt(exactly: 🐰) == nil +private func assertExactlyBigIntIsNil(_ d: T, + _ message: String? = nil, + file: StaticString, + line: UInt) { + let result = BigInt(exactly: d) + let message = message.map { ": \($0)" } ?? "" + XCTAssertNil(result, + "BigInt(exactly: \(d))\(message)", + file: file, + line: line) +} + +// MARK: - Data + +private let signs: [FloatingPointSign] = [.plus, .minus] + +// MARK: - Tests + +/// Based on 'IEEE-754 2008 Floating point specification'. +/// Binary part; no decimals; no TensorFloat-32; no unums/posits. +/// 2019 is also available, but I'm poor… 🤷. +final class InitFromBinaryFloatingPoint754: XCTestCase { + + // MARK: - Zero, infinity, Batman, subnormal + + // F | Exponent | Result + // --+----------+------------------ + // 0 | 0 | Signed 0 + // _ | 0 | Subnormal numbers + // 0 | all 1 | Infinity + // _ | all 1 | NaNs + // + // * Fractional part of the significand + + func test_zero() { + func test(type: T.Type, + file: StaticString = #file, + line: UInt = #line) { + let zeros = [+T.zero, -T.zero] + let expected = BigInt() + + for zero in zeros { + assertBigInt(zero, expected, file: file, line: line) + assertExactlyBigInt(zero, expected, file: file, line: line) + + let up = zero.nextUp + assert(up.isSubnormal) + assertBigInt(up, expected, file: file, line: line) + assertExactlyBigIntIsNil(up, file: file, line: line) + + let down = zero.nextDown + assert(down.isSubnormal) + assertBigInt(down, expected, file: file, line: line) + assertExactlyBigIntIsNil(down, file: file, line: line) + } + + let half = T(1) / T(2) + assertBigInt(+half, expected, file: file, line: line) + assertBigInt(-half, expected, file: file, line: line) + assertExactlyBigIntIsNil(+half, file: file, line: line) + assertExactlyBigIntIsNil(-half, file: file, line: line) + } + + test(type: Float.self) + test(type: Double.self) +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + test(type: Float80.self) +#endif + } + + func test_infinity() { + func test(type: T.Type, + file: StaticString = #file, + line: UInt = #line) { + // BigInt(plus) will crash + let plus = +T.infinity + assertExactlyBigIntIsNil(plus, file: file, line: line) + + // BigInt(minus) will crash + let minus = -T.infinity + assertExactlyBigIntIsNil(minus, file: file, line: line) + } + + test(type: Float.self) + test(type: Double.self) +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + test(type: Float80.self) +#endif + } + + // https://www.youtube.com/watch?v=EtoMN_xi-AM + func test_nanana_Batman() { + // There is a catch in this: + // The IEEE-754 2008 -> '6.2.1 NaN encodings in binary formats' specifies the + // proper encoding, but AFAIK it is not enforced and may be platform dependent + // (because in IEEE-754 1985 it was 'left to the implementor’s discretion'). + // This is why we will use Swift.Float.nan and not construct it by hand. + // + // DO NOT use high 'significand' bits for payload! This is where encoding + // should be if we are fully IEEE-754 2008 compliant. + + func addPayload(_ nan: T, payload: T.RawSignificand) -> T { + assert(nan.isNaN) + return T( + sign: nan.sign, + exponentBitPattern: nan.exponentBitPattern, + significandBitPattern: nan.significandBitPattern | payload + ) + } + + func test(type: T.Type, + file: StaticString = #file, + line: UInt = #line) { + // BigInt(qNaN) will crash + let qNaN = T.nan + let qNaNPayload = addPayload(qNaN, payload: 101) + assertExactlyBigIntIsNil(qNaN, file: file, line: line) + assertExactlyBigIntIsNil(qNaNPayload, file: file, line: line) + + // BigInt(sNaN) will crash + let sNaN = T.signalingNaN + let sNaNPayload = addPayload(sNaN, payload: 101) + assertExactlyBigIntIsNil(sNaN, file: file, line: line) + assertExactlyBigIntIsNil(sNaNPayload, file: file, line: line) + } + + test(type: Float.self) + test(type: Double.self) +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + test(type: Float80.self) +#endif + } + + func test_subnormal() { + func test(type: T.Type, + file: StaticString = #file, + line: UInt = #line) { + let zero = BigInt() + // Remember that significand = 0 is reserved for '0'! + let significands = [ + 1, + T.RawSignificand(1) << (T.significandBitCount / 2), + T.significandAll1 + ] + + for (sign, significand) in CartesianProduct(signs, significands) { + let d = T(sign: sign, exponentBitPattern: 0, significandBitPattern: significand) + assert(d.isSubnormal) + + // Check if Swift works as expected + assert(Int(d) == 0) + assert(Int(exactly: d) == nil) + + let message = "\(sign), significand: \(significand)" + assertBigInt(d, zero, message, file: file, line: line) + assertExactlyBigIntIsNil(d, message, file: file, line: line) + } + } + + test(type: Float.self) + test(type: Double.self) +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + test(type: Float80.self) +#endif + } + + // MARK: - Normal + + struct PredefinedTestCase { + let name: String + let value: T + let expected: BigInt + let exactly: BigInt? + } + + func test_predefinedCases() { + typealias TC = PredefinedTestCase + + func test(type: T.Type, + file: StaticString = #file, + line: UInt = #line) { + let gfmUnpack = Unpack(T.greatestFiniteMagnitude) + assert(gfmUnpack.isInteger) + let gfm = gfmUnpack.whole + + let testCases = [ + TC(name: "+π", value: +T.pi, expected: +3, exactly: nil), + TC(name: "-π", value: -T.pi, expected: -3, exactly: nil), + TC(name: "+ulpOfOne", value: +T.ulpOfOne, expected: 0, exactly: nil), + TC(name: "-ulpOfOne", value: -T.ulpOfOne, expected: 0, exactly: nil), + TC(name: "+leastNonzeroMagnitude", value: +T.leastNonzeroMagnitude, expected: 0, exactly: nil), + TC(name: "-leastNonzeroMagnitude", value: -T.leastNonzeroMagnitude, expected: 0, exactly: nil), + TC(name: "+leastNormalMagnitude", value: +T.leastNormalMagnitude, expected: 0, exactly: nil), + TC(name: "-leastNormalMagnitude", value: -T.leastNormalMagnitude, expected: 0, exactly: nil), + TC(name: "+greatestFiniteMagnitude", value: +T.greatestFiniteMagnitude, expected: +gfm, exactly: +gfm), + TC(name: "-greatestFiniteMagnitude", value: -T.greatestFiniteMagnitude, expected: -gfm, exactly: -gfm), + ] + + for testCase in testCases { + let d = testCase.value + let message = testCase.name + + assertBigInt(d, testCase.expected, message, file: file, line: line) + + if let exactly = testCase.exactly { + assertExactlyBigInt(d, exactly, message, file: file, line: line) + } else { + assertExactlyBigIntIsNil(d, message, file: file, line: line) + } + } + } + + test(type: Float.self) + test(type: Double.self) +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + test(type: Float80.self) +#endif + } + + // MARK: - Normal - unpack + + /// Destruct normal (non 0) floating point number into `BigInt`. + /// + /// `7.0/3.0 = 2⅓` will be encoded as: + /// - whole: `2` + /// - fractionNumerator: `1501199875790166` + /// - fractionDenominator: `4503599627370496` + /// + /// Note that: `1501199875790166/4503599627370496 = 0.333333333333 ≈ ⅓`. + /// + /// `T` is a phantom type that denotes the precision. + struct Unpack { + + let whole: BigInt + let fractionNumerator: BigInt + let fractionDenominator: BigInt + + var isInteger: Bool { + return self.fractionNumerator == 0 + } + + init(_ d: T) { + assert(d.isFinite && !d.isSubnormal) + + // Significand is encoded as 1.significandBitPattern: + // - '1' is implicit and does not exist in 'significandBitPattern' + // (except for Float80, but whatever…) + // - 'significandBitPattern' represents a fraction of 'significandDenominator' + let significandDenominator = BigInt(1) << T.significandBitCount + let significandRestored1 = significandDenominator + let significandFraction = BigInt(d.significandBitPattern) + let significand = significandRestored1 | significandFraction + + // Proper equation is: + // value = (-1 ** sign) * 1.significandBitPattern * (2 ** exponent) = + // = (-1 ** sign) * significand/significandDenominator * (2 ** exponent) = + // = (-1 ** sign) * (significand << exponent) / significandDenominator + // + // 'numerator' calculation is prone to overflow. + // If only we had an 'Int' representation that does not overflow… + let sign: BigInt = d.sign == .plus ? 1 : -1 + let numerator = sign * (significand << d.exponent) + let (q, r) = numerator.quotientAndRemainder(dividingBy: significandDenominator) + + self.whole = q + self.fractionNumerator = r + self.fractionDenominator = significandDenominator + } + } + + /// Produce elements according to `element = previousElement * K`. + /// + /// `K` is chosen, so that the whole `range` is filled (which means that `K>1`). + /// Obviously, this means that the spacing between elements will gradually increase. + struct GeometricSample: Sequence { + + let lowerBound: T + let upperBound: T + let count: Int + private let K: T + + init(lowerBound: T, upperBound: T, count: Int) { + assert(upperBound > lowerBound) + assert(!lowerBound.isZero, "Geometric series starting with 0?") + self.lowerBound = lowerBound + self.upperBound = upperBound + self.count = count + + // lowerBound * K*K*…*K ≈ upperBound -> + // lowerBound * (K**count) ≈ upperBound -> + // K**count ≈ upperBound/lowerBound -> + // K ≈ count√(upperBound/lowerBound) + let span = upperBound / lowerBound + let root = T(1) / T(count) + self.K = Self.pow(span, root) + assert(self.K > 1) + } + + private static func pow(_ d: T, _ p: T) -> T { + if (T.self == Float.self) { return Foundation.pow(d as! Float, p as! Float) as! T } + if (T.self == Double.self) { return Foundation.pow(d as! Double, p as! Double) as! T } +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + if (T.self == Float80.self) { return Foundation.pow(d as! Float80, p as! Float80) as! T } +#endif + fatalError("You used 'pow' on \(T.self). It is not very effective.") + } + + func makeIterator() -> AnyIterator { + var index = 0 + + return AnyIterator { + if index == count { + return nil + } + + // Using 'pow' has better precision than multiplying the previous element. + let element = self.lowerBound * Self.pow(self.K, T(index)) + index += 1 + + let isLast = !element.isFinite || element > self.upperBound + return isLast ? upperBound : element + } + } + } + + func test_unpack() { + func test(type: T.Type, + file: StaticString = #file, + line: UInt = #line) { + let samples = GeometricSample( + lowerBound: T(1), + upperBound: T.greatestFiniteMagnitude, + count: 2_000 + ) + + for d in samples { + let unpack = Unpack(d) + assertBigInt(+d, +unpack.whole, file: file, line: line) + assertBigInt(-d, -unpack.whole, file: file, line: line) + + if unpack.isInteger { + assertExactlyBigInt(+d, +unpack.whole, file: file, line: line) + assertExactlyBigInt(-d, -unpack.whole, file: file, line: line) + } else { + assertExactlyBigIntIsNil(+d, file: file, line: line) + assertExactlyBigIntIsNil(-d, file: file, line: line) + } + } + } + + test(type: Float.self) + test(type: Double.self) +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + test(type: Float80.self) +#endif + } + + // MARK: - Normal - spacing + + // === IEEE_754_SPACING_NOTE === + // The spacing of the numbers in the range from [2**n, 2**(n+1)) is 2**(n−F) + // where F = significand bit width without the implicit 1 + // (for Float80 it is an explicit 1, but whatever…). + // + // Example for Double (significand has 52 bits explicitly stored): + // - 2**51 to 2**52 - spacing = 1/2 + // - 2**52 to 2**53 - spacing = 1; exactly integers + // - 2**53 to 2**54 - spacing = 2; everything is multiplied by 2 -> only even numbers + + /// Equally spaced samples over all of the positive integers with a given + /// `exponent` exactly representable by a given `T`. + /// + /// We start from `lowerBound = 2**exponent` and produce values according to + /// `lowerBound + N * spacing` formula. `N` is calculated, so that the samples + /// are spread evenly across the whole range. + /// + /// Please read IEEE_754_SPACING_NOTE above. + struct SampleExactlyRepresentableIntegers: Sequence { + + struct Element { + private let n: BigInt + private let d: T + + init(_ n: BigInt, _ d: T) { + self.n = n + self.d = d + } + + func withSign(_ sign: FloatingPointSign) -> (n: BigInt, d: T) { + switch sign { + case .plus: return (+self.n, +self.d) + case .minus: return (-self.n, -self.d) + } + } + } + + /// Distance between exactly representable integers for a given `T` and `exponent`. + /// If 'exponent < T.significandBitCount' then 'spacing = 1'. + let spacing: BigInt + /// Sample lower bound (included in result). + let lowerBound: BigInt + /// Sample upper bound (excluded in result). + let upperBound: BigInt + /// Number of produced samples. + let count: BigInt + /// Distance between each sample. + private let sampleInterval: BigInt + + // Equation from a IEEE_754_SPACING_NOTE (see above). + // Will round <1 spacing to 1. + static func calculateSpacing(exponent: Int) -> BigInt { + let F = T.significandBitCount + return Swift.max(1, 1 << (exponent - F)) + } + + init(exponent: Int, count: BigInt) { + self.count = count + self.spacing = Self.calculateSpacing(exponent: exponent) + + // Powers of 2 are exactly representable in 'T' (up to some point). + self.lowerBound = BigInt(1) << exponent + self.upperBound = lowerBound << 1 + + // Below we do: ((range / spacing) / count) * spacing. + // You may be tempted to remove 'spacing' from this equation (/ then *), + // but remember that in integer arithmetic '(n/a)*a' does not always give 'n'! + let totalSpacesCount = (self.upperBound - self.lowerBound) / spacing + let sampleIntervalInSpaces = totalSpacesCount / count + assert(sampleIntervalInSpaces != 0, "Got \(totalSpacesCount) values, requested \(count) count") + self.sampleInterval = sampleIntervalInSpaces * spacing + } + + func makeIterator() -> AnyIterator { + var index: BigInt = 0 + + return AnyIterator { + if index == self.count { + return nil + } + + let n = self.lowerBound + index * self.sampleInterval + assert(self.lowerBound <= n && n < self.upperBound) + + guard let d = T(exactly: n) else { + fatalError("\(n) is not exactly representable as \(T.self)") + } + + index += 1 // Important! + return Element(n, d) + } + } + } + + // Please read IEEE_754_SPACING_NOTE above. + func test_integerSpacing_by1() { + // In '2**T.significandBitCount' range using 'd.next' to go away from 0 + // will increment integer by 1. + + spacingWhereNextGoesUpByInteger(type: Float.self, + exponent: Float.significandBitCount, + spacing: 1) + + spacingWhereNextGoesUpByInteger(type: Double.self, + exponent: Double.significandBitCount, + spacing: 1) + +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + spacingWhereNextGoesUpByInteger(type: Float80.self, + exponent: Float80.significandBitCount, + spacing: 1) +#endif + } + + // Please read IEEE_754_SPACING_NOTE above. + func test_integerSpacing_by2() { + // In '2**(T.significandBitCount+1)' range using 'd.next' to go away from 0 + // will increment integer by 2. + + spacingWhereNextGoesUpByInteger(type: Float.self, + exponent: Float.significandBitCount + 1, + spacing: 2) + + spacingWhereNextGoesUpByInteger(type: Double.self, + exponent: Double.significandBitCount + 1, + spacing: 2) + +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + spacingWhereNextGoesUpByInteger(type: Float80.self, + exponent: Float80.significandBitCount + 1, + spacing: 2) +#endif + } + + // Please read IEEE_754_SPACING_NOTE above. + func test_integerSpacing_by4() { + // In '2**(T.significandBitCount+2)' range using 'd.next' to go away from 0 + // will increment integer by 4. + + spacingWhereNextGoesUpByInteger(type: Float.self, + exponent: Float.significandBitCount + 2, + spacing: 4) + + spacingWhereNextGoesUpByInteger(type: Double.self, + exponent: Double.significandBitCount + 2, + spacing: 4) + +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + spacingWhereNextGoesUpByInteger(type: Float80.self, + exponent: Float80.significandBitCount + 2, + spacing: 4) +#endif + } + + // Please read IEEE_754_SPACING_NOTE above. + func test_integerSpacing_galaxyFarFarAway() { + // See the tests above for details. + // Here we are at the very edge of the representable integers. + // Only 'greatestFiniteMagnitude' is bigger. + // Waaaay outside of the Int64 range, but I guess that's what BigInt is for. + + guard let floatSpacing = self.create("800000000000000000000", radix: 32) else { + XCTFail() + return + } + + spacingWhereNextGoesUpByInteger( + type: Float.self, + exponent: Float.greatestFiniteMagnitude.exponent - 1, + spacing: floatSpacing + ) + + let doubleSpacingString = "1" + String(repeating: "0", count: 194) + guard let doubleSpacing = self.create(doubleSpacingString, radix: 32) else { + XCTFail() + return + } + + spacingWhereNextGoesUpByInteger( + type: Double.self, + exponent: Double.greatestFiniteMagnitude.exponent - 1, + spacing: doubleSpacing + ) + +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + // This number has 3264 digits with 32 radix. + // We were not joking about this 'galaxy far far away'. + let float80SpacingString = "g" + String(repeating: "0", count: 3263) + guard let float80Spacing = self.create(float80SpacingString, radix: 32) else { + XCTFail() + return + } + + spacingWhereNextGoesUpByInteger( + type: Float80.self, + exponent: Float80.greatestFiniteMagnitude.exponent - 1, + spacing: float80Spacing + ) +#endif + } + + private func create(_ s: String, radix: Int) -> BigInt? { + return BigInt(s, radix: radix) + } + + /// Spacing test where 'T.next[Up/Down]' moves to next integer. + private func spacingWhereNextGoesUpByInteger( + type: T.Type, + exponent: Int, + spacing: BigInt, + file: StaticString = #file, + line: UInt = #line + ) { + assert(spacing >= 1) + + let sample = SampleExactlyRepresentableIntegers(exponent: exponent, count: 100) + assert(sample.spacing == spacing) + + for (sign, element) in CartesianProduct(signs, Array(sample)) { + let (n, d) = element.withSign(sign) + assertBigInt(d, n, file: file, line: line) + assertExactlyBigInt(d, n, file: file, line: line) + + // We chose exponent, so that the 'next' differs by 'spacing'. + let awayN = n + (sign == .plus ? spacing : -spacing) + let awayD = d.nextAwayFromZero + assertBigInt(awayD, awayN, file: file, line: line) + assertExactlyBigInt(awayD, awayN, file: file, line: line) + + // Going toward 0 is a little bit more difficult because: + // if we are at the lowerBound (1 << exponent) then going toward 0 changes + // the power of 2 which increases number density (spacing is smaller). + let towardDecreasesPowerOf2 = n.magnitude == sample.lowerBound + let towardSpacing = towardDecreasesPowerOf2 ? + SampleExactlyRepresentableIntegers.calculateSpacing(exponent: exponent - 1) : + spacing + + let towardN = n + (sign == .plus ? -towardSpacing : towardSpacing) + let towardD = d.nextTowardZero + assertBigInt(towardD, towardN, file: file, line: line) + + // If old spacing was 1 and we lowered it, then it is now 0.5. + // Any integer - 0.5 makes fraction which is not exactly representable by int. + // If we had any other spacing (like 2/4/etc) then we still have int. + let isTowardSpacingHalf = towardDecreasesPowerOf2 && spacing == 1 + if isTowardSpacingHalf { + assertExactlyBigIntIsNil(towardD, file: file, line: line) + } else { + assertExactlyBigInt(towardD, towardN, file: file, line: line) + } + } + } + + // Please read IEEE_754_SPACING_NOTE above. + func test_integerSpacing_by¹౹₂() { // ½ + // In '2**(T.significandBitCount-1)' range using 'd.next' to go away from 0 + // will increment integer by 0.5. + + spacingWhereNextGoesUpByFraction(type: Float.self, + exponent: Float.significandBitCount - 1) + + spacingWhereNextGoesUpByFraction(type: Double.self, + exponent: Double.significandBitCount - 1) + +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + spacingWhereNextGoesUpByFraction(type: Float80.self, + exponent: Float80.significandBitCount - 1) +#endif + } + + // Please read IEEE_754_SPACING_NOTE above. + func test_integerSpacing_by¹౹₄() { // ¼ + // In '2**(T.significandBitCount-2)' range using 'd.next' to go away from 0 + // will increment integer by 0.25. + + spacingWhereNextGoesUpByFraction(type: Float.self, + exponent: Float.significandBitCount - 2) + + spacingWhereNextGoesUpByFraction(type: Double.self, + exponent: Double.significandBitCount - 2) + +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + spacingWhereNextGoesUpByFraction(type: Float80.self, + exponent: Float80.significandBitCount - 2) +#endif + } + + /// Spacing test where 'T.next[Up/Down]' moves by a fraction (not a whole integer). + private func spacingWhereNextGoesUpByFraction( + type: T.Type, + exponent: Int, + file: StaticString = #file, + line: UInt = #line + ) { + let sample = SampleExactlyRepresentableIntegers(exponent: exponent, count: 100) + + for (sign, element) in CartesianProduct(signs, Array(sample)) { + let (n, d) = element.withSign(sign) + assertBigInt(d, n, file: file, line: line) + assertExactlyBigInt(d, n, file: file, line: line) + + // We chose exponent, so that the 'next' differs by 1/2 or 1/4 etc. + // This is not enough for 'next' to move to next integer, + // so rounding stays the same. But now the 'exact' fails. + let awayD = d.nextAwayFromZero + assertBigInt(awayD, n, file: file, line: line) + assertExactlyBigIntIsNil(awayD, file: file, line: line) + + // Going toward 0 decreases 'n' magnitude (obviously) and because the step + // is <1 (for example 1/2 or 1/4) the number is not exactly representable. + let towardN = n + (sign == .plus ? -1 : 1) + let towardD = d.nextTowardZero + assertBigInt(towardD, towardN, file: file, line: line) + assertExactlyBigIntIsNil(towardD, file: file, line: line) + } + } +} From 58ea906b44924b390c890fbdcffe1e2d628e764e Mon Sep 17 00:00:00 2001 From: LiarPrincess <4982138+LiarPrincess@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:57:36 +0100 Subject: [PATCH 3/4] Swiftlint --- .../InitFromBinaryFloatingPoint754.swift | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift b/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift index 096d34cb..263f72cc 100755 --- a/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift +++ b/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift @@ -13,6 +13,11 @@ import XCTest import Foundation @testable import BigIntModule +// swiftlint:disable xctfail_message +// swiftlint:disable function_default_parameter_at_end +// swiftlint:disable line_length +// swiftlint:disable file_length + // MARK: - Assertions /// BigInt(🐰) @@ -214,34 +219,28 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { // MARK: - Normal - struct PredefinedTestCase { - let name: String - let value: T - let expected: BigInt - let exactly: BigInt? - } - + // swiftlint:disable:next function_body_length func test_predefinedCases() { - typealias TC = PredefinedTestCase - func test(type: T.Type, file: StaticString = #file, line: UInt = #line) { + typealias Test = (name: String, value: T, expected: BigInt, exactly: BigInt?) + let gfmUnpack = Unpack(T.greatestFiniteMagnitude) assert(gfmUnpack.isInteger) let gfm = gfmUnpack.whole let testCases = [ - TC(name: "+π", value: +T.pi, expected: +3, exactly: nil), - TC(name: "-π", value: -T.pi, expected: -3, exactly: nil), - TC(name: "+ulpOfOne", value: +T.ulpOfOne, expected: 0, exactly: nil), - TC(name: "-ulpOfOne", value: -T.ulpOfOne, expected: 0, exactly: nil), - TC(name: "+leastNonzeroMagnitude", value: +T.leastNonzeroMagnitude, expected: 0, exactly: nil), - TC(name: "-leastNonzeroMagnitude", value: -T.leastNonzeroMagnitude, expected: 0, exactly: nil), - TC(name: "+leastNormalMagnitude", value: +T.leastNormalMagnitude, expected: 0, exactly: nil), - TC(name: "-leastNormalMagnitude", value: -T.leastNormalMagnitude, expected: 0, exactly: nil), - TC(name: "+greatestFiniteMagnitude", value: +T.greatestFiniteMagnitude, expected: +gfm, exactly: +gfm), - TC(name: "-greatestFiniteMagnitude", value: -T.greatestFiniteMagnitude, expected: -gfm, exactly: -gfm), + Test(name: "+π", value: +T.pi, expected: +3, exactly: nil), + Test(name: "-π", value: -T.pi, expected: -3, exactly: nil), + Test(name: "+ulpOfOne", value: +T.ulpOfOne, expected: 0, exactly: nil), + Test(name: "-ulpOfOne", value: -T.ulpOfOne, expected: 0, exactly: nil), + Test(name: "+leastNonzeroMagnitude", value: +T.leastNonzeroMagnitude, expected: 0, exactly: nil), + Test(name: "-leastNonzeroMagnitude", value: -T.leastNonzeroMagnitude, expected: 0, exactly: nil), + Test(name: "+leastNormalMagnitude", value: +T.leastNormalMagnitude, expected: 0, exactly: nil), + Test(name: "-leastNormalMagnitude", value: -T.leastNormalMagnitude, expected: 0, exactly: nil), + Test(name: "+greatestFiniteMagnitude", value: +T.greatestFiniteMagnitude, expected: +gfm, exactly: +gfm), + Test(name: "-greatestFiniteMagnitude", value: -T.greatestFiniteMagnitude, expected: -gfm, exactly: -gfm) ] for testCase in testCases { @@ -345,12 +344,14 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { } private static func pow(_ d: T, _ p: T) -> T { - if (T.self == Float.self) { return Foundation.pow(d as! Float, p as! Float) as! T } - if (T.self == Double.self) { return Foundation.pow(d as! Double, p as! Double) as! T } + // swiftlint:disable force_cast + if T.self == Float.self { return Foundation.pow(d as! Float, p as! Float) as! T } + if T.self == Double.self { return Foundation.pow(d as! Double, p as! Double) as! T } #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) - if (T.self == Float80.self) { return Foundation.pow(d as! Float80, p as! Float80) as! T } + if T.self == Float80.self { return Foundation.pow(d as! Float80, p as! Float80) as! T } #endif fatalError("You used 'pow' on \(T.self). It is not very effective.") + // swiftlint:enable force_cast } func makeIterator() -> AnyIterator { @@ -425,6 +426,7 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { /// Please read IEEE_754_SPACING_NOTE above. struct SampleExactlyRepresentableIntegers: Sequence { + // swiftlint:disable:next nesting struct Element { private let n: BigInt private let d: T @@ -607,7 +609,7 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { } private func create(_ s: String, radix: Int) -> BigInt? { - return BigInt(s, radix: radix) + return try? BigInt(s, radix: radix) } /// Spacing test where 'T.next[Up/Down]' moves to next integer. From de3b10eea17c8331c7d19e63888091ebf46ecf38 Mon Sep 17 00:00:00 2001 From: LiarPrincess <4982138+LiarPrincess@users.noreply.github.com> Date: Sun, 19 Mar 2023 22:10:06 +0100 Subject: [PATCH 4/4] Missing `self` + added 'Tests' suffix --- ...InitFromBinaryFloatingPoint754Tests.swift} | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) rename Tests/BigIntTests/{InitFromBinaryFloatingPoint754.swift => InitFromBinaryFloatingPoint754Tests.swift} (89%) diff --git a/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift b/Tests/BigIntTests/InitFromBinaryFloatingPoint754Tests.swift similarity index 89% rename from Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift rename to Tests/BigIntTests/InitFromBinaryFloatingPoint754Tests.swift index 263f72cc..921cc6c0 100755 --- a/Tests/BigIntTests/InitFromBinaryFloatingPoint754.swift +++ b/Tests/BigIntTests/InitFromBinaryFloatingPoint754Tests.swift @@ -72,7 +72,7 @@ private let signs: [FloatingPointSign] = [.plus, .minus] /// Based on 'IEEE-754 2008 Floating point specification'. /// Binary part; no decimals; no TensorFloat-32; no unums/posits. /// 2019 is also available, but I'm poor… 🤷. -final class InitFromBinaryFloatingPoint754: XCTestCase { +final class InitFromBinaryFloatingPoint754Tests: XCTestCase { // MARK: - Zero, infinity, Batman, subnormal @@ -469,15 +469,15 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { // Powers of 2 are exactly representable in 'T' (up to some point). self.lowerBound = BigInt(1) << exponent - self.upperBound = lowerBound << 1 + self.upperBound = self.lowerBound << 1 // Below we do: ((range / spacing) / count) * spacing. // You may be tempted to remove 'spacing' from this equation (/ then *), // but remember that in integer arithmetic '(n/a)*a' does not always give 'n'! - let totalSpacesCount = (self.upperBound - self.lowerBound) / spacing + let totalSpacesCount = (self.upperBound - self.lowerBound) / self.spacing let sampleIntervalInSpaces = totalSpacesCount / count assert(sampleIntervalInSpaces != 0, "Got \(totalSpacesCount) values, requested \(count) count") - self.sampleInterval = sampleIntervalInSpaces * spacing + self.sampleInterval = sampleIntervalInSpaces * self.spacing } func makeIterator() -> AnyIterator { @@ -506,18 +506,18 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { // In '2**T.significandBitCount' range using 'd.next' to go away from 0 // will increment integer by 1. - spacingWhereNextGoesUpByInteger(type: Float.self, - exponent: Float.significandBitCount, - spacing: 1) + self.spacingWhereNextGoesUpByInteger(type: Float.self, + exponent: Float.significandBitCount, + spacing: 1) - spacingWhereNextGoesUpByInteger(type: Double.self, - exponent: Double.significandBitCount, - spacing: 1) + self.spacingWhereNextGoesUpByInteger(type: Double.self, + exponent: Double.significandBitCount, + spacing: 1) #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) - spacingWhereNextGoesUpByInteger(type: Float80.self, - exponent: Float80.significandBitCount, - spacing: 1) + self.spacingWhereNextGoesUpByInteger(type: Float80.self, + exponent: Float80.significandBitCount, + spacing: 1) #endif } @@ -526,18 +526,18 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { // In '2**(T.significandBitCount+1)' range using 'd.next' to go away from 0 // will increment integer by 2. - spacingWhereNextGoesUpByInteger(type: Float.self, - exponent: Float.significandBitCount + 1, - spacing: 2) + self.spacingWhereNextGoesUpByInteger(type: Float.self, + exponent: Float.significandBitCount + 1, + spacing: 2) - spacingWhereNextGoesUpByInteger(type: Double.self, - exponent: Double.significandBitCount + 1, - spacing: 2) + self.spacingWhereNextGoesUpByInteger(type: Double.self, + exponent: Double.significandBitCount + 1, + spacing: 2) #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) - spacingWhereNextGoesUpByInteger(type: Float80.self, - exponent: Float80.significandBitCount + 1, - spacing: 2) + self.spacingWhereNextGoesUpByInteger(type: Float80.self, + exponent: Float80.significandBitCount + 1, + spacing: 2) #endif } @@ -546,18 +546,18 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { // In '2**(T.significandBitCount+2)' range using 'd.next' to go away from 0 // will increment integer by 4. - spacingWhereNextGoesUpByInteger(type: Float.self, - exponent: Float.significandBitCount + 2, - spacing: 4) + self.spacingWhereNextGoesUpByInteger(type: Float.self, + exponent: Float.significandBitCount + 2, + spacing: 4) - spacingWhereNextGoesUpByInteger(type: Double.self, - exponent: Double.significandBitCount + 2, - spacing: 4) + self.spacingWhereNextGoesUpByInteger(type: Double.self, + exponent: Double.significandBitCount + 2, + spacing: 4) #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) - spacingWhereNextGoesUpByInteger(type: Float80.self, - exponent: Float80.significandBitCount + 2, - spacing: 4) + self.spacingWhereNextGoesUpByInteger(type: Float80.self, + exponent: Float80.significandBitCount + 2, + spacing: 4) #endif } @@ -573,7 +573,7 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { return } - spacingWhereNextGoesUpByInteger( + self.spacingWhereNextGoesUpByInteger( type: Float.self, exponent: Float.greatestFiniteMagnitude.exponent - 1, spacing: floatSpacing @@ -585,7 +585,7 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { return } - spacingWhereNextGoesUpByInteger( + self.spacingWhereNextGoesUpByInteger( type: Double.self, exponent: Double.greatestFiniteMagnitude.exponent - 1, spacing: doubleSpacing @@ -600,7 +600,7 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { return } - spacingWhereNextGoesUpByInteger( + self.spacingWhereNextGoesUpByInteger( type: Float80.self, exponent: Float80.greatestFiniteMagnitude.exponent - 1, spacing: float80Spacing @@ -609,7 +609,7 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { } private func create(_ s: String, radix: Int) -> BigInt? { - return try? BigInt(s, radix: radix) + return BigInt(s, radix: radix) } /// Spacing test where 'T.next[Up/Down]' moves to next integer. @@ -665,15 +665,15 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { // In '2**(T.significandBitCount-1)' range using 'd.next' to go away from 0 // will increment integer by 0.5. - spacingWhereNextGoesUpByFraction(type: Float.self, - exponent: Float.significandBitCount - 1) + self.spacingWhereNextGoesUpByFraction(type: Float.self, + exponent: Float.significandBitCount - 1) - spacingWhereNextGoesUpByFraction(type: Double.self, - exponent: Double.significandBitCount - 1) + self.spacingWhereNextGoesUpByFraction(type: Double.self, + exponent: Double.significandBitCount - 1) #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) - spacingWhereNextGoesUpByFraction(type: Float80.self, - exponent: Float80.significandBitCount - 1) + self.spacingWhereNextGoesUpByFraction(type: Float80.self, + exponent: Float80.significandBitCount - 1) #endif } @@ -682,15 +682,15 @@ final class InitFromBinaryFloatingPoint754: XCTestCase { // In '2**(T.significandBitCount-2)' range using 'd.next' to go away from 0 // will increment integer by 0.25. - spacingWhereNextGoesUpByFraction(type: Float.self, - exponent: Float.significandBitCount - 2) + self.spacingWhereNextGoesUpByFraction(type: Float.self, + exponent: Float.significandBitCount - 2) - spacingWhereNextGoesUpByFraction(type: Double.self, - exponent: Double.significandBitCount - 2) + self.spacingWhereNextGoesUpByFraction(type: Double.self, + exponent: Double.significandBitCount - 2) #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) - spacingWhereNextGoesUpByFraction(type: Float80.self, - exponent: Float80.significandBitCount - 2) + self.spacingWhereNextGoesUpByFraction(type: Float80.self, + exponent: Float80.significandBitCount - 2) #endif }