diff --git a/Sources/BigIntModule/BigInt.swift b/Sources/BigIntModule/BigInt.swift index 2e2721c6..3e06993d 100644 --- a/Sources/BigIntModule/BigInt.swift +++ b/Sources/BigIntModule/BigInt.swift @@ -29,12 +29,6 @@ public struct BigInt: SignedInteger { internal var _isNegative: Bool { words[words.endIndex - 1] > Int.max } - - private static let _digits: [BigInt] = (0 ... 36).map { - BigInt(_uncheckedWords: [UInt(bitPattern: $0)]) - } - - private static let _digitRadix = BigInt(_uncheckedWords: [0, 1]) } // MARK: - Basic Behaviors @@ -96,39 +90,342 @@ extension BigInt: LosslessStringConvertible { public init?(_ description: String) { self.init(description, radix: 10) } + + //////////////////////////////////////////////////////////////////////////// + /// + /// NEW CODE STARTS + /// Code liberated and adapted from the Violet BigInt implementation + /// Speeds the time to initialize a BigInt by about a factor of 35 for + /// the test case of the string for 512! using radix 10. A radix 16 test + /// for the same number was 310X faster. + public init?(_ description: String, radix: Int = 10) { + guard 2 <= radix && radix <= Self.maxRadix else { return nil } + guard !description.isEmpty else { return nil } + + let utf8 = description.utf8 + + // Most of the time we will go 'fast' (in Swift dominant apps). + // Fast (on 'UnsafeBufferPointer') is 2x faster than on 'UTF8View'. + let fast = utf8.withContiguousStorageIfAvailable { buffer in + return Self.parse(buffer, radix: radix) + } - public init?(_ description: T, radix: Int = 10) where T: StringProtocol { - precondition(2 ... 36 ~= radix, "Radix not in range 2 ... 36") + // Try again -- if necessary -- with a standard buffer + if let r = fast ?? Self.parse(utf8, radix: radix) { + self = r + return + } + return nil + } - self = 0 + private static func mulAdd(result: inout Words, multiplier: UInt, addIn: UInt) { + var carry = addIn + var overflow = false + let highWord = result.count-1 + for i in 0...highWord { + let product = result[i].multipliedFullWidth(by: multiplier) + (result[i], overflow) = carry.addingReportingOverflow(product.low) + (carry, overflow) = product.high.addingReportingOverflow(overflow ? 1 : 0) + } + assert(!overflow, "Word size overflow during \(#function)") + } - let isNegative = description.hasPrefix("-") - let hasPrefix = isNegative || description.hasPrefix("+") - let utf8 = description.utf8.dropFirst(hasPrefix ? 1 : 0) - guard !utf8.isEmpty else { return nil } + // MARK: - Parse + + private static func parse(_ chars: C, radix: Int) -> BigInt? where C.Element == UInt8 { + var (isNegative, index) = Self.checkSign(chars) + let endIndex = chars.endIndex + if index == endIndex { return nil } // only the sign was found + + // note and discard leading zeros + let hasZeroPrefix = Self.stripLeadingZeros(chars, index: &index) + if index == endIndex { return hasZeroPrefix ? BigInt() : nil } // only zeros - return 0 + let (charCountPerWord, power) = Self.maxRepresentablePower(of: radix) + let remainingCount = chars.distance(from: index, to: endIndex) + + // allocate one more word than estimated + let capacity = (remainingCount / charCountPerWord) + 1 + let digitParser = DigitParser(radix: radix) + var currentWord = UInt(0) + let firstWordCount = remainingCount % charCountPerWord + var remainingCharsInCurrentWord = firstWordCount == 0 ? charCountPerWord : firstWordCount + let isPowerOfTwo = radix & (radix - 1) == 0 + + // working buffer + var buffer = Words(repeating: 0, count: capacity) + if isPowerOfTwo { + // Radix powers of 2 convert about 10X faster with this algorithm + var reverseIndex = chars.endIndex + chars.formIndex(before: &reverseIndex) + var wordIndex = 0 + var wordShift = 0 + let bitsPerChar = radix.trailingZeroBitCount + buffer[wordIndex] = 0 + while reverseIndex >= index { + // check for illegal digits and convert to UInt + let char = chars[reverseIndex] + guard let digit = digitParser.parse(char) else { return nil } + + buffer[wordIndex] |= digit &<< wordShift + wordShift &+= bitsPerChar + if wordShift >= UInt.bitWidth { + wordIndex &+= 1 + wordShift = wordShift % UInt.bitWidth + assert(wordIndex < capacity) + buffer[wordIndex] = digit &>> (bitsPerChar &- wordShift) + } + chars.formIndex(before: &reverseIndex) + } + } else { + while index != endIndex { + // check for illegal digits and convert to UInt + let char = chars[index] + guard let digit = digitParser.parse(char) else { return nil } + + // Overflows are guaranteed to not occur due to `charCountPerWord` + currentWord = currentWord &* UInt(radix) &+ digit + remainingCharsInCurrentWord &-= 1 + if remainingCharsInCurrentWord == 0 { + // Append word even if it is zero -- zeros can occur in the middle of a number + mulAdd(result: &buffer, multiplier: power, addIn: currentWord) + currentWord = 0 + remainingCharsInCurrentWord = charCountPerWord + } + chars.formIndex(after: &index) + } + } - for var byte in utf8 { - switch byte { - case UInt8(ascii: "0") ... UInt8(ascii: "9"): - byte -= UInt8(ascii: "0") - case UInt8(ascii: "A") ... UInt8(ascii: "Z"): - byte -= UInt8(ascii: "A") - byte += 10 - case UInt8(ascii: "a") ... UInt8(ascii: "z"): - byte -= UInt8(ascii: "a") - byte += 10 - default: - return nil + BigInt._dropExcessWords(words: &buffer) + var result = BigInt(_uncheckedWords: buffer) + if isNegative { result.negate() } + return result + } + + private static func checkSign(_ chars: C) -> (isNegative:Bool, index: C.Index) where C.Element == UInt8 { + var index = chars.startIndex + let first = chars[index] + if first == _plus { + chars.formIndex(after: &index) + return (isNegative: false, index: index) + } + if first == _minus { + chars.formIndex(after: &index) + return (isNegative: true, index: index) + } + return (isNegative: false, index: index) + } + + private static func stripLeadingZeros(_ chars: C, index: inout C.Index) -> Bool where C.Element == UInt8 { + let hasZeroPrefix = chars[index] == _0 + let endIndex = chars.endIndex + + // skip leading zeros + while index != endIndex && chars[index] == _0 { + chars.formIndex(after: &index) + } + return hasZeroPrefix + } + + private struct DigitParser { + private let numericalUpperBound: UInt8 + private let uppercaseUpperBound: UInt8 + private let lowercaseUpperBound: UInt8 + + fileprivate init(radix: Int) { + if radix <= 10 { + self.numericalUpperBound = _0 &+ UInt8(truncatingIfNeeded: radix) + self.uppercaseUpperBound = _A + self.lowercaseUpperBound = _a + } else { + self.numericalUpperBound = _0 &+ 10 + self.uppercaseUpperBound = _A &+ UInt8(truncatingIfNeeded: radix &- 10) + self.lowercaseUpperBound = _a &+ UInt8(truncatingIfNeeded: radix &- 10) } - guard byte < radix else { return nil } - self *= BigInt._digits[radix] - self += BigInt._digits[Int(byte)] } - if isNegative { - self.negate() + fileprivate func parse(_ char: UInt8) -> UInt? { + return + _0 <= char && char < numericalUpperBound ? UInt(truncatingIfNeeded: char &- _0) : + _A <= char && char < uppercaseUpperBound ? UInt(truncatingIfNeeded: char &- _A &+ 10) : + _a <= char && char < lowercaseUpperBound ? UInt(truncatingIfNeeded: char &- _a &+ 10) : + nil } } + + /// NEW CODE ENDS + /// + //////////////////////////////////////////////////////////////////////////// +} + +extension BigInt : CustomStringConvertible { + + //////////////////////////////////////////////////////////////////////////// + /// + /// NEW CODE STARTS + + /// Code below was shamelessly adapted from the Violet implementation of + /// BigInt. I took a few shortcuts to reduce the code size: There's no + /// DivBuffer and instead used a copy of the self.words for doing the + /// divisions/shifts. A different approach was used to calculate the number + /// of digits needed in a string — again less code was needed. The good: + /// This code is almost 20X faster for decimals and 90X faster for binary + /// radixes. The bad: Lots of code required to implement this improvement. + /// Is it worth it? You decide. It's always easy to comment out and use the + /// default (albeit much slower) String() implementation. Maybe the String + /// in the runtime could be updated to be faster. + public var description: String { + return self.toString(radix: 10, uppercase: false) + } + + private static var maxRadix: Int { 36 } + + // ASCII constants + + private static let _0 = UInt8(ascii: "0") + private static let _A = UInt8(ascii: "A") + private static let _a = UInt8(ascii: "a") + private static let _plus = UInt8(ascii: "+") + private static let _minus = UInt8(ascii: "-") + + internal func div(_ dividend: inout Words, by divisor: UInt) -> UInt { + var carry = UInt(0) + for i in (0.. String { + precondition(2 ... Self.maxRadix ~= radix, "Radix not in range 2 ... \(Self.maxRadix)") + + if self.signum() == 0 { return "0" } + + let digitCount = Int(Self.radixDigits[radix-2] * Double(words.count) + 0.5) + let (charPerWord, power) = Self.maxRepresentablePower(of: radix) + let stringMaxSize = digitCount + (self._isNegative ? 1 : 0) + let radixValid = radix != 8 && radix != 32 + let isValidPowerOfTwoRadix = (radix & (radix-1) == 0) && radixValid + + var words = self.magnitude.words; words.reserveCapacity(self.words.count) + if #available(macOS 11.0, iOS 14.0, *) { + return StringLiteralType(unsafeUninitializedCapacity: stringMaxSize) { buffer in + if isValidPowerOfTwoRadix { + // Handle powers-of-two radixes + let bitsPerChar = radix.trailingZeroBitCount + let qr = UInt.bitWidth.quotientAndRemainder(dividingBy: bitsPerChar) + assert(qr.remainder == 0, "String radix = \(radix) which does not fit in \(UInt.bitWidth) bits") + var index = 0 + if self._isNegative { + buffer[index] = Self._minus + index = 1 + } + + // digits fit exactly into a word + // 0000 0001 1010 0101 + // ^^^^ skip (but only for 1st word) + let charCountPerWord = qr.quotient + let last = words[words.count - 1] + var skip = charCountPerWord - last.leadingZeroBitCount / bitsPerChar - 1 + let mask = UInt(radix - 1) + + for word in words.reversed() { + for groupIndex in stride(from: skip, through: 0, by: -1) { + let shift = groupIndex &* bitsPerChar + let group = (word &>> shift) & mask + buffer[index] = ascii(group, uppercase: uppercase) + index &+= 1 + } + + // From now on we print everything, even middle '0' + skip = charCountPerWord - 1 + } + + assert(index <= stringMaxSize) + return index + } else { + // Deal with the hard-to-use radixes including base 10 + var index = stringMaxSize - 1 // start at end of the buffer + while !words.isEmpty { + var remainder = div(&words, by: power) + let end = index - charPerWord + + // extract `radix` digits and add to the string `buffer` + while remainder != 0 { + let qr = remainder.quotientAndRemainder(dividingBy: UInt(radix)) + remainder = qr.quotient + buffer[index] = ascii(qr.remainder, uppercase: uppercase) + index &-= 1 + } + + // fill remaining word digits (except the first) with "0"s + let isFirstWord = words.isEmpty + while !isFirstWord && index != end { + buffer[index] = Self._0 + index &-= 1 + } + } + + // add a minus sign if the number is negative + if self._isNegative { + buffer[index] = Self._minus + index &-= 1 + } + + // check for invalid capacity estimate + var count = stringMaxSize + if index != -1 { + count = stringMaxSize - index - 1 + let dstPtr = buffer.baseAddress! + let srcPtr = dstPtr.advanced(by: index+1) + dstPtr.update(from: srcPtr, count: count) + dstPtr[count] = 0 + } + return count + } + } + } else { + // Fallback on earlier versions + return String(self, radix: radix, uppercase: uppercase) + } + } + + /// Returns the highest number that satisfy `radix^n <= 2^Self.bitWidth` + internal static func maxRepresentablePower(of radix: Int) -> (n: Int, power: UInt) { + var n = 1 + var power = UInt(radix) + while true { + let (newPower, overflow) = power.multipliedReportingOverflow(by: UInt(radix)) + if overflow { return (n, power) } + n += 1 + power = newPower + } + } + + private func ascii(_ n: UInt, uppercase: Bool) -> UInt8 { + assert(n < Self.maxRadix) // Always less, never equal! + let n = UInt8(truncatingIfNeeded: n) + return n < 10 ? n + Self._0 : n - 10 + (uppercase ? Self._A : Self._a) + } + + /// Table of the number of digits for each radix from 2 to 36 + /// y = log(Double(UInt.max)) / log(Double(radix)) + static let radixDigits : [Double] = [ + 64, 40.3795042286, 32.0000000000, 27.5632997167, 24.7585796630, + 22.7972599749, 21.3333333333, 20.1897521143, 19.2659197225, 18.5001488843, + 17.8523485217, 17.2952418833, 16.8095702424, 16.3813135878, 16.0000000000, + 15.6576346956, 15.3479978604, 15.0661704555, 14.8082056422, 14.5708959166, + 14.3516047499, 14.1481426853, 13.9586746871, 13.7816498583, 13.6157474274, + 13.4598347429, 13.3129342513, 13.1741972775, 13.0428830138, 12.9183415413, + 12.8000000000, 12.6873512429, 12.5799444629, 12.4773774012, 12.3792898315 + ] + + /// NEW CODE ENDS + /// + //////////////////////////////////////////////////////////////////////////// } extension BigInt: Decodable { @@ -158,8 +455,8 @@ extension BigInt: Encodable { extension BigInt: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int) { - if value >= 0, value < BigInt._digits.count { - self = BigInt._digits[value] + if value >= 0, value <= UInt.max { + words = [UInt(value)] // No need for a table lookup here } else { words = [UInt(bitPattern: value)] } @@ -251,7 +548,7 @@ extension BigInt: Numeric { let count = lhsWords.count + rhsWords.count + 1 var newWords = Words(repeating: 0, count: count) - + for i in 0 ..< rhsWords.count { var carry: UInt = 0 var digit: UInt = 0 @@ -284,7 +581,7 @@ extension BigInt: Numeric { newWords[lastJ + 1] = digit } } - + for i in stride(from: count - 1, through: 1, by: -1) { if newWords[i] == 0, newWords[i - 1] <= Int.max { newWords.removeLast() @@ -310,6 +607,7 @@ extension BigInt: SignedNumeric { public mutating func negate() { var isOverflow = true + let isNegative = self._isNegative for i in 0 ..< words.count { if isOverflow { (words[i], isOverflow) = (~words[i]).addingReportingOverflow(1) @@ -319,6 +617,12 @@ extension BigInt: SignedNumeric { } BigInt._dropExcessWords(words: &words) + if self != Self.zero && self._isNegative == isNegative { + // Corner case where numbers like `0x8000000000000000 ... 0000` + // remain unchanged after negation so we make sure any negative + // numbers are truly negated into positive numbers + if isNegative { words.append(0) } // make the number positive + } } @inlinable @@ -372,8 +676,8 @@ extension BigInt: BinaryInteger { } public init(_ source: T) where T: BinaryInteger { - if source >= 0, source < BigInt._digits.count { - self = BigInt._digits[Int(source)] + if source >= 0 && source < Int.max { + words = [UInt(source)] } else { words = Words(source.words) if source > 0 && source.words[source.words.endIndex - 1] > Int.max { @@ -390,7 +694,7 @@ extension BigInt: BinaryInteger { } public init(truncatingIfNeeded source: T) where T: BinaryInteger { - words = Words(source.words) + self.init(source) //words = Words(source.words) } public var bitWidth: Int { words.count * UInt.bitWidth } @@ -422,7 +726,6 @@ extension BigInt: BinaryInteger { @inlinable public static func % (lhs: BigInt, rhs: BigInt) -> BigInt { let (_, result) = _div(lhs: lhs, rhs: rhs) - return result } @@ -464,7 +767,7 @@ extension BigInt: BinaryInteger { BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) for i in 0 ..< rhsWords.count { - lhs.words[i] &= rhsWords[i] + lhs.words[i] ^= rhsWords[i] } BigInt._dropExcessWords(words: &lhs.words) @@ -634,11 +937,17 @@ extension BigInt { /// See _The Art of Computer Programming_ volume 2 by Donald Knuth, Section 4.3.1: The Classical Algorithms @usableFromInline internal static func _div(lhs: BigInt, rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { - precondition(rhs != _digits[0], "Division by zero error!") + precondition(rhs != 0, "Division by zero error!") + // Speed up single-word divisions if lhs.words.count == 1, rhs.words.count == 1 { - let (quot, rem) = Int(bitPattern: lhs.words[0]).quotientAndRemainder(dividingBy: Int(bitPattern: rhs.words[0])) - return (BigInt(_uncheckedWords: [UInt(bitPattern: quot)]), BigInt(_uncheckedWords: [UInt(bitPattern: rem)])) + // check for corner case that causes overflow: Int.min / -1 + let lhsInt = Int(bitPattern: lhs.words[0]) + let rhsInt = Int(bitPattern: rhs.words[0]) + if !(lhsInt == Int.min && rhsInt == -1) { + let (quot, rem) = lhsInt.quotientAndRemainder(dividingBy: rhsInt) + return (BigInt(_uncheckedWords: [UInt(bitPattern: quot)]), BigInt(_uncheckedWords: [UInt(bitPattern: rem)])) + } } let lhsIsNeg = lhs._isNegative @@ -663,7 +972,13 @@ extension BigInt { } BigInt._dropExcessWords(words: ") - return (quotient: BigInt(_uncheckedWords: quot), remainder: BigInt(r)) + // signs are based on the Int definitions + switch (lhsIsNeg, rhsIsNeg) { + case (false, true): return (-BigInt(_uncheckedWords: quot), BigInt(r)) + case (false, false): return ( BigInt(_uncheckedWords: quot), BigInt(r)) + case (true, false): return (-BigInt(_uncheckedWords: quot), -BigInt(r)) + case (true, true): return ( BigInt(_uncheckedWords: quot), -BigInt(r)) + } } while rhsWords[rhsWords.endIndex - 1] == 0 { @@ -770,8 +1085,14 @@ extension BigInt { BigInt._dropExcessWords(words: ") BigInt._dropExcessWords(words: &rem) - - return (BigInt(_uncheckedWords: quot), BigInt(_uncheckedWords: rem)) + + // signs are based on the Int definitions + switch (lhsIsNeg, rhsIsNeg) { + case (false, true): return (-BigInt(_uncheckedWords: quot), BigInt(_uncheckedWords: rem)) + case (false, false): return ( BigInt(_uncheckedWords: quot), BigInt(_uncheckedWords: rem)) + case (true, false): return (-BigInt(_uncheckedWords: quot), -BigInt(_uncheckedWords: rem)) + case (true, true): return ( BigInt(_uncheckedWords: quot), -BigInt(_uncheckedWords: rem)) + } } private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) { diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 444ae38a..103c5b5d 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -81,27 +81,71 @@ final class BigIntTests: XCTestCase { 7AHD1LS5YKZ66F4UPG0RCBGG000000000000000000000000000000000000000000000000000\ 000000000000000000000000000000000000000000000000000000000000000000000000000 """ + + static let descriptionFactorial512_radix10 = + """ + 3477289793132605363283045917545604711992250655643514570342474831551610412066352543473209850\ + 3395022536443224331102139454529500170207006901326415311326093794135871186404471618686104089\ + 9557497361427588282356254968425012480396855239725120562512065555822121708786443620799246550\ + 9591872320268380814151785881725352800207863134700768597399809657208738499042913738268415847\ + 1279861843038733804232977180172476769109501954575898694273251503355152959500987699927955393\ + 1070378592917099002397061907147143424113252117585950817850896618433994140232823316432187410\ + 3563412623863324969543199731304073425672820273985793825430484568768008623499281404119054312\ + 7619743567460328184253074417752736588572162951225387238661311882154084789749310739838195608\ + 1763695236422795880296204301770808809477147632428639299038833046264585834888158847387737841\ + 8434136648928335862091963669797757488958218269240400578451402875222386750821375703159545267\ + 2743709490491479678264100074077789791913409339353042276095514021138717365004735834735337923\ + 4387609261306673773281412893026941927424000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000 + """ + static let descriptionFactorial512_radix16 = + """ + 8f9ef398c97defd735dfa6eb05f0aab2afbf84ea79a8e30b10dd6a305f7e1fc1243dc22f19bb1fc48602a8019d5\ + 889719e4de855351eb6fc5db53c44cfc9ad3d56120ebd8e9ac5cfcac1f438a9c62189b0e1987b27344ac0a871b0\ + bafacb4b900597d9408ffe7329be5cf061ccc22723714a2c5576bdb663c32b7e9a9a51f799a6dfd461f7f5805ae\ + 1b9e79950d5552be34cd47ad1b4abd6a731f34825654ad34f676d84533464c50503d7643ffe6a616f055754e580\ + b59be37a89987abde817d5ecc43903a676a7259ca793dc1975dab19b63d0855003af4981fbd726b009309cceb9e\ + 70bd68b548a0f17b78d27da4f1d829ca1adafe45e65a720e2ff815382c9fcbd81342636f6cd97e790ebbaa766f5\ + 122cf6c1585707c09ca491f07603c33c95a4fce736bf54255f16b085aa2ef59cd9883929a14c35be1c7f54547db\ + d2ff9b17bf93175b3950bdf82b97bc3d6ffceb5b3466231ce4c655db08ea6d0ac135113d0253b49f1d15dcf5ffe\ + 372a3edc3ea1a747d78baa21c6163be4580e989c93731057959e3c803a1d292aab93f30419d63f5667be6146889\ + f9532c580769e1a06eb145800000000000000000000000000000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000000000000000000000 + """ // MARK: - Basic arithmetic func testDivision() { + // Signed division test + let numa = BigInt("-18446744073709551616")! + let dena = BigInt(123) + let expecteda = BigInt(Int64(-149973529054549200)) + XCTAssertEqual(numa / dena, expecteda) + + let numb = BigInt("18446744073709551616")! + let denb = BigInt(-123) + XCTAssertEqual(numb / denb, expecteda) + let expectedb = BigInt(Int64(149973529054549200)) + XCTAssertEqual(numa / denb, expectedb) + + // Previous test cases let num1 = BigInt("18446744073709551616")! let den1 = BigInt(123) let expected1 = BigInt(UInt64(149973529054549200)) XCTAssertEqual(num1 / den1, expected1) - + let num2 = BigInt.pow(BigInt(10), 100) let den2: BigInt = 3 let expected2: BigInt = BigInt(String(repeating: "3", count: 100))! let actual2 = num2 / den2 XCTAssertEqual(actual2, expected2) - + let num3 = BigInt.pow(BigInt(10), 97) let den3: BigInt = BigInt("33333333333333333333")! let expected3: BigInt = BigInt("300000000000000000003000000000000000000030000000000000000000300000000000000000")! let actual3 = num3 / den3 XCTAssertEqual(actual3, expected3) - + let foo = BigInt("12345678901234567890123456789012345678901234567890123456789012345678901234567890")! let bar = BigInt("351235231535161613134135135135")! let baz = foo / bar @@ -110,7 +154,7 @@ final class BigIntTests: XCTestCase { XCTAssertNotNil(BigInt(exactly: 2.4e39)) XCTAssertNotNil(BigInt(exactly: 1e38)) XCTAssertEqual(BigInt(2.4e39) / BigInt(1e38), BigInt(24)) - + for _ in 0 ..< 100 { let expected = BigInt(Float64.random(in: 0x1p64 ... 0x1p255)) let divisor = BigInt(Float64.random(in: 0x1p64 ... 0x1p128)) @@ -133,6 +177,27 @@ final class BigIntTests: XCTestCase { """) } } + + func testStringToBigIntImprovements() { + var expectedNumber: BigInt! + + measure { + expectedNumber = BigInt(Self.descriptionFactorial512_radix16, radix: 16) + } + + XCTAssertEqual(expectedNumber, BigInt(Self.descriptionFactorial512_radix10, radix: 10)) + } + + func testBigIntToStringImprovements() { + let number = BigInt.fac(512) + var expectedString = "" + + measure { + expectedString = number.toString(radix: 16, uppercase: false) + } + + XCTAssertEqual(expectedString, Self.descriptionFactorial512_radix16) + } func testFactorial() { var expectedNumber: BigInt? @@ -201,11 +266,11 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(BigInt(Int64.max).signum(), +1) XCTAssertEqual(BigInt(+0x1p1023).signum(), +1) } - + func testTrailingZeroCount() { let foo = BigInt(1) << 300 XCTAssertEqual(foo.trailingZeroBitCount, 300) - + let bar = (BigInt(1) << 300) + 0b101000 XCTAssertEqual(bar.trailingZeroBitCount, 3) } @@ -254,6 +319,180 @@ final class BigIntTests: XCTestCase { +BigInt("+1234567890123456789012345678901234567890")!) } + func testsWhichCrashed() { + // -9223372036854775808 = Int64.min, obviously '-Int64.min' overflows + let int: Int64 = -9223372036854775808 + var big = BigInt(int) + XCTAssertEqual(-big, big * -1) + + // 9223372036854775808 = UInt64(1) << Float80.significandBitCount + var int2 : UInt64 = 9223372036854775808 + big = BigInt(int2) + let _ = Float80(exactly: int2) // works + let _ = Float80(exactly: big) // crash (not anymore) + + // 18446744073709551615 = UInt64.max - was crashing + int2 = 18446744073709551615 + big = BigInt(int2) + let _ = UInt64(big) + + // was generating an overflow + let lhsInt = -9223372036854775808 + let rhsInt = -1 + let lhs = BigInt(lhsInt) + let rhs = BigInt(rhsInt) + _ = lhs / rhs // Overflow + } + + func test_initFromInt_exactly() { + let int: UInt64 = 18446744073709551614 + let big = BigInt(exactly: int)! + let revert = UInt64(exactly: big) + XCTAssertEqual(int, revert) + } + + func test_initFromInt_clamping() { + let int: UInt64 = 18446744073709551614 + let big = BigInt(clamping: int) + let revert = UInt64(clamping: big) + XCTAssertEqual(int, revert) + } + + func test_initFromInt_truncatingIfNeeded() { + let int: UInt64 = 18446744073709551615 + let big = BigInt(truncatingIfNeeded: int) + let intString = String(int, radix: 10, uppercase: false) + let bigString = String(big, radix: 10, uppercase: false) + XCTAssertEqual(bigString, intString) + } + + func test_unaryMinus() { + // -9223372036854775808 = Int.min + // 'Int.min' negation overflows - ok now + let int = -9223372036854775808 + let expected = BigInt(int.magnitude) + + let big = -BigInt(int) + XCTAssertEqual(big, expected) + + var negated = BigInt(int) + negated.negate() + XCTAssertEqual(negated, expected, "\(negated) == \(expected)") + } + + func test_div_sign() { + // positive / negative = negative + var lhs = BigInt("18446744073709551615")! + var rhs = BigInt("-1")! + var expected = BigInt("-18446744073709551615")! + XCTAssertEqual(lhs / rhs, expected) + + // negative / positive = negative + lhs = BigInt("-340282366920938463481821351505477763074")! + rhs = BigInt("18446744073709551629")! + expected = BigInt("-18446744073709551604")! + XCTAssertEqual(lhs / rhs, expected) + } + + func test_magnitude() { + let big = BigInt(-9223372036854775808) + let expect = UInt64(9223372036854775808) + XCTAssertEqual(big.magnitude, BigInt(expect)) + } + + // ApplyA_ApplyB_Equals_ApplyAB.swift + func test_a_b_ab() { + // (a/-3) / -5 = a/15 + let lhs = BigInt("-18446744073709551615")! + let a = BigInt(-3) + let b = BigInt(-5) + let ab = BigInt(15) + + let r0 = (lhs / a) / b + let r1 = lhs / ab + XCTAssertEqual(r0, r1) + } + + // ApplyA_UndoA.swift + func test_a_undoA() { + let n = BigInt(-1) + let x = BigInt(-9223372036854775808) + XCTAssertEqual(n, (n + x) - x) + XCTAssertEqual(n, (n * x) / x) + } + + func test_node_mod_incorrectSign() { + // SMALL % BIG = SMALL + // We need to satisfy: BIG * 0 + result = SMALL -> result = SMALL + // The same, but on the standard Swift.Int to prove the point: + XCTAssertEqual(-1 % 123, -1) + XCTAssertEqual(-1 % -123, -1) + // In general the 'reminder' follows the 'lhs' sign (round toward 0). + // Except for the case where 'lhs' is negative and 'reminder' is 0. + + var lhs = BigInt("-1")! + var rhs = BigInt("18446744073709551615")! + XCTAssertEqual(lhs % rhs, lhs) + + // Also fails if 'rhs' is negative + lhs = BigInt("-7730941133")! + rhs = BigInt("-18446744073709551615")! + XCTAssertEqual(lhs % rhs, lhs) + } + + // From my observations all of the `xor` tests are failing. + func test_node_xor() { + var lhs = BigInt("0")! + var rhs = BigInt("1")! + var expected = BigInt("1")! + XCTAssertEqual(lhs ^ rhs, expected) + XCTAssertEqual(0 ^ 1, 1) // Proof + + lhs = BigInt("0")! + rhs = BigInt("-1")! + expected = BigInt("-1")! + XCTAssertEqual(lhs ^ rhs, expected) + XCTAssertEqual(0 ^ -1, -1) // Proof + } + + func test_xor_truthTable() { + let lhsWord : UInt8 = 0b1100 + let rhsWord : UInt8 = 0b1010 + + let lhs = BigInt(lhsWord) + let rhs = BigInt(rhsWord) + + let expected = BigInt(lhsWord ^ rhsWord) + XCTAssertEqual(lhs ^ rhs, expected) + XCTAssertEqual(rhs ^ lhs, expected) + } + + func test_binarySub() { + // https://www.wolframalpha.com/input?i=-922337203685477587+-+%28-9223372036854775808%29 + let lhs = BigInt("-922337203685477587")! + let rhs = BigInt("-9223372036854775808")! + let expected = BigInt("8301034833169298221")! + XCTAssertEqual(lhs - rhs, expected) + // The same on Swift.Int: + XCTAssertEqual(-922337203685477587 - (-9223372036854775808), 8301034833169298221) + } + + func test_binarySub_2() { + typealias Word = UInt + + let intMax = Int.max + let intMaxAsWord = Word(intMax.magnitude) + + // intMax - (-(Word.max - intMaxAsWord)) = + // intMax - (-Word.max + intMaxAsWord) = + // intMax + Word.max - intMaxAsWord = + // Word.max + let max = BigInt(intMax) + let value = -BigInt((Word.max - intMaxAsWord)) + let expected = BigInt(Word.max) + XCTAssertEqual(max - value, expected) + } + func testHashable() { let foo = BigInt("1234567890123456789012345678901234567890")! let bar = BigInt("1234567890123456789112345678901234567890")! @@ -267,7 +506,7 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(dict[foo]!, "Hello") XCTAssertEqual(dict[bar]!, "World") } - + func testClampingConversion() { XCTAssertEqual(BigInt(clamping: UInt64.max), BigInt(UInt64(18446744073709551615))) } @@ -280,6 +519,33 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(bar, BigInt(-1)) } + // MARK: - Testing logical functions + + func testLogical() { + let a = BigInt("7FFF555512340000", radix: 16)! + let b = BigInt("0000ABCD9876FFFF", radix: 16)! + + let aAndb = String(a & b, radix: 16) + let aOrb = String(a | b, radix: 16) + let aXorb = String(a ^ b, radix: 16) + let notb = String(~b, radix: 16) + + let shiftLeft1 = String(a << 16, radix:16) + let shiftLeft2 = String(a << -3, radix:16) + let shiftRight1 = String(a >> 1000, radix:16) + let shiftRight2 = String(a >> -7, radix:16) + + print("a & b = 0x\(aAndb)"); XCTAssertEqual(aAndb, "14510340000") + print("a | b = 0x\(aOrb)"); XCTAssertEqual(aOrb, "7fffffdd9a76ffff") + print("a ^ b = 0x\(aXorb)"); XCTAssertEqual(aXorb, "7ffffe988a42ffff") + print("~b = 0x\(notb)"); XCTAssertEqual(notb, "-abcd98770000") + + print("a << 16 = \(shiftLeft1)"); XCTAssertEqual(shiftLeft1, "7fff5555123400000000") + print("a << -3 = \(shiftLeft2)"); XCTAssertEqual(shiftLeft2, "fffeaaaa2468000") + print("a >> 1000 = \(shiftRight1)"); XCTAssertEqual(shiftRight1, "0") + print("a >> -7 = \(shiftRight2)"); XCTAssertEqual(shiftRight2, "3fffaaaa891a000000") + } + // MARK: - Converting to/from textual representations func testCodable() throws { @@ -355,7 +621,7 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(BigInt(Int64.max) + 0, BigInt("9223372036854775807")) XCTAssertEqual(BigInt(Int64.max) + 1, BigInt("9223372036854775808")) XCTAssertEqual(BigInt(Int64.max) + 2, BigInt("9223372036854775809")) - + XCTAssertEqual(-(BigInt(1) << 1023), BigInt(Self.descriptionInt1024Min)) XCTAssertEqual(+(BigInt(1) << 1023) - 1, BigInt(Self.descriptionInt1024Max)) }