From 96d41bcf9f138ec2768d27b880525374ac5ee8bf Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 17 Dec 2019 11:49:05 -0500 Subject: [PATCH 01/92] Initial commit of BigInt --- Sources/BigInt/BigInt.swift | 737 ++++++++++++++++++++++++++++++++++++ 1 file changed, 737 insertions(+) create mode 100644 Sources/BigInt/BigInt.swift diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift new file mode 100644 index 00000000..b650ebd1 --- /dev/null +++ b/Sources/BigInt/BigInt.swift @@ -0,0 +1,737 @@ +// +// BigInt.swift +// lispish +// +// Created by Robert Thompson on 2/4/19. +// + +#if os(Linux) +import Glibc +#else +import Darwin +#endif + +fileprivate let digitsAndDash = Set("-0123456789") + +public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringConvertible, LosslessStringConvertible, Hashable { + public private(set) var words: Array + + public init?(_ description: String) { + guard description.allSatisfy({ digitsAndDash.contains($0) }) else { return nil } + var result: BigInt = 0 + var description = description + let isNegative: Bool + if description.first == "-" { + guard description.count > 1 else { return nil } + + isNegative = true + description.removeFirst() + } else { + isNegative = false + } + + for (i, char) in description.reversed().enumerated() { + guard let digitInt = Int(String(char)) else { return nil } + result += BigInt(digitInt) * pow(10, BigInt(i)) + } + + if isNegative { + result = -result + } + + words = result.words + } + + public init?(exactly source: T) where T : BinaryInteger { + self.init(source) + } + + public init(bitPattern source: T) where T : BinaryInteger { + words = Words(source.words) + } + + public var magnitude: BigInt { + if _isNegative { + return -self + } + return self + } + + public static func <<= (lhs: inout BigInt, rhs: RHS) where RHS : BinaryInteger { + if rhs.signum() < 0 { + lhs >>= rhs.magnitude + return + } + + let wordLength = UInt.bitWidth + let isNegative = lhs._isNegative + + let (fullWords, remainder) = rhs.quotientAndRemainder(dividingBy: RHS(wordLength)) + lhs.words = Words(repeating: 0, count: Int(fullWords)) + lhs.words + [isNegative ? UInt.max : 0] + + if remainder > 0 { + var value: UInt = 0 + for i in 0..> (wordLength - Int(remainder)) + lhs.words[i] <<= Int(remainder) + if i > 0 { + lhs.words[i] &= UInt.max << Int(remainder) + lhs.words[i] |= value + } + + value = temp + } + + if isNegative { + lhs.words[lhs.words.count - 1] |= (UInt.max << Int(remainder)) + } + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + public static func >>= (lhs: inout BigInt, rhs: RHS) where RHS : BinaryInteger { + if rhs.signum() < 0 { + lhs <<= rhs.magnitude + return + } + + let wordLength = UInt.bitWidth + let isNegative = lhs._isNegative + + let (fullWords, remainder) = rhs.quotientAndRemainder(dividingBy: RHS(wordLength)) + if fullWords < lhs.words.count { + lhs.words.removeFirst(Int(fullWords)) + } else { + lhs = isNegative ? -1 : 0 + return + } + + if remainder > 0 { + let mask = ~(UInt.max << remainder) + var value: UInt = 0 + for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { + let temp = lhs.words[i] & mask + lhs.words[i] >>= remainder + lhs.words[i] |= (value << (UInt.bitWidth - Int(remainder))) + value = temp + } + } + + if isNegative { + lhs.words[lhs.words.count - 1] |= (UInt.max << (UInt.bitWidth - Int(remainder))) + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + @inlinable + public func quotientAndRemainder(dividingBy rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { + return BigInt._div(lhs: self, rhs: rhs) + } + + @inlinable + public func signum() -> BigInt { + return _isNegative ? -1 : 1 + } + + @inlinable + public static prefix func ~ (x: BigInt) -> BigInt { + let newWords = x.words.map { ~$0 } + return BigInt(words: Words(newWords)) + } + + @inlinable + public static prefix func - (x: BigInt) -> BigInt { + var newWords = x.words.map { ~$0 } + let carry: UInt = 1 + for i in 0.. Int.max + } + + public init(_ source: T) where T : BinaryFloatingPoint { + let isNegative = source < 0.0 + var float = isNegative ? -source : source + var words = Words() + while float > 0.0 { + let digit = UInt(remainder(float, T(UInt.max) + 1.0)) + words.append(digit) + + float = floor(float / (T(UInt.max) + 1.0)) + } + + self.words = words + + if isNegative { + self = -self + } + } + + public init(_ source: T) where T : BinaryInteger { + if source >= 0 && source < BigInt._digits.count { + self = BigInt._digits[Int(source)] + } else { + words = Words(source.words) + if source > Int.max { + words.append(0) + } + } + } + + public init(clamping source: T) where T : BinaryInteger { + self.init(source) + } + + public init(truncatingIfNeeded source: T) where T : BinaryInteger { + words = Words(source.words) + } + + @inlinable + public static func - (lhs: BigInt, rhs: BigInt) -> BigInt { + var result = lhs + result -= rhs + return result + } + + public init?(exactly source: T) where T : BinaryFloatingPoint { + if ceil(source) != source { return nil } + self.init(source) + } + + @usableFromInline + internal init(words: Words) { + self.words = words + } + + public static func += (lhs: inout BigInt, rhs: BigInt) { + if lhs.words.count == 1 && rhs.words.count == 1 { + let lhsWord = lhs.words[0] + let rhsWord = rhs.words[0] + + let (result, isOverflow) = lhsWord.addingReportingOverflow(rhsWord) + + if !isOverflow { + lhs.words[0] = result + return + } + let knownNegativeResult = lhsWord > Int.max && rhsWord > Int.max + + if (lhsWord > Int.max || rhsWord > Int.max) && !knownNegativeResult { + // positive + negative is always smaller, so overflow is a red herring + lhs.words[0] = result + return + } + } + + var isOverflow = false + + var rhsWords = rhs.words + + lhs.words.append(lhs._isNegative ? UInt.max : 0) + rhsWords.append(rhs._isNegative ? UInt.max : 0) + + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + var temp: UInt = 0 + for index in 0..= 0 && value < BigInt._digits.count { + self = BigInt._digits[value] + } else { + words = [UInt(bitPattern: value)] + } + } + + public static var isSigned: Bool { return true } + + public var bitWidth: Int { return words.count * MemoryLayout.size * 8 } + + public var trailingZeroBitCount: Int { return words.first?.trailingZeroBitCount ?? 0 } + + @inlinable + public static func < (lhs: BigInt, rhs: BigInt) -> Bool { + let lhsNegative = lhs._isNegative + let rhsNegative = rhs._isNegative + + if lhsNegative && !rhsNegative { return true } + if rhsNegative && !lhsNegative { return false } + + if (lhsNegative && rhsNegative) || (!lhsNegative && !rhsNegative) { + if lhs.words.count > rhs.words.count { + return lhsNegative ? true : false + } + if lhs.words.count < rhs.words.count { + return lhsNegative ? false : true + } + + for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { + if lhs.words[i] > rhs.words[i] { return false } + } + } + + return true + } + + @inlinable + public static func ==(lhs: BigInt, rhs: BigInt) -> Bool { + return lhs.words == rhs.words + } + + @inlinable + public static func !=(lhs: BigInt, rhs: BigInt) -> Bool { + return !(lhs == rhs) + } + + @inlinable + public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { + let (result, _) = _div(lhs: lhs, rhs: rhs) + return result + } + + private static func findQhat(high: UInt, low: UInt.Magnitude, divisor: UInt, nextVdigit: UInt, nextUdigit: UInt) -> UInt { + var (qhat, rhat) = divisor.dividingFullWidth((high, low)) + + if high >= divisor {// This means qhat >= b + qhat = UInt.max + } + + let (tempHigh, tempLow) = qhat.multipliedFullWidth(by: nextVdigit) + var (rtempHigh, rtempLow) = rhat.multipliedFullWidth(by: UInt.max) + var overflow = false + (rtempLow, overflow) = rtempLow.addingReportingOverflow(1) + if overflow { + rtempHigh += 1 + } + + (rtempLow, overflow) = rtempLow.addingReportingOverflow(nextUdigit) + if overflow { + rtempHigh += 1 + } + + while true { + if (tempHigh > rtempHigh) || ((tempHigh == rtempHigh) && (tempLow > rtempLow)) { + qhat -= 1 + (rhat, overflow) = rhat.addingReportingOverflow(divisor) + if !overflow { + continue + } else { + break + } + } else { + break + } + } + + return qhat + } + + @usableFromInline + internal static func _div(lhs: BigInt, rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { + precondition(rhs != _digits[0], "Division by zero error!") + + 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(words: [UInt(bitPattern: quot)]), BigInt(words: [UInt(bitPattern: rem)])) + } + + let lhsIsNeg = lhs._isNegative + let rhsIsNeg = rhs._isNegative + + var lhsWords = lhsIsNeg ? (-lhs).words : lhs.words + var rhsWords = rhsIsNeg ? (-rhs).words : rhs.words + + while rhsWords[rhsWords.endIndex - 1] == 0 && rhsWords.count > 2 { + rhsWords.removeLast() + } + + if rhsWords.count > lhsWords.count { return (0, lhs) } + + if rhsWords.count == 1 { + rhsWords.append(0) + } + + func normalize(x: UInt) -> UInt { + #if arch(arm64) || arch(x86_64) + if x == 0 { + return 64 + } + var x = UInt64(x) + var n: UInt = 0 + if x <= 0x00000000FFFFFFFF { + n += 32 + x <<= 32 + } + if x <= 0x0000FFFFFFFFFFFF { + n += 16 + x <<= 16 + } + if x <= 0x00FFFFFFFFFFFFFF { + n += 8 + x <<= 8 + } + if x <= 0x0FFFFFFFFFFFFFFF { + n += 4 + x <<= 4 + } + if x <= 0x3FFFFFFFFFFFFFFF { + n += 2 + x <<= 2 + } + if x <= 0x7FFFFFFFFFFFFFFF { + n += 1 + } + + return n + #else + if x == 0 { + return 32 + } + + var x = x + var n: UInt = 0 + if x <= 0x0000FFFF { + n += 16 + x <<= 16 + } + if x <= 0x00FFFFFF { + n += 8 + x <<= 8 + } + if x <= 0x0FFFFFFF { + n += 4 + x <<= 4 + } + if x <= 0x3FFFFFFF { + n += 2 + x <<= 2 + } + if x <= 0x7FFFFFFF { + n += 1 + } + + return n + #endif + } + + if lhsWords.count < rhsWords.count { + for _ in 0..<(rhsWords.count - lhsWords.count) { + lhsWords.append(0) + } + } + + let m = lhsWords.count + let n = rhsWords.count + + let bitWidth = UInt(UInt.bitWidth) + + let s = normalize(x: rhsWords[n - 1]) + guard let rn = calloc(n, MemoryLayout.stride)?.assumingMemoryBound(to: UInt.self) else { fatalError("OOM") } + defer { free(rn) } + for i in (1...(n - 1)).reversed() { + rn[i] = (rhsWords[i] << s) | (rhsWords[i - 1] >> (bitWidth - s)) + } + rn[0] <<= s + + guard let ln = calloc(m + n + 1, MemoryLayout.stride)?.assumingMemoryBound(to: UInt.self) else { fatalError("OOM") } + defer { free(ln) } + ln[m] = lhsWords[m - 1] >> (bitWidth - s) + for i in (1...(m - 1)).reversed() { + ln[i] = (lhsWords[i] << s) | (lhsWords[i - 1] >> (bitWidth - s)) + } + ln[0] <<= s + + let resultSize = m + 1 + var quot = Words(repeating: 0, count: resultSize) + + for j in (0...m).reversed() { + let qhat = findQhat(high: ln[j + n], low: UInt.Magnitude(ln[j + n - 1]), divisor: rn[n - 1], nextVdigit: rn[n - 2], nextUdigit: ln[j + n - 2]) + + var carry: UInt = 0 + var isOverflow = false + var borrow: UInt = 0 + var underflow = false + for i in 0.. 0 { + (ln[i + j], underflow) = ln[i + j].subtractingReportingOverflow(borrow) + borrow = underflow ? 1 : 0 + } + + var (pHigh, pLow) = qhat.multipliedFullWidth(by: rn[i]) + (pLow, isOverflow) = pLow.addingReportingOverflow(carry) + if isOverflow { + pHigh += 1 + } + + (ln[i + j], underflow) = ln[i + j].subtractingReportingOverflow(pLow) + if underflow { + borrow += 1 + } + + carry = pHigh + } + + (ln[j + n], underflow) = ln[j + n].subtractingReportingOverflow(carry + borrow) + + if underflow { + let newQhat = qhat - 1 + + carry = 0 + var total: UInt = 0 + for i in 0..> s) | ln[i + 1] << (bitWidth - s) + } + rem[n - 1] = ln[n - 1] >> s + + BigInt._dropExcessWords(words: ") + BigInt._dropExcessWords(words: &rem) + + return (BigInt(words: quot), BigInt(words: rem)) + } + + @inlinable + public static func /= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs / rhs + } + + @inlinable + public static func % (lhs: BigInt, rhs: BigInt) -> BigInt { + let (_, result) = _div(lhs: lhs, rhs: rhs) + + return result + } + + @inlinable + public static func %= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs % rhs + } + + public static func * (lhs: BigInt, rhs: BigInt) -> BigInt { + let lhsIsNeg = lhs.words[lhs.words.endIndex - 1] > Int.max + let rhsIsNeg = rhs.words[rhs.words.endIndex - 1] > Int.max + + let lhsWords = lhsIsNeg ? (-lhs).words : lhs.words + let rhsWords = rhsIsNeg ? (-rhs).words : rhs.words + + let count = lhsWords.count + rhsWords.count + 1 + var newWords = Words(repeating: 0, count: count) + + for i in 0.. BigInt { + var result = lhs + result += rhs + return result + } + + @inlinable + public static func -= (lhs: inout BigInt, rhs: BigInt) { + lhs += -rhs + } + + private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) { + let lhsIsNeg = (lhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 + let rhsIsNeg = (rhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 + + if lhsWords.count > rhsWords.count { + for _ in 0..<(lhsWords.count - rhsWords.count) { + rhsIsNeg ? rhsWords.append(UInt.max) : rhsWords.append(0) + } + } else if rhsWords.count > lhsWords.count { + for _ in 0..<(rhsWords.count - lhsWords.count) { + lhsIsNeg ? lhsWords.append(UInt.max) : lhsWords.append(0) + } + } + } + + private static func _dropExcessWords(words: inout Words) { + while words.count > 1 && words[words.endIndex - 1] == 0 { + if words[words.endIndex - 2] <= Int.max { + words.removeLast() + } else { + break + } + } + + while words.count > 1 && words[words.endIndex - 1] == UInt.max { + if words[words.endIndex - 2] > Int.max { + words.removeLast() + } else { + break + } + } + } + + public var description: String { + var result = "" + + if words.count == 1 { + return Int64(bitPattern: UInt64(words[0])).description + } else { + var next = abs(self) + while next != 0 { + let digit: BigInt + (next, digit) = BigInt._div(lhs: next, rhs: 10) + result += "\(digit.words[0])" + } + } + + return (self < 0 ? "-" : "") + String(result.reversed()) + } + + private static let _digits = { + (0...10).map { BigInt(words: [UInt(bitPattern: $0)]) } + }() +} + +// inspired by https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms +public func pow(_ lhs: BigInt, _ rhs: BigInt) -> BigInt { + let bits_of_n = { + (n: BigInt) -> Array in + var bits: Array = [] + var n = n + while n != 0 { + bits.append(Int(n % 2)) + n /= 2 + } + + return bits + } + + var r: BigInt = 1 + for bit in bits_of_n(rhs).reversed() { + r *= r + if bit == 1 { + r *= lhs + } + } + + return r +} From bc2f9fb7c6741b502f962a08da4d0f4a10323108 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 17 Dec 2019 13:17:24 -0500 Subject: [PATCH 02/92] Use NumericShims for math functions --- Package.swift | 5 +- Sources/BigInt/BigInt.swift | 33 ++++----- Sources/NumericsShims/include/NumericsShims.h | 36 +++++++++ Tests/BigIntTests/BigIntTests.swift | 73 +++++++++++++++++++ 4 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 Tests/BigIntTests/BigIntTests.swift diff --git a/Package.swift b/Package.swift index f91f4414..a085c78c 100644 --- a/Package.swift +++ b/Package.swift @@ -18,16 +18,19 @@ let package = Package( .library(name: "Complex", targets: ["Complex"]), .library(name: "Numerics", targets: ["Numerics"]), .library(name: "Real", targets: ["Real"]), + .library(name: "BigInt", targets: ["BigInt"]), ], dependencies: [ ], targets: [ .target(name: "Complex", dependencies: ["Real"]), - .target(name: "Numerics", dependencies: ["Complex", "Real"]), + .target(name: "Numerics", dependencies: ["Complex", "Real", "BigInt"]), .target(name: "NumericsShims", dependencies: []), .target(name: "Real", dependencies: ["NumericsShims"]), + .target(name: "BigInt", dependencies: ["NumericsShims"]), .testTarget(name: "ComplexTests", dependencies: ["Complex", "NumericsShims"]), .testTarget(name: "RealTests", dependencies: ["Real"]), + .testTarget(name: "BigIntTests", dependencies: ["BigInt", "NumericsShims"]), ] ) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index b650ebd1..80222e12 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -1,19 +1,16 @@ // // BigInt.swift -// lispish +// swift-numerics // // Created by Robert Thompson on 2/4/19. // -#if os(Linux) -import Glibc -#else -import Darwin -#endif +import NumericsShims fileprivate let digitsAndDash = Set("-0123456789") public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringConvertible, LosslessStringConvertible, Hashable { + public private(set) var words: Array public init?(_ description: String) { @@ -163,17 +160,17 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC public init(_ source: T) where T : BinaryFloatingPoint { let isNegative = source < 0.0 - var float = isNegative ? -source : source + var float = Double(isNegative ? -source : source) var words = Words() while float > 0.0 { - let digit = UInt(remainder(float, T(UInt.max) + 1.0)) + let digit = UInt(libm_remainder(float, Double(UInt.max) + 1.0)) words.append(digit) - - float = floor(float / (T(UInt.max) + 1.0)) + + float = libm_floor(float / (Double(UInt.max) + 1.0)) } - + self.words = words - + if isNegative { self = -self } @@ -206,7 +203,7 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC } public init?(exactly source: T) where T : BinaryFloatingPoint { - if ceil(source) != source { return nil } + if libm_ceil(Double(source)) != Double(source) { return nil } self.init(source) } @@ -452,15 +449,17 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC let bitWidth = UInt(UInt.bitWidth) let s = normalize(x: rhsWords[n - 1]) - guard let rn = calloc(n, MemoryLayout.stride)?.assumingMemoryBound(to: UInt.self) else { fatalError("OOM") } - defer { free(rn) } + let rn = UnsafeMutablePointer.allocate(capacity: n) + rn.initialize(repeating: 0, count: n) + defer { rn.deallocate() } for i in (1...(n - 1)).reversed() { rn[i] = (rhsWords[i] << s) | (rhsWords[i - 1] >> (bitWidth - s)) } rn[0] <<= s - guard let ln = calloc(m + n + 1, MemoryLayout.stride)?.assumingMemoryBound(to: UInt.self) else { fatalError("OOM") } - defer { free(ln) } + let ln = UnsafeMutablePointer.allocate(capacity: m + n + 1) + ln.initialize(repeating: 0, count: m + n + 1) + defer { ln.deallocate() } ln[m] = lhsWords[m - 1] >> (bitWidth - s) for i in (1...(m - 1)).reversed() { ln[i] = (lhsWords[i] << s) | (lhsWords[i - 1] >> (bitWidth - s)) diff --git a/Sources/NumericsShims/include/NumericsShims.h b/Sources/NumericsShims/include/NumericsShims.h index 4e1b40bf..3f3c1d27 100644 --- a/Sources/NumericsShims/include/NumericsShims.h +++ b/Sources/NumericsShims/include/NumericsShims.h @@ -143,6 +143,18 @@ HEADER_SHIM float libm_log10f(float x) { return __builtin_log10f(x); } +HEADER_SHIM float libm_ceilf(float x) { + return __builtin_ceilf(x); +} + +HEADER_SHIM float libm_floorf(float x) { + return __builtin_floorf(x); +} + +HEADER_SHIM float libm_remainderf(float x, float y) { + return __builtin_remainderf(x, y); +} + #if !defined _WIN32 HEADER_SHIM float libm_lgammaf(float x, int *signp) { extern float lgammaf_r(float, int *); @@ -263,6 +275,18 @@ HEADER_SHIM double libm_log10(double x) { return __builtin_log10(x); } +HEADER_SHIM double libm_ceil(double x) { + return __builtin_ceil(x); +} + +HEADER_SHIM double libm_floor(double x) { + return __builtin_floor(x); +} + +HEADER_SHIM double libm_remainder(double x, double y) { + return __builtin_remainder(x, y); +} + #if !defined _WIN32 HEADER_SHIM double libm_lgamma(double x, int *signp) { extern double lgamma_r(double, int *); @@ -380,6 +404,18 @@ HEADER_SHIM long double libm_lgammal(long double x, int *signp) { extern long double lgammal_r(long double, int *); return lgammal_r(x, signp); } + +HEADER_SHIM long double libm_ceill(long double x) { + return __builtin_ceill(x); +} + +HEADER_SHIM long double libm_floorl(long double x) { + return __builtin_floorl(x); +} + +HEADER_SHIM long double libm_remainderl(long double x, long double y) { + return __builtin_remainderl(x, y); +} #endif // MARK: - shims to import C complex operations for timing purposes diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift new file mode 100644 index 00000000..cd358f87 --- /dev/null +++ b/Tests/BigIntTests/BigIntTests.swift @@ -0,0 +1,73 @@ +import XCTest +import BigInt + +func fac(_ n: BigInt) -> BigInt { + var result: BigInt = 1 + var count = n + while count >= 1 { + result *= count + count -= 1 + } + + return result +} + +final class BigIntTests: XCTestCase { + func testExample() throws { + let bar = BigInt(exactly: -100) + XCTAssertNotNil(bar) + XCTAssert(bar! < 0) + + XCTAssert(-(bar!) > 0) + XCTAssertEqual(-(bar!), BigInt(100)) + + XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, BigInt("1234567890123456789012345678901234567890")!) + } + + func testFloatingConversion() { + let bar = BigInt(3.14159) + XCTAssertEqual(bar, BigInt(3)) + let foo = BigInt(exactly: 3.14159) + XCTAssertNil(foo) + + let baz = BigInt(exactly: 2.4e39) + XCTAssertNotNil(baz) + let equal = (baz ?? 0) / BigInt(1e38) == BigInt(24) + XCTAssertEqual(equal, true) + } + + func testUIntConversion() { + let foo = BigInt(UInt.max) + XCTAssertNotEqual(foo, BigInt(-1)) + + let bar = BigInt(bitPattern: UInt.max) + XCTAssertEqual(bar, BigInt(-1)) + } + + func testComparison() { + let foo = BigInt(-10) + let bar = BigInt(-20) + + XCTAssert(foo > bar) + XCTAssert(bar < foo) + XCTAssert(foo == BigInt(-10)) + + let baz = pow(foo, -bar) + XCTAssertEqual(baz, BigInt("100000000000000000000")!) + } + + func testMath() { + let foo = pow(BigInt(10), 20) + let bar = BigInt("1234567890123456789012345678901234567890")! + + let baz = foo + bar + + XCTAssertEqual(baz, BigInt("1234567890123456789112345678901234567890")!) + + let fooz = foo >> BigInt(10) + XCTAssertEqual(fooz, foo / 1024) + + let barz = BigInt(1) << 64 + XCTAssertEqual(barz, BigInt(UInt.max) + 1) + } +} From cb56525312ad1f541ef3df8ebfd5b6014041896c Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 17 Dec 2019 15:29:19 -0500 Subject: [PATCH 03/92] Address PR feedback Eliminate unnecessary math function calls, and also handle floating point edge cases better. --- Sources/BigInt/BigInt.swift | 35 +++++++++++------- Sources/NumericsShims/include/NumericsShims.h | 36 ------------------- Tests/BigIntTests/BigIntTests.swift | 3 ++ 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 80222e12..03707146 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -4,9 +4,6 @@ // // Created by Robert Thompson on 2/4/19. // - -import NumericsShims - fileprivate let digitsAndDash = Set("-0123456789") public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringConvertible, LosslessStringConvertible, Hashable { @@ -159,18 +156,28 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC } public init(_ source: T) where T : BinaryFloatingPoint { + precondition( + !(source.isNaN || source.isInfinite), + "\(type(of: source)) value cannot be converted to BigInt because it is either infinite or NaN" + ) + let isNegative = source < 0.0 - var float = Double(isNegative ? -source : source) - var words = Words() - while float > 0.0 { - let digit = UInt(libm_remainder(float, Double(UInt.max) + 1.0)) - words.append(digit) + var float = isNegative ? -source : source + + if let _ = UInt(exactly: T.greatestFiniteMagnitude) { + self.words = [UInt(float)] + } else { + var words = Words() + let radix = T(sign: .plus, exponent: T.Exponent(UInt.bitWidth), significand: 1) + repeat { + let digit = UInt(float.truncatingRemainder(dividingBy: radix)) + words.append(digit) + float = (float / radix).rounded(.towardZero) + } while float != 0 - float = libm_floor(float / (Double(UInt.max) + 1.0)) + self.words = words } - self.words = words - if isNegative { self = -self } @@ -203,7 +210,11 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC } public init?(exactly source: T) where T : BinaryFloatingPoint { - if libm_ceil(Double(source)) != Double(source) { return nil } + if (source.isNaN || source.isInfinite) || + (source.rounded(.towardZero) != source) { + return nil + } + self.init(source) } diff --git a/Sources/NumericsShims/include/NumericsShims.h b/Sources/NumericsShims/include/NumericsShims.h index 3f3c1d27..4e1b40bf 100644 --- a/Sources/NumericsShims/include/NumericsShims.h +++ b/Sources/NumericsShims/include/NumericsShims.h @@ -143,18 +143,6 @@ HEADER_SHIM float libm_log10f(float x) { return __builtin_log10f(x); } -HEADER_SHIM float libm_ceilf(float x) { - return __builtin_ceilf(x); -} - -HEADER_SHIM float libm_floorf(float x) { - return __builtin_floorf(x); -} - -HEADER_SHIM float libm_remainderf(float x, float y) { - return __builtin_remainderf(x, y); -} - #if !defined _WIN32 HEADER_SHIM float libm_lgammaf(float x, int *signp) { extern float lgammaf_r(float, int *); @@ -275,18 +263,6 @@ HEADER_SHIM double libm_log10(double x) { return __builtin_log10(x); } -HEADER_SHIM double libm_ceil(double x) { - return __builtin_ceil(x); -} - -HEADER_SHIM double libm_floor(double x) { - return __builtin_floor(x); -} - -HEADER_SHIM double libm_remainder(double x, double y) { - return __builtin_remainder(x, y); -} - #if !defined _WIN32 HEADER_SHIM double libm_lgamma(double x, int *signp) { extern double lgamma_r(double, int *); @@ -404,18 +380,6 @@ HEADER_SHIM long double libm_lgammal(long double x, int *signp) { extern long double lgammal_r(long double, int *); return lgammal_r(x, signp); } - -HEADER_SHIM long double libm_ceill(long double x) { - return __builtin_ceill(x); -} - -HEADER_SHIM long double libm_floorl(long double x) { - return __builtin_floorl(x); -} - -HEADER_SHIM long double libm_remainderl(long double x, long double y) { - return __builtin_remainderl(x, y); -} #endif // MARK: - shims to import C complex operations for timing purposes diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index cd358f87..7e5de1ce 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -34,6 +34,9 @@ final class BigIntTests: XCTestCase { XCTAssertNotNil(baz) let equal = (baz ?? 0) / BigInt(1e38) == BigInt(24) XCTAssertEqual(equal, true) + + let infinite = BigInt(exactly: Double.infinity) + XCTAssertNil(infinite) } func testUIntConversion() { From ea2b6f8debd7614012f44a646d63acfbf6341732 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 17 Dec 2019 15:32:43 -0500 Subject: [PATCH 04/92] BigInt no longer depends on NumericsShims --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index a085c78c..b3d6ffee 100644 --- a/Package.swift +++ b/Package.swift @@ -27,10 +27,10 @@ let package = Package( .target(name: "Numerics", dependencies: ["Complex", "Real", "BigInt"]), .target(name: "NumericsShims", dependencies: []), .target(name: "Real", dependencies: ["NumericsShims"]), - .target(name: "BigInt", dependencies: ["NumericsShims"]), + .target(name: "BigInt", dependencies: []), .testTarget(name: "ComplexTests", dependencies: ["Complex", "NumericsShims"]), .testTarget(name: "RealTests", dependencies: ["Real"]), - .testTarget(name: "BigIntTests", dependencies: ["BigInt", "NumericsShims"]), + .testTarget(name: "BigIntTests", dependencies: ["BigInt"]), ] ) From 524dc69ae1f45e3b5565942cfe7dad756fe5850e Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 30 Dec 2019 10:41:40 -0500 Subject: [PATCH 05/92] slightly refactor LosslessStringConvertible initializer as per PR feedback --- Sources/BigInt/BigInt.swift | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 03707146..cc22bb56 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -4,29 +4,32 @@ // // Created by Robert Thompson on 2/4/19. // -fileprivate let digitsAndDash = Set("-0123456789") public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringConvertible, LosslessStringConvertible, Hashable { public private(set) var words: Array public init?(_ description: String) { - guard description.allSatisfy({ digitsAndDash.contains($0) }) else { return nil } - var result: BigInt = 0 + guard let firstCharacter = description.first else { return nil } + var description = description let isNegative: Bool - if description.first == "-" { - guard description.count > 1 else { return nil } - - isNegative = true - description.removeFirst() - } else { - isNegative = false + if description.count > 1 { + if firstCharacter == "-" { + isNegative = true + description.removeFirst() + } else if firstCharacter == "+" { + description.removeFirst() + } } + let digitsAndDash = Set("0123456789") + guard description.allSatisfy({ digitsAndDash.contains($0) }) else { return nil } + + var result: BigInt = 0 for (i, char) in description.reversed().enumerated() { - guard let digitInt = Int(String(char)) else { return nil } - result += BigInt(digitInt) * pow(10, BigInt(i)) + // We're ok to force unwrap here because of the guard above. + result += BigInt(char.wholeNumberValue!) * pow(10, BigInt(i)) } if isNegative { From ac50a90a6068e47134ffc5ddeee65c04a415a305 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 30 Dec 2019 10:44:48 -0500 Subject: [PATCH 06/92] whoops --- Sources/BigInt/BigInt.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index cc22bb56..a54a376f 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -13,7 +13,7 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC guard let firstCharacter = description.first else { return nil } var description = description - let isNegative: Bool + var isNegative: Bool = false if description.count > 1 { if firstCharacter == "-" { isNegative = true From eceae6d7bde01d3f33931c0b4db7511e1e2129a9 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 30 Dec 2019 15:12:20 -0500 Subject: [PATCH 07/92] Update Sources/BigInt/BigInt.swift As per benrimmington's excellent suggestion Co-Authored-By: Ben Rimmington --- Sources/BigInt/BigInt.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index a54a376f..b7cc02e5 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -23,7 +23,7 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC } } - let digitsAndDash = Set("0123456789") + let digits = Set("0123456789") guard description.allSatisfy({ digitsAndDash.contains($0) }) else { return nil } var result: BigInt = 0 From b46dcf96c61cf88e641f4046c51ec7ae7fa9fded Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 30 Dec 2019 15:13:26 -0500 Subject: [PATCH 08/92] oops, change both occurrences of digitsAndDash --- Sources/BigInt/BigInt.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index b7cc02e5..48b8fea5 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -24,7 +24,7 @@ public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringC } let digits = Set("0123456789") - guard description.allSatisfy({ digitsAndDash.contains($0) }) else { return nil } + guard description.allSatisfy({ digits.contains($0) }) else { return nil } var result: BigInt = 0 for (i, char) in description.reversed().enumerated() { From 821fcbd6a4ee7ced7beeaeefac1c1ae367b57a38 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 30 Dec 2019 15:46:18 -0500 Subject: [PATCH 09/92] Update following suggestions from @benrimmington Includes reformatting to use 2-space indentation and the correct file headers, among other simplifications. --- Package.swift | 6 +- Sources/BigInt/BigInt.swift | 1398 ++++++++++++++------------- Tests/BigIntTests/BigIntTests.swift | 157 +-- 3 files changed, 794 insertions(+), 767 deletions(-) diff --git a/Package.swift b/Package.swift index b3d6ffee..19bec320 100644 --- a/Package.swift +++ b/Package.swift @@ -15,22 +15,22 @@ import PackageDescription let package = Package( name: "swift-numerics", products: [ + .library(name: "BigInt", targets: ["BigInt"]), .library(name: "Complex", targets: ["Complex"]), .library(name: "Numerics", targets: ["Numerics"]), .library(name: "Real", targets: ["Real"]), - .library(name: "BigInt", targets: ["BigInt"]), ], dependencies: [ ], targets: [ + .target(name: "BigInt", dependencies: []), .target(name: "Complex", dependencies: ["Real"]), .target(name: "Numerics", dependencies: ["Complex", "Real", "BigInt"]), .target(name: "NumericsShims", dependencies: []), .target(name: "Real", dependencies: ["NumericsShims"]), - .target(name: "BigInt", dependencies: []), + .testTarget(name: "BigIntTests", dependencies: ["BigInt"]), .testTarget(name: "ComplexTests", dependencies: ["Complex", "NumericsShims"]), .testTarget(name: "RealTests", dependencies: ["Real"]), - .testTarget(name: "BigIntTests", dependencies: ["BigInt"]), ] ) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 48b8fea5..0b3eba62 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -1,750 +1,752 @@ +//===--- BigInt.swift ----------------------------------------*- swift -*-===// // -// BigInt.swift -// swift-numerics +// This source file is part of the Swift Numerics open source project // -// Created by Robert Thompson on 2/4/19. +// Copyright (c) 2019 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 +// +//===----------------------------------------------------------------------===// -public struct BigInt: BinaryInteger, SignedNumeric, SignedInteger, CustomStringConvertible, LosslessStringConvertible, Hashable { - - public private(set) var words: Array - - public init?(_ description: String) { - guard let firstCharacter = description.first else { return nil } - - var description = description - var isNegative: Bool = false - if description.count > 1 { - if firstCharacter == "-" { - isNegative = true - description.removeFirst() - } else if firstCharacter == "+" { - description.removeFirst() - } - } - - let digits = Set("0123456789") - guard description.allSatisfy({ digits.contains($0) }) else { return nil } - - var result: BigInt = 0 - for (i, char) in description.reversed().enumerated() { - // We're ok to force unwrap here because of the guard above. - result += BigInt(char.wholeNumberValue!) * pow(10, BigInt(i)) - } - - if isNegative { - result = -result - } - - words = result.words +public struct BigInt: SignedInteger, LosslessStringConvertible { + public private(set) var words: [UInt] + + public init?(_ description: String) { + var description = description + let isNegative = description.hasPrefix("-") + if isNegative || description.hasPrefix("+") { + _ = description.removeFirst() } - - public init?(exactly source: T) where T : BinaryInteger { - self.init(source) + + let digits = Set("0123456789") + guard description.allSatisfy({ digits.contains($0) }) else { return nil } + + var result: BigInt = 0 + for (i, char) in description.reversed().enumerated() { + // We're ok to force unwrap here because of the guard above. + result += BigInt(char.wholeNumberValue!) * pow(10, BigInt(i)) } - - public init(bitPattern source: T) where T : BinaryInteger { - words = Words(source.words) + + if isNegative { + result = -result } - - public var magnitude: BigInt { - if _isNegative { - return -self - } - return self + + words = result.words + } + + public init?(exactly source: T) where T: BinaryInteger { + self.init(source) + } + + public init(bitPattern source: T) where T: BinaryInteger { + words = Words(source.words) + } + + public var magnitude: BigInt { + if _isNegative { + return -self } - - public static func <<= (lhs: inout BigInt, rhs: RHS) where RHS : BinaryInteger { - if rhs.signum() < 0 { - lhs >>= rhs.magnitude - return - } - - let wordLength = UInt.bitWidth - let isNegative = lhs._isNegative - - let (fullWords, remainder) = rhs.quotientAndRemainder(dividingBy: RHS(wordLength)) - lhs.words = Words(repeating: 0, count: Int(fullWords)) + lhs.words + [isNegative ? UInt.max : 0] - - if remainder > 0 { - var value: UInt = 0 - for i in 0..> (wordLength - Int(remainder)) - lhs.words[i] <<= Int(remainder) - if i > 0 { - lhs.words[i] &= UInt.max << Int(remainder) - lhs.words[i] |= value - } - - value = temp - } - - if isNegative { - lhs.words[lhs.words.count - 1] |= (UInt.max << Int(remainder)) - } - } + return self + } - BigInt._dropExcessWords(words: &lhs.words) + public static func <<= (lhs: inout BigInt, rhs: RHS) where RHS: BinaryInteger { + if rhs.signum() < 0 { + lhs >>= rhs.magnitude + return } - public static func >>= (lhs: inout BigInt, rhs: RHS) where RHS : BinaryInteger { - if rhs.signum() < 0 { - lhs <<= rhs.magnitude - return - } - - let wordLength = UInt.bitWidth - let isNegative = lhs._isNegative + let wordLength = UInt.bitWidth + let isNegative = lhs._isNegative - let (fullWords, remainder) = rhs.quotientAndRemainder(dividingBy: RHS(wordLength)) - if fullWords < lhs.words.count { - lhs.words.removeFirst(Int(fullWords)) - } else { - lhs = isNegative ? -1 : 0 - return - } + let (fullWords, remainder) = rhs.quotientAndRemainder(dividingBy: RHS(wordLength)) + lhs.words = Words(repeating: 0, count: Int(fullWords)) + lhs.words + [isNegative ? UInt.max : 0] - if remainder > 0 { - let mask = ~(UInt.max << remainder) - var value: UInt = 0 - for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { - let temp = lhs.words[i] & mask - lhs.words[i] >>= remainder - lhs.words[i] |= (value << (UInt.bitWidth - Int(remainder))) - value = temp - } + if remainder > 0 { + var value: UInt = 0 + for i in 0 ..< lhs.words.count { + let temp = lhs.words[i] >> (wordLength - Int(remainder)) + lhs.words[i] <<= Int(remainder) + if i > 0 { + lhs.words[i] &= UInt.max << Int(remainder) + lhs.words[i] |= value } - if isNegative { - lhs.words[lhs.words.count - 1] |= (UInt.max << (UInt.bitWidth - Int(remainder))) - } + value = temp + } - BigInt._dropExcessWords(words: &lhs.words) - } - - @inlinable - public func quotientAndRemainder(dividingBy rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { - return BigInt._div(lhs: self, rhs: rhs) - } - - @inlinable - public func signum() -> BigInt { - return _isNegative ? -1 : 1 - } - - @inlinable - public static prefix func ~ (x: BigInt) -> BigInt { - let newWords = x.words.map { ~$0 } - return BigInt(words: Words(newWords)) - } - - @inlinable - public static prefix func - (x: BigInt) -> BigInt { - var newWords = x.words.map { ~$0 } - let carry: UInt = 1 - for i in 0.. Int.max - } - - public init(_ source: T) where T : BinaryFloatingPoint { - precondition( - !(source.isNaN || source.isInfinite), - "\(type(of: source)) value cannot be converted to BigInt because it is either infinite or NaN" - ) - - let isNegative = source < 0.0 - var float = isNegative ? -source : source - - if let _ = UInt(exactly: T.greatestFiniteMagnitude) { - self.words = [UInt(float)] - } else { - var words = Words() - let radix = T(sign: .plus, exponent: T.Exponent(UInt.bitWidth), significand: 1) - repeat { - let digit = UInt(float.truncatingRemainder(dividingBy: radix)) - words.append(digit) - float = (float / radix).rounded(.towardZero) - } while float != 0 - - self.words = words - } + if isNegative { + lhs.words[lhs.words.count - 1] |= (UInt.max << Int(remainder)) + } + } - if isNegative { - self = -self - } + BigInt._dropExcessWords(words: &lhs.words) + } + + public static func >>= (lhs: inout BigInt, rhs: RHS) where RHS: BinaryInteger { + if rhs.signum() < 0 { + lhs <<= rhs.magnitude + return } - - public init(_ source: T) where T : BinaryInteger { - if source >= 0 && source < BigInt._digits.count { - self = BigInt._digits[Int(source)] - } else { - words = Words(source.words) - if source > Int.max { - words.append(0) - } - } + + let wordLength = UInt.bitWidth + let isNegative = lhs._isNegative + + let (fullWords, remainder) = rhs.quotientAndRemainder(dividingBy: RHS(wordLength)) + if fullWords < lhs.words.count { + lhs.words.removeFirst(Int(fullWords)) + } else { + lhs = isNegative ? -1 : 0 + return } - - public init(clamping source: T) where T : BinaryInteger { - self.init(source) - } - - public init(truncatingIfNeeded source: T) where T : BinaryInteger { - words = Words(source.words) - } - - @inlinable - public static func - (lhs: BigInt, rhs: BigInt) -> BigInt { - var result = lhs - result -= rhs - return result - } - - public init?(exactly source: T) where T : BinaryFloatingPoint { - if (source.isNaN || source.isInfinite) || - (source.rounded(.towardZero) != source) { - return nil - } - - self.init(source) - } - - @usableFromInline - internal init(words: Words) { - self.words = words - } - - public static func += (lhs: inout BigInt, rhs: BigInt) { - if lhs.words.count == 1 && rhs.words.count == 1 { - let lhsWord = lhs.words[0] - let rhsWord = rhs.words[0] - - let (result, isOverflow) = lhsWord.addingReportingOverflow(rhsWord) - - if !isOverflow { - lhs.words[0] = result - return - } - let knownNegativeResult = lhsWord > Int.max && rhsWord > Int.max - - if (lhsWord > Int.max || rhsWord > Int.max) && !knownNegativeResult { - // positive + negative is always smaller, so overflow is a red herring - lhs.words[0] = result - return - } - } - - var isOverflow = false - - var rhsWords = rhs.words - - lhs.words.append(lhs._isNegative ? UInt.max : 0) - rhsWords.append(rhs._isNegative ? UInt.max : 0) - - BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) - var temp: UInt = 0 - for index in 0.. 0 { + let mask = ~(UInt.max << remainder) + var value: UInt = 0 + for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { + let temp = lhs.words[i] & mask + lhs.words[i] >>= remainder + lhs.words[i] |= (value << (UInt.bitWidth - Int(remainder))) + value = temp + } + } + + if isNegative { + lhs.words[lhs.words.count - 1] |= (UInt.max << (UInt.bitWidth - Int(remainder))) + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + @inlinable + public func quotientAndRemainder(dividingBy rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { + return BigInt._div(lhs: self, rhs: rhs) + } + + @inlinable + public func signum() -> BigInt { + if _isNegative { + return -1 + } else if self == 0 { + return 0 + } + + return 1 + } + + @inlinable + public static prefix func ~ (x: BigInt) -> BigInt { + let newWords = x.words.map { ~$0 } + return BigInt(words: Words(newWords)) + } + + @inlinable + public static prefix func - (x: BigInt) -> BigInt { + var newWords = x.words.map { ~$0 } + let carry: UInt = 1 + for i in 0 ..< newWords.count { + let isOverflow: Bool + (newWords[i], isOverflow) = newWords[i].addingReportingOverflow(carry) + if !isOverflow { + break + } + } + + return BigInt(words: Words(newWords)) + } + + @usableFromInline + internal var _isNegative: Bool { + words[words.endIndex - 1] > Int.max + } + + public init(_ source: T) where T: BinaryFloatingPoint { + precondition( + !(source.isNaN || source.isInfinite), + "\(type(of: source)) value cannot be converted to BigInt because it is either infinite or NaN" + ) + + let isNegative = source < 0.0 + var float = isNegative ? -source : source + + if let _ = UInt(exactly: T.greatestFiniteMagnitude) { + words = [UInt(float)] + } else { + var words = Words() + let radix = T(sign: .plus, exponent: T.Exponent(UInt.bitWidth), significand: 1) + repeat { + let digit = UInt(float.truncatingRemainder(dividingBy: radix)) + words.append(digit) + float = (float / radix).rounded(.towardZero) + } while float != 0 + + self.words = words + } + + if isNegative { + self = -self + } + } + + public init(_ source: T) where T: BinaryInteger { + if source >= 0, source < BigInt._digits.count { + self = BigInt._digits[Int(source)] + } else { + words = Words(source.words) + if source > Int.max { + words.append(0) + } + } + } + + public init(clamping source: T) where T: BinaryInteger { + self.init(source) + } + + public init(truncatingIfNeeded source: T) where T: BinaryInteger { + words = Words(source.words) + } + + @inlinable + public static func - (lhs: BigInt, rhs: BigInt) -> BigInt { + var result = lhs + result -= rhs + return result + } + + public init?(exactly source: T) where T: BinaryFloatingPoint { + if (source.isNaN || source.isInfinite) || + (source.rounded(.towardZero) != source) { + return nil + } + + self.init(source) + } + + @usableFromInline + internal init(words: Words) { + self.words = words + } + + public static func += (lhs: inout BigInt, rhs: BigInt) { + if lhs.words.count == 1, rhs.words.count == 1 { + let lhsWord = lhs.words[0] + let rhsWord = rhs.words[0] + + let (result, isOverflow) = lhsWord.addingReportingOverflow(rhsWord) + + if !isOverflow { + lhs.words[0] = result + return + } + let knownNegativeResult = lhsWord > Int.max && rhsWord > Int.max + + if lhsWord > Int.max || rhsWord > Int.max, !knownNegativeResult { + // positive + negative is always smaller, so overflow is a red herring + lhs.words[0] = result + return + } + } + + var isOverflow = false + + var rhsWords = rhs.words + + lhs.words.append(lhs._isNegative ? UInt.max : 0) + rhsWords.append(rhs._isNegative ? UInt.max : 0) + + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + var temp: UInt = 0 + for index in 0 ..< lhs.words.count { + var carryOverflow = false + + if isOverflow { + (temp, carryOverflow) = rhsWords[index].addingReportingOverflow(1) + } else { + temp = rhsWords[index] + } + + (lhs.words[index], isOverflow) = lhs.words[index].addingReportingOverflow(temp) + + isOverflow = carryOverflow || isOverflow + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + public init(integerLiteral value: Int) { + if value >= 0, value < BigInt._digits.count { + self = BigInt._digits[value] + } else { + words = [UInt(bitPattern: value)] + } + } + + public static var isSigned: Bool { true } + + public var bitWidth: Int { words.count * MemoryLayout.size * 8 } + + public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } + + @inlinable + public static func < (lhs: BigInt, rhs: BigInt) -> Bool { + let lhsNegative = lhs._isNegative + let rhsNegative = rhs._isNegative + + if lhsNegative && !rhsNegative { return true } + if rhsNegative && !lhsNegative { return false } + + if (lhsNegative && rhsNegative) || (!lhsNegative && !rhsNegative) { + if lhs.words.count > rhs.words.count { + return lhsNegative ? true : false + } + if lhs.words.count < rhs.words.count { + return lhsNegative ? false : true + } + + for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { + if lhs.words[i] > rhs.words[i] { return false } + } + } + + return true + } + + @inlinable + public static func == (lhs: BigInt, rhs: BigInt) -> Bool { + lhs.words == rhs.words + } + + @inlinable + public static func != (lhs: BigInt, rhs: BigInt) -> Bool { + !(lhs == rhs) + } + + @inlinable + public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { + let (result, _) = _div(lhs: lhs, rhs: rhs) + return result + } + + private static func findQhat(high: UInt, low: UInt.Magnitude, divisor: UInt, nextVdigit: UInt, nextUdigit: UInt) -> UInt { + var (qhat, rhat) = divisor.dividingFullWidth((high, low)) + + if high >= divisor { // This means qhat >= b + qhat = UInt.max + } + + let (tempHigh, tempLow) = qhat.multipliedFullWidth(by: nextVdigit) + var (rtempHigh, rtempLow) = rhat.multipliedFullWidth(by: UInt.max) + var overflow = false + (rtempLow, overflow) = rtempLow.addingReportingOverflow(1) + if overflow { + rtempHigh += 1 } - - public init(integerLiteral value: Int) { - if value >= 0 && value < BigInt._digits.count { - self = BigInt._digits[value] + + (rtempLow, overflow) = rtempLow.addingReportingOverflow(nextUdigit) + if overflow { + rtempHigh += 1 + } + + while true { + if (tempHigh > rtempHigh) || ((tempHigh == rtempHigh) && (tempLow > rtempLow)) { + qhat -= 1 + (rhat, overflow) = rhat.addingReportingOverflow(divisor) + if !overflow { + continue } else { - words = [UInt(bitPattern: value)] + break } + } else { + break + } } - - public static var isSigned: Bool { return true } - - public var bitWidth: Int { return words.count * MemoryLayout.size * 8 } - - public var trailingZeroBitCount: Int { return words.first?.trailingZeroBitCount ?? 0 } - - @inlinable - public static func < (lhs: BigInt, rhs: BigInt) -> Bool { - let lhsNegative = lhs._isNegative - let rhsNegative = rhs._isNegative - - if lhsNegative && !rhsNegative { return true } - if rhsNegative && !lhsNegative { return false } - - if (lhsNegative && rhsNegative) || (!lhsNegative && !rhsNegative) { - if lhs.words.count > rhs.words.count { - return lhsNegative ? true : false - } - if lhs.words.count < rhs.words.count { - return lhsNegative ? false : true - } - - for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { - if lhs.words[i] > rhs.words[i] { return false } - } - } - - return true - } - - @inlinable - public static func ==(lhs: BigInt, rhs: BigInt) -> Bool { - return lhs.words == rhs.words - } - - @inlinable - public static func !=(lhs: BigInt, rhs: BigInt) -> Bool { - return !(lhs == rhs) - } - - @inlinable - public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { - let (result, _) = _div(lhs: lhs, rhs: rhs) - return result - } - - private static func findQhat(high: UInt, low: UInt.Magnitude, divisor: UInt, nextVdigit: UInt, nextUdigit: UInt) -> UInt { - var (qhat, rhat) = divisor.dividingFullWidth((high, low)) - - if high >= divisor {// This means qhat >= b - qhat = UInt.max - } - - let (tempHigh, tempLow) = qhat.multipliedFullWidth(by: nextVdigit) - var (rtempHigh, rtempLow) = rhat.multipliedFullWidth(by: UInt.max) - var overflow = false - (rtempLow, overflow) = rtempLow.addingReportingOverflow(1) - if overflow { - rtempHigh += 1 + + return qhat + } + + @usableFromInline + internal static func _div(lhs: BigInt, rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { + precondition(rhs != _digits[0], "Division by zero error!") + + 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(words: [UInt(bitPattern: quot)]), BigInt(words: [UInt(bitPattern: rem)])) + } + + let lhsIsNeg = lhs._isNegative + let rhsIsNeg = rhs._isNegative + + var lhsWords = lhsIsNeg ? (-lhs).words : lhs.words + var rhsWords = rhsIsNeg ? (-rhs).words : rhs.words + + while rhsWords[rhsWords.endIndex - 1] == 0, rhsWords.count > 2 { + rhsWords.removeLast() + } + + if rhsWords.count > lhsWords.count { return (0, lhs) } + + if rhsWords.count == 1 { + rhsWords.append(0) + } + + func normalize(x: UInt) -> UInt { + #if arch(arm64) || arch(x86_64) + if x == 0 { + return 64 } - - (rtempLow, overflow) = rtempLow.addingReportingOverflow(nextUdigit) - if overflow { - rtempHigh += 1 + var x = UInt64(x) + var n: UInt = 0 + if x <= 0x0000_0000_FFFF_FFFF { + n += 32 + x <<= 32 } - - while true { - if (tempHigh > rtempHigh) || ((tempHigh == rtempHigh) && (tempLow > rtempLow)) { - qhat -= 1 - (rhat, overflow) = rhat.addingReportingOverflow(divisor) - if !overflow { - continue - } else { - break - } - } else { - break - } + if x <= 0x0000_FFFF_FFFF_FFFF { + n += 16 + x <<= 16 } - - return qhat - } - - @usableFromInline - internal static func _div(lhs: BigInt, rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { - precondition(rhs != _digits[0], "Division by zero error!") - - 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(words: [UInt(bitPattern: quot)]), BigInt(words: [UInt(bitPattern: rem)])) + if x <= 0x00FF_FFFF_FFFF_FFFF { + n += 8 + x <<= 8 } - - let lhsIsNeg = lhs._isNegative - let rhsIsNeg = rhs._isNegative - - var lhsWords = lhsIsNeg ? (-lhs).words : lhs.words - var rhsWords = rhsIsNeg ? (-rhs).words : rhs.words - - while rhsWords[rhsWords.endIndex - 1] == 0 && rhsWords.count > 2 { - rhsWords.removeLast() + if x <= 0x0FFF_FFFF_FFFF_FFFF { + n += 4 + x <<= 4 } - - if rhsWords.count > lhsWords.count { return (0, lhs) } - - if rhsWords.count == 1 { - rhsWords.append(0) + if x <= 0x3FFF_FFFF_FFFF_FFFF { + n += 2 + x <<= 2 } - - func normalize(x: UInt) -> UInt { - #if arch(arm64) || arch(x86_64) - if x == 0 { - return 64 - } - var x = UInt64(x) - var n: UInt = 0 - if x <= 0x00000000FFFFFFFF { - n += 32 - x <<= 32 - } - if x <= 0x0000FFFFFFFFFFFF { - n += 16 - x <<= 16 - } - if x <= 0x00FFFFFFFFFFFFFF { - n += 8 - x <<= 8 - } - if x <= 0x0FFFFFFFFFFFFFFF { - n += 4 - x <<= 4 - } - if x <= 0x3FFFFFFFFFFFFFFF { - n += 2 - x <<= 2 - } - if x <= 0x7FFFFFFFFFFFFFFF { - n += 1 - } - - return n - #else - if x == 0 { - return 32 - } - - var x = x - var n: UInt = 0 - if x <= 0x0000FFFF { - n += 16 - x <<= 16 - } - if x <= 0x00FFFFFF { - n += 8 - x <<= 8 - } - if x <= 0x0FFFFFFF { - n += 4 - x <<= 4 - } - if x <= 0x3FFFFFFF { - n += 2 - x <<= 2 - } - if x <= 0x7FFFFFFF { - n += 1 - } - - return n - #endif + if x <= 0x7FFF_FFFF_FFFF_FFFF { + n += 1 } - - if lhsWords.count < rhsWords.count { - for _ in 0..<(rhsWords.count - lhsWords.count) { - lhsWords.append(0) - } + + return n + #else + if x == 0 { + return 32 } - - let m = lhsWords.count - let n = rhsWords.count - - let bitWidth = UInt(UInt.bitWidth) - - let s = normalize(x: rhsWords[n - 1]) - let rn = UnsafeMutablePointer.allocate(capacity: n) - rn.initialize(repeating: 0, count: n) - defer { rn.deallocate() } - for i in (1...(n - 1)).reversed() { - rn[i] = (rhsWords[i] << s) | (rhsWords[i - 1] >> (bitWidth - s)) + + var x = x + var n: UInt = 0 + if x <= 0x0000_FFFF { + n += 16 + x <<= 16 } - rn[0] <<= s - - let ln = UnsafeMutablePointer.allocate(capacity: m + n + 1) - ln.initialize(repeating: 0, count: m + n + 1) - defer { ln.deallocate() } - ln[m] = lhsWords[m - 1] >> (bitWidth - s) - for i in (1...(m - 1)).reversed() { - ln[i] = (lhsWords[i] << s) | (lhsWords[i - 1] >> (bitWidth - s)) + if x <= 0x00FF_FFFF { + n += 8 + x <<= 8 } - ln[0] <<= s - - let resultSize = m + 1 - var quot = Words(repeating: 0, count: resultSize) - - for j in (0...m).reversed() { - let qhat = findQhat(high: ln[j + n], low: UInt.Magnitude(ln[j + n - 1]), divisor: rn[n - 1], nextVdigit: rn[n - 2], nextUdigit: ln[j + n - 2]) - - var carry: UInt = 0 - var isOverflow = false - var borrow: UInt = 0 - var underflow = false - for i in 0.. 0 { - (ln[i + j], underflow) = ln[i + j].subtractingReportingOverflow(borrow) - borrow = underflow ? 1 : 0 - } - - var (pHigh, pLow) = qhat.multipliedFullWidth(by: rn[i]) - (pLow, isOverflow) = pLow.addingReportingOverflow(carry) - if isOverflow { - pHigh += 1 - } - - (ln[i + j], underflow) = ln[i + j].subtractingReportingOverflow(pLow) - if underflow { - borrow += 1 - } - - carry = pHigh - } - - (ln[j + n], underflow) = ln[j + n].subtractingReportingOverflow(carry + borrow) - - if underflow { - let newQhat = qhat - 1 - - carry = 0 - var total: UInt = 0 - for i in 0..> s) | ln[i + 1] << (bitWidth - s) + if x <= 0x3FFF_FFFF { + n += 2 + x <<= 2 } - rem[n - 1] = ln[n - 1] >> s - - BigInt._dropExcessWords(words: ") - BigInt._dropExcessWords(words: &rem) - - return (BigInt(words: quot), BigInt(words: rem)) - } - - @inlinable - public static func /= (lhs: inout BigInt, rhs: BigInt) { - lhs = lhs / rhs - } - - @inlinable - public static func % (lhs: BigInt, rhs: BigInt) -> BigInt { - let (_, result) = _div(lhs: lhs, rhs: rhs) - - return result - } - - @inlinable - public static func %= (lhs: inout BigInt, rhs: BigInt) { - lhs = lhs % rhs - } - - public static func * (lhs: BigInt, rhs: BigInt) -> BigInt { - let lhsIsNeg = lhs.words[lhs.words.endIndex - 1] > Int.max - let rhsIsNeg = rhs.words[rhs.words.endIndex - 1] > Int.max - - let lhsWords = lhsIsNeg ? (-lhs).words : lhs.words - let rhsWords = rhsIsNeg ? (-rhs).words : rhs.words - - let count = lhsWords.count + rhsWords.count + 1 - var newWords = Words(repeating: 0, count: count) - - for i in 0...allocate(capacity: n) + rn.initialize(repeating: 0, count: n) + defer { rn.deallocate() } + for i in (1 ... (n - 1)).reversed() { + rn[i] = (rhsWords[i] << s) | (rhsWords[i - 1] >> (bitWidth - s)) + } + rn[0] <<= s + + let ln = UnsafeMutablePointer.allocate(capacity: m + n + 1) + ln.initialize(repeating: 0, count: m + n + 1) + defer { ln.deallocate() } + ln[m] = lhsWords[m - 1] >> (bitWidth - s) + for i in (1 ... (m - 1)).reversed() { + ln[i] = (lhsWords[i] << s) | (lhsWords[i - 1] >> (bitWidth - s)) + } + ln[0] <<= s + + let resultSize = m + 1 + var quot = Words(repeating: 0, count: resultSize) + + for j in (0 ... m).reversed() { + let qhat = findQhat(high: ln[j + n], low: UInt.Magnitude(ln[j + n - 1]), divisor: rn[n - 1], nextVdigit: rn[n - 2], nextUdigit: ln[j + n - 2]) + + var carry: UInt = 0 + var isOverflow = false + var borrow: UInt = 0 + var underflow = false + for i in 0 ..< n { + if borrow > 0 { + (ln[i + j], underflow) = ln[i + j].subtractingReportingOverflow(borrow) + borrow = underflow ? 1 : 0 } - - if (lhsIsNeg || rhsIsNeg) && !(lhsIsNeg && rhsIsNeg) { - return -BigInt(words: newWords) + + var (pHigh, pLow) = qhat.multipliedFullWidth(by: rn[i]) + (pLow, isOverflow) = pLow.addingReportingOverflow(carry) + if isOverflow { + pHigh += 1 } - - return BigInt(words: newWords) - } - - @inlinable - public static func *= (lhs: inout BigInt, rhs: BigInt) { - lhs = lhs * rhs - } - - public static func &= (lhs: inout BigInt, rhs: BigInt) { - var rhsWords = rhs.words - BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) - - for i in 0..> s) | ln[i + 1] << (bitWidth - s) + } + rem[n - 1] = ln[n - 1] >> s + + BigInt._dropExcessWords(words: ") + BigInt._dropExcessWords(words: &rem) + + return (BigInt(words: quot), BigInt(words: rem)) + } + + @inlinable + public static func /= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs / rhs + } + + @inlinable + public static func % (lhs: BigInt, rhs: BigInt) -> BigInt { + let (_, result) = _div(lhs: lhs, rhs: rhs) + + return result + } + + @inlinable + public static func %= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs % rhs + } + + public static func * (lhs: BigInt, rhs: BigInt) -> BigInt { + let lhsIsNeg = lhs.words[lhs.words.endIndex - 1] > Int.max + let rhsIsNeg = rhs.words[rhs.words.endIndex - 1] > Int.max + + let lhsWords = lhsIsNeg ? (-lhs).words : lhs.words + let rhsWords = rhsIsNeg ? (-rhs).words : rhs.words + + 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 + + var lastJ: Int = 0 + for j in i ..< (lhsWords.count + i) { + var (high, low) = rhsWords[i].multipliedFullWidth(by: lhsWords[j - i]) + var isOverflow: Bool + (digit, isOverflow) = low.addingReportingOverflow(newWords[j]) + if isOverflow { + high += 1 } - - BigInt._dropExcessWords(words: &lhs.words) - } - - public static func ^= (lhs: inout BigInt, rhs: BigInt) { - var rhsWords = rhs.words - BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) - - for i in 0.. BigInt { - var result = lhs - result += rhs - return result - } - - @inlinable - public static func -= (lhs: inout BigInt, rhs: BigInt) { - lhs += -rhs - } - - private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) { - let lhsIsNeg = (lhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 - let rhsIsNeg = (rhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 - - if lhsWords.count > rhsWords.count { - for _ in 0..<(lhsWords.count - rhsWords.count) { - rhsIsNeg ? rhsWords.append(UInt.max) : rhsWords.append(0) - } - } else if rhsWords.count > lhsWords.count { - for _ in 0..<(rhsWords.count - lhsWords.count) { - lhsIsNeg ? lhsWords.append(UInt.max) : lhsWords.append(0) - } + + carry = high + newWords[j] = digit + lastJ = j + } + + if carry != 0 { + let isOverflow: Bool + (digit, isOverflow) = newWords[lastJ + 1].addingReportingOverflow(carry) + if isOverflow { + carry = 1 } + newWords[lastJ + 1] = digit + } } - - private static func _dropExcessWords(words: inout Words) { - while words.count > 1 && words[words.endIndex - 1] == 0 { - if words[words.endIndex - 2] <= Int.max { - words.removeLast() - } else { - break - } - } - - while words.count > 1 && words[words.endIndex - 1] == UInt.max { - if words[words.endIndex - 2] > Int.max { - words.removeLast() - } else { - break - } - } + + for i in stride(from: count - 1, through: 1, by: -1) { + if newWords[i] == 0, newWords[i - 1] <= Int.max { + newWords.removeLast() + } else { + break + } } - - public var description: String { - var result = "" - if words.count == 1 { - return Int64(bitPattern: UInt64(words[0])).description - } else { - var next = abs(self) - while next != 0 { - let digit: BigInt - (next, digit) = BigInt._div(lhs: next, rhs: 10) - result += "\(digit.words[0])" - } - } - - return (self < 0 ? "-" : "") + String(result.reversed()) + if lhsIsNeg || rhsIsNeg, !(lhsIsNeg && rhsIsNeg) { + return -BigInt(words: newWords) + } + + return BigInt(words: newWords) + } + + @inlinable + public static func *= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs * rhs + } + + public static func &= (lhs: inout BigInt, rhs: BigInt) { + var rhsWords = rhs.words + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + + for i in 0 ..< rhsWords.count { + lhs.words[i] &= rhsWords[i] + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + public static func |= (lhs: inout BigInt, rhs: BigInt) { + var rhsWords = rhs.words + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + + for i in 0 ..< rhsWords.count { + lhs.words[i] |= rhsWords[i] } - - private static let _digits = { - (0...10).map { BigInt(words: [UInt(bitPattern: $0)]) } - }() + + BigInt._dropExcessWords(words: &lhs.words) + } + + public static func ^= (lhs: inout BigInt, rhs: BigInt) { + var rhsWords = rhs.words + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + + for i in 0 ..< rhsWords.count { + lhs.words[i] &= rhsWords[i] + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + @inlinable + public static func + (lhs: BigInt, rhs: BigInt) -> BigInt { + var result = lhs + result += rhs + return result + } + + @inlinable + public static func -= (lhs: inout BigInt, rhs: BigInt) { + lhs += -rhs + } + + private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) { + let lhsIsNeg = (lhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 + let rhsIsNeg = (rhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 + + if lhsWords.count > rhsWords.count { + for _ in 0 ..< (lhsWords.count - rhsWords.count) { + rhsIsNeg ? rhsWords.append(UInt.max) : rhsWords.append(0) + } + } else if rhsWords.count > lhsWords.count { + for _ in 0 ..< (rhsWords.count - lhsWords.count) { + lhsIsNeg ? lhsWords.append(UInt.max) : lhsWords.append(0) + } + } + } + + private static func _dropExcessWords(words: inout Words) { + while words.count > 1, words[words.endIndex - 1] == 0 { + if words[words.endIndex - 2] <= Int.max { + words.removeLast() + } else { + break + } + } + + while words.count > 1, words[words.endIndex - 1] == UInt.max { + if words[words.endIndex - 2] > Int.max { + words.removeLast() + } else { + break + } + } + } + + public var description: String { + var result = "" + + if words.count == 1 { + return Int64(bitPattern: UInt64(words[0])).description + } else { + var next = abs(self) + while next != 0 { + let digit: BigInt + (next, digit) = BigInt._div(lhs: next, rhs: 10) + result += "\(digit.words[0])" + } + } + + return (self < 0 ? "-" : "") + String(result.reversed()) + } + + private static let _digits = { + (0 ... 10).map { BigInt(words: [UInt(bitPattern: $0)]) } + }() } // inspired by https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms public func pow(_ lhs: BigInt, _ rhs: BigInt) -> BigInt { - let bits_of_n = { - (n: BigInt) -> Array in - var bits: Array = [] - var n = n - while n != 0 { - bits.append(Int(n % 2)) - n /= 2 - } - - return bits - } - - var r: BigInt = 1 - for bit in bits_of_n(rhs).reversed() { - r *= r - if bit == 1 { - r *= lhs - } + let bits_of_n = { + (n: BigInt) -> [Int] in + var bits: [Int] = [] + var n = n + while n != 0 { + bits.append(Int(n % 2)) + n /= 2 } - - return r + + return bits + } + + var r: BigInt = 1 + for bit in bits_of_n(rhs).reversed() { + r *= r + if bit == 1 { + r *= lhs + } + } + + return r } diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 7e5de1ce..d79da0f6 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -1,76 +1,101 @@ -import XCTest +//===--- BigIntTests.swift -----------------------*- swift -*-===// +// +// This source file is part of the Swift Numerics open source project +// +// Copyright (c) 2019 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 BigInt +import XCTest func fac(_ n: BigInt) -> BigInt { - var result: BigInt = 1 - var count = n - while count >= 1 { - result *= count - count -= 1 - } - - return result + var result: BigInt = 1 + var count = n + while count >= 1 { + result *= count + count -= 1 + } + + return result } final class BigIntTests: XCTestCase { - func testExample() throws { - let bar = BigInt(exactly: -100) - XCTAssertNotNil(bar) - XCTAssert(bar! < 0) - - XCTAssert(-(bar!) > 0) - XCTAssertEqual(-(bar!), BigInt(100)) - - XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, BigInt("1234567890123456789012345678901234567890")!) - } - - func testFloatingConversion() { - let bar = BigInt(3.14159) - XCTAssertEqual(bar, BigInt(3)) - let foo = BigInt(exactly: 3.14159) - XCTAssertNil(foo) - - let baz = BigInt(exactly: 2.4e39) - XCTAssertNotNil(baz) - let equal = (baz ?? 0) / BigInt(1e38) == BigInt(24) - XCTAssertEqual(equal, true) - - let infinite = BigInt(exactly: Double.infinity) - XCTAssertNil(infinite) - } + func testExample() throws { + let bar = BigInt(exactly: -100) + XCTAssertNotNil(bar) + XCTAssert(bar! < 0) + + XCTAssert(-(bar!) > 0) + XCTAssertEqual(-(bar!), BigInt(100)) + + XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, BigInt("1234567890123456789012345678901234567890")!) + } + + func testFloatingConversion() { + let bar = BigInt(3.14159) + XCTAssertEqual(bar, BigInt(3)) + let foo = BigInt(exactly: 3.14159) + XCTAssertNil(foo) + + let baz = BigInt(exactly: 2.4e39) + XCTAssertNotNil(baz) + let equal = (baz ?? 0) / BigInt(1e38) == BigInt(24) + XCTAssertEqual(equal, true) + + let infinite = BigInt(exactly: Double.infinity) + XCTAssertNil(infinite) + } + + func testUIntConversion() { + let foo = BigInt(UInt.max) + XCTAssertNotEqual(foo, BigInt(-1)) + + let bar = BigInt(bitPattern: UInt.max) + XCTAssertEqual(bar, BigInt(-1)) + } + + func testComparison() { + let foo = BigInt(-10) + let bar = BigInt(-20) + + XCTAssert(foo > bar) + XCTAssert(bar < foo) + XCTAssert(foo == BigInt(-10)) + + let baz = pow(foo, -bar) + XCTAssertEqual(baz, BigInt("100000000000000000000")!) + } + + func testMath() { + let foo = pow(BigInt(10), 20) + let bar = BigInt("1234567890123456789012345678901234567890")! + + let baz = foo + bar + + XCTAssertEqual(baz, BigInt("1234567890123456789112345678901234567890")!) + + let fooz = foo >> BigInt(10) + XCTAssertEqual(fooz, foo / 1024) + + let barz = BigInt(1) << 64 + XCTAssertEqual(barz, BigInt(UInt.max) + 1) + } + + func testHashable(){ + let foo = BigInt("1234567890123456789012345678901234567890")! + let bar = BigInt("1234567890123456789112345678901234567890")! + let baz: BigInt = 153 - func testUIntConversion() { - let foo = BigInt(UInt.max) - XCTAssertNotEqual(foo, BigInt(-1)) - - let bar = BigInt(bitPattern: UInt.max) - XCTAssertEqual(bar, BigInt(-1)) - } + let dict = [ foo: "Hello", bar: "World", baz: "!" ] - func testComparison() { - let foo = BigInt(-10) - let bar = BigInt(-20) - - XCTAssert(foo > bar) - XCTAssert(bar < foo) - XCTAssert(foo == BigInt(-10)) - - let baz = pow(foo, -bar) - XCTAssertEqual(baz, BigInt("100000000000000000000")!) - } + let hash = foo.hashValue + print(hash) - func testMath() { - let foo = pow(BigInt(10), 20) - let bar = BigInt("1234567890123456789012345678901234567890")! - - let baz = foo + bar - - XCTAssertEqual(baz, BigInt("1234567890123456789112345678901234567890")!) - - let fooz = foo >> BigInt(10) - XCTAssertEqual(fooz, foo / 1024) - - let barz = BigInt(1) << 64 - XCTAssertEqual(barz, BigInt(UInt.max) + 1) - } + XCTAssertEqual(dict[foo]!, "Hello") + XCTAssertEqual(dict[bar]!, "World") + } } From cced27a6c036937cd855cb74db32e53cb8193e13 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 30 Dec 2019 15:50:03 -0500 Subject: [PATCH 10/92] Missed a spot where things should be alphabetical --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 19bec320..5063c8a7 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,7 @@ let package = Package( targets: [ .target(name: "BigInt", dependencies: []), .target(name: "Complex", dependencies: ["Real"]), - .target(name: "Numerics", dependencies: ["Complex", "Real", "BigInt"]), + .target(name: "Numerics", dependencies: ["BigInt", "Complex", "Real"]), .target(name: "NumericsShims", dependencies: []), .target(name: "Real", dependencies: ["NumericsShims"]), From 67858690eb4bc8ce0117019f963087318c5b86b2 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 30 Dec 2019 16:01:04 -0500 Subject: [PATCH 11/92] Update `LosslessStringConvertible` initializer as per further suggestions from @Wildchild9, adding in the removing leading zeros part I missed. --- Sources/BigInt/BigInt.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 0b3eba62..605ec0b9 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -13,17 +13,17 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { public private(set) var words: [UInt] public init?(_ description: String) { - var description = description let isNegative = description.hasPrefix("-") - if isNegative || description.hasPrefix("+") { - _ = description.removeFirst() - } + let hasPrefixOperator = isNegative || description.hasPrefix("+") + + let unprefixedDescription = hasPrefixOperator ? description.dropFirst() : description[...] + guard !unprefixedDescription.isEmpty else { return nil } let digits = Set("0123456789") - guard description.allSatisfy({ digits.contains($0) }) else { return nil } + guard unprefixedDescription.allSatisfy({ digits.contains($0) }) else { return nil } var result: BigInt = 0 - for (i, char) in description.reversed().enumerated() { + for (i, char) in unprefixedDescription.drop(while: { $0 == "0" }).reversed().enumerated() { // We're ok to force unwrap here because of the guard above. result += BigInt(char.wholeNumberValue!) * pow(10, BigInt(i)) } From e490f1fc6698ed4de6e06616cd418674227bc3d1 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 13 Jan 2020 14:45:48 -0500 Subject: [PATCH 12/92] Explicitly implment hash(into:) per @benrimmington feedback Technically, I believe the auto-generated implementation was already doing exactly this, since `words` is the only stored property of `BigInt`. However, it rarely hurts to be explicit. --- Sources/BigInt/BigInt.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 605ec0b9..13fa53c2 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -316,6 +316,11 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { public static func == (lhs: BigInt, rhs: BigInt) -> Bool { lhs.words == rhs.words } + + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(words) + } @inlinable public static func != (lhs: BigInt, rhs: BigInt) -> Bool { From 2863c44182253ce9cfa3549d79acdda9d164d4ba Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 01:42:43 +0000 Subject: [PATCH 13/92] [BigInt] Remove the isSigned property --- Sources/BigInt/BigInt.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 13fa53c2..68ca8aed 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -282,8 +282,6 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { } } - public static var isSigned: Bool { true } - public var bitWidth: Int { words.count * MemoryLayout.size * 8 } public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } From 38073d2ab5e0a850f82cf80c85f3bc74921d6f3a Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 01:49:26 +0000 Subject: [PATCH 14/92] [BigInt] Move init(words:) and init(bitPattern:) --- Sources/BigInt/BigInt.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 68ca8aed..fb971b9e 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -12,6 +12,15 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { public private(set) var words: [UInt] + @usableFromInline + internal init(words: Words) { + self.words = words + } + + public init(bitPattern source: T) where T: BinaryInteger { + words = Words(source.words) + } + public init?(_ description: String) { let isNegative = description.hasPrefix("-") let hasPrefixOperator = isNegative || description.hasPrefix("+") @@ -39,10 +48,6 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { self.init(source) } - public init(bitPattern source: T) where T: BinaryInteger { - words = Words(source.words) - } - public var magnitude: BigInt { if _isNegative { return -self @@ -223,11 +228,6 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { self.init(source) } - @usableFromInline - internal init(words: Words) { - self.words = words - } - public static func += (lhs: inout BigInt, rhs: BigInt) { if lhs.words.count == 1, rhs.words.count == 1 { let lhsWord = lhs.words[0] From 2f40de6195fdcf011d461b58e614c0509121a986 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 01:52:01 +0000 Subject: [PATCH 15/92] [BigInt] Move _isNegative and _digits properties --- Sources/BigInt/BigInt.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index fb971b9e..308dca2f 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -21,6 +21,15 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { words = Words(source.words) } + @usableFromInline + internal var _isNegative: Bool { + words[words.endIndex - 1] > Int.max + } + + private static let _digits = { + (0 ... 10).map { BigInt(words: [UInt(bitPattern: $0)]) } + }() + public init?(_ description: String) { let isNegative = description.hasPrefix("-") let hasPrefixOperator = isNegative || description.hasPrefix("+") @@ -160,11 +169,6 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { return BigInt(words: Words(newWords)) } - @usableFromInline - internal var _isNegative: Bool { - words[words.endIndex - 1] > Int.max - } - public init(_ source: T) where T: BinaryFloatingPoint { precondition( !(source.isNaN || source.isInfinite), @@ -723,10 +727,6 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { return (self < 0 ? "-" : "") + String(result.reversed()) } - - private static let _digits = { - (0 ... 10).map { BigInt(words: [UInt(bitPattern: $0)]) } - }() } // inspired by https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms From dbf743b31444f5d57aabe81814a01800907fff2e Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 01:54:58 +0000 Subject: [PATCH 16/92] [BigInt] Move the Equatable conformance --- Sources/BigInt/BigInt.swift | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 308dca2f..13f42b77 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -29,6 +29,22 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { private static let _digits = { (0 ... 10).map { BigInt(words: [UInt(bitPattern: $0)]) } }() +} + +extension BigInt: Equatable { + + @inlinable + public static func == (lhs: BigInt, rhs: BigInt) -> Bool { + lhs.words == rhs.words + } + + @inlinable + public static func != (lhs: BigInt, rhs: BigInt) -> Bool { + !(lhs == rhs) + } +} + +extension BigInt { public init?(_ description: String) { let isNegative = description.hasPrefix("-") @@ -314,21 +330,11 @@ public struct BigInt: SignedInteger, LosslessStringConvertible { return true } - @inlinable - public static func == (lhs: BigInt, rhs: BigInt) -> Bool { - lhs.words == rhs.words - } - @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(words) } - @inlinable - public static func != (lhs: BigInt, rhs: BigInt) -> Bool { - !(lhs == rhs) - } - @inlinable public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { let (result, _) = _div(lhs: lhs, rhs: rhs) From a799618103c88912ae2ebf17c3073e63a794a750 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 01:56:43 +0000 Subject: [PATCH 17/92] [BigInt] Move the Hashable conformance --- Sources/BigInt/BigInt.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 13f42b77..d734bafc 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -44,6 +44,14 @@ extension BigInt: Equatable { } } +extension BigInt: Hashable { + + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(words) + } +} + extension BigInt { public init?(_ description: String) { @@ -330,11 +338,6 @@ extension BigInt { return true } - @inlinable - public func hash(into hasher: inout Hasher) { - hasher.combine(words) - } - @inlinable public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { let (result, _) = _div(lhs: lhs, rhs: rhs) From 368caf5b3f49e5710aeb0aabc6940cd1f0634edc Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 01:58:03 +0000 Subject: [PATCH 18/92] [BigInt] Move the Comparable conformance --- Sources/BigInt/BigInt.swift | 51 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index d734bafc..13431207 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -52,6 +52,33 @@ extension BigInt: Hashable { } } +extension BigInt: Comparable { + + @inlinable + public static func < (lhs: BigInt, rhs: BigInt) -> Bool { + let lhsNegative = lhs._isNegative + let rhsNegative = rhs._isNegative + + if lhsNegative && !rhsNegative { return true } + if rhsNegative && !lhsNegative { return false } + + if (lhsNegative && rhsNegative) || (!lhsNegative && !rhsNegative) { + if lhs.words.count > rhs.words.count { + return lhsNegative ? true : false + } + if lhs.words.count < rhs.words.count { + return lhsNegative ? false : true + } + + for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { + if lhs.words[i] > rhs.words[i] { return false } + } + } + + return true + } +} + extension BigInt { public init?(_ description: String) { @@ -314,30 +341,6 @@ extension BigInt { public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } - @inlinable - public static func < (lhs: BigInt, rhs: BigInt) -> Bool { - let lhsNegative = lhs._isNegative - let rhsNegative = rhs._isNegative - - if lhsNegative && !rhsNegative { return true } - if rhsNegative && !lhsNegative { return false } - - if (lhsNegative && rhsNegative) || (!lhsNegative && !rhsNegative) { - if lhs.words.count > rhs.words.count { - return lhsNegative ? true : false - } - if lhs.words.count < rhs.words.count { - return lhsNegative ? false : true - } - - for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { - if lhs.words[i] > rhs.words[i] { return false } - } - } - - return true - } - @inlinable public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { let (result, _) = _div(lhs: lhs, rhs: rhs) From 2419741a7cda55cfbb7ed7bf66013dec78c56798 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:00:12 +0000 Subject: [PATCH 19/92] [BigInt] Move the CustomStringConvertible conformance --- Sources/BigInt/BigInt.swift | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 13431207..5ad5a511 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -79,6 +79,26 @@ extension BigInt: Comparable { } } +extension BigInt: CustomStringConvertible { + + public var description: String { + var result = "" + + if words.count == 1 { + return Int64(bitPattern: UInt64(words[0])).description + } else { + var next = abs(self) + while next != 0 { + let digit: BigInt + (next, digit) = BigInt._div(lhs: next, rhs: 10) + result += "\(digit.words[0])" + } + } + + return (self < 0 ? "-" : "") + String(result.reversed()) + } +} + extension BigInt { public init?(_ description: String) { @@ -722,23 +742,6 @@ extension BigInt { } } } - - public var description: String { - var result = "" - - if words.count == 1 { - return Int64(bitPattern: UInt64(words[0])).description - } else { - var next = abs(self) - while next != 0 { - let digit: BigInt - (next, digit) = BigInt._div(lhs: next, rhs: 10) - result += "\(digit.words[0])" - } - } - - return (self < 0 ? "-" : "") + String(result.reversed()) - } } // inspired by https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms From 5db4a69e574c908ddf03d4e058db3ccc1d554f8e Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:04:16 +0000 Subject: [PATCH 20/92] [BigInt] Move the LosslessStringConvertible conformance --- Sources/BigInt/BigInt.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 5ad5a511..d7065a2e 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -public struct BigInt: SignedInteger, LosslessStringConvertible { +public struct BigInt: SignedInteger { public private(set) var words: [UInt] @usableFromInline @@ -99,12 +99,12 @@ extension BigInt: CustomStringConvertible { } } -extension BigInt { +extension BigInt: LosslessStringConvertible { public init?(_ description: String) { let isNegative = description.hasPrefix("-") let hasPrefixOperator = isNegative || description.hasPrefix("+") - + let unprefixedDescription = hasPrefixOperator ? description.dropFirst() : description[...] guard !unprefixedDescription.isEmpty else { return nil } @@ -123,6 +123,9 @@ extension BigInt { words = result.words } +} + +extension BigInt { public init?(exactly source: T) where T: BinaryInteger { self.init(source) From 5cb9b95ce87117473459fb2ceb828b794514dd17 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:05:42 +0000 Subject: [PATCH 21/92] [BigInt] Move the ExpressibleByIntegerLiteral conformance --- Sources/BigInt/BigInt.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index d7065a2e..55b21e15 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -125,6 +125,17 @@ extension BigInt: LosslessStringConvertible { } } +extension BigInt: ExpressibleByIntegerLiteral { + + public init(integerLiteral value: Int) { + if value >= 0, value < BigInt._digits.count { + self = BigInt._digits[value] + } else { + words = [UInt(bitPattern: value)] + } + } +} + extension BigInt { public init?(exactly source: T) where T: BinaryInteger { @@ -352,14 +363,6 @@ extension BigInt { BigInt._dropExcessWords(words: &lhs.words) } - public init(integerLiteral value: Int) { - if value >= 0, value < BigInt._digits.count { - self = BigInt._digits[value] - } else { - words = [UInt(bitPattern: value)] - } - } - public var bitWidth: Int { words.count * MemoryLayout.size * 8 } public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } From cb5e7f3c7aed60d3aef84826737e1b4dab7090d3 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:09:45 +0000 Subject: [PATCH 22/92] [BigInt] Move the AdditiveArithmetic conformance --- Sources/BigInt/BigInt.swift | 133 ++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 55b21e15..c320f03d 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -136,6 +136,74 @@ extension BigInt: ExpressibleByIntegerLiteral { } } +extension BigInt: AdditiveArithmetic { + + @inlinable + public static func + (lhs: BigInt, rhs: BigInt) -> BigInt { + var result = lhs + result += rhs + return result + } + + public static func += (lhs: inout BigInt, rhs: BigInt) { + if lhs.words.count == 1, rhs.words.count == 1 { + let lhsWord = lhs.words[0] + let rhsWord = rhs.words[0] + + let (result, isOverflow) = lhsWord.addingReportingOverflow(rhsWord) + + if !isOverflow { + lhs.words[0] = result + return + } + let knownNegativeResult = lhsWord > Int.max && rhsWord > Int.max + + if lhsWord > Int.max || rhsWord > Int.max, !knownNegativeResult { + // positive + negative is always smaller, so overflow is a red herring + lhs.words[0] = result + return + } + } + + var isOverflow = false + + var rhsWords = rhs.words + + lhs.words.append(lhs._isNegative ? UInt.max : 0) + rhsWords.append(rhs._isNegative ? UInt.max : 0) + + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + var temp: UInt = 0 + for index in 0 ..< lhs.words.count { + var carryOverflow = false + + if isOverflow { + (temp, carryOverflow) = rhsWords[index].addingReportingOverflow(1) + } else { + temp = rhsWords[index] + } + + (lhs.words[index], isOverflow) = lhs.words[index].addingReportingOverflow(temp) + + isOverflow = carryOverflow || isOverflow + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + @inlinable + public static func - (lhs: BigInt, rhs: BigInt) -> BigInt { + var result = lhs + result -= rhs + return result + } + + @inlinable + public static func -= (lhs: inout BigInt, rhs: BigInt) { + lhs += -rhs + } +} + extension BigInt { public init?(exactly source: T) where T: BinaryInteger { @@ -301,13 +369,6 @@ extension BigInt { words = Words(source.words) } - @inlinable - public static func - (lhs: BigInt, rhs: BigInt) -> BigInt { - var result = lhs - result -= rhs - return result - } - public init?(exactly source: T) where T: BinaryFloatingPoint { if (source.isNaN || source.isInfinite) || (source.rounded(.towardZero) != source) { @@ -317,52 +378,6 @@ extension BigInt { self.init(source) } - public static func += (lhs: inout BigInt, rhs: BigInt) { - if lhs.words.count == 1, rhs.words.count == 1 { - let lhsWord = lhs.words[0] - let rhsWord = rhs.words[0] - - let (result, isOverflow) = lhsWord.addingReportingOverflow(rhsWord) - - if !isOverflow { - lhs.words[0] = result - return - } - let knownNegativeResult = lhsWord > Int.max && rhsWord > Int.max - - if lhsWord > Int.max || rhsWord > Int.max, !knownNegativeResult { - // positive + negative is always smaller, so overflow is a red herring - lhs.words[0] = result - return - } - } - - var isOverflow = false - - var rhsWords = rhs.words - - lhs.words.append(lhs._isNegative ? UInt.max : 0) - rhsWords.append(rhs._isNegative ? UInt.max : 0) - - BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) - var temp: UInt = 0 - for index in 0 ..< lhs.words.count { - var carryOverflow = false - - if isOverflow { - (temp, carryOverflow) = rhsWords[index].addingReportingOverflow(1) - } else { - temp = rhsWords[index] - } - - (lhs.words[index], isOverflow) = lhs.words[index].addingReportingOverflow(temp) - - isOverflow = carryOverflow || isOverflow - } - - BigInt._dropExcessWords(words: &lhs.words) - } - public var bitWidth: Int { words.count * MemoryLayout.size * 8 } public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } @@ -704,18 +719,6 @@ extension BigInt { BigInt._dropExcessWords(words: &lhs.words) } - @inlinable - public static func + (lhs: BigInt, rhs: BigInt) -> BigInt { - var result = lhs - result += rhs - return result - } - - @inlinable - public static func -= (lhs: inout BigInt, rhs: BigInt) { - lhs += -rhs - } - private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) { let lhsIsNeg = (lhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 let rhsIsNeg = (rhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 From 5b84051f2aaf86868b525e782cb918a0437bba73 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:14:30 +0000 Subject: [PATCH 23/92] [BigInt] Move the Numeric conformance --- Sources/BigInt/BigInt.swift | 131 ++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index c320f03d..64df66dd 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -204,7 +204,7 @@ extension BigInt: AdditiveArithmetic { } } -extension BigInt { +extension BigInt: Numeric { public init?(exactly source: T) where T: BinaryInteger { self.init(source) @@ -217,6 +217,72 @@ extension BigInt { return self } + public static func * (lhs: BigInt, rhs: BigInt) -> BigInt { + let lhsIsNeg = lhs.words[lhs.words.endIndex - 1] > Int.max + let rhsIsNeg = rhs.words[rhs.words.endIndex - 1] > Int.max + + let lhsWords = lhsIsNeg ? (-lhs).words : lhs.words + let rhsWords = rhsIsNeg ? (-rhs).words : rhs.words + + 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 + + var lastJ: Int = 0 + for j in i ..< (lhsWords.count + i) { + var (high, low) = rhsWords[i].multipliedFullWidth(by: lhsWords[j - i]) + var isOverflow: Bool + (digit, isOverflow) = low.addingReportingOverflow(newWords[j]) + if isOverflow { + high += 1 + } + + (digit, isOverflow) = digit.addingReportingOverflow(carry) + if isOverflow { + high += 1 + } + + carry = high + newWords[j] = digit + lastJ = j + } + + if carry != 0 { + let isOverflow: Bool + (digit, isOverflow) = newWords[lastJ + 1].addingReportingOverflow(carry) + if isOverflow { + carry = 1 + } + 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() + } else { + break + } + } + + if lhsIsNeg || rhsIsNeg, !(lhsIsNeg && rhsIsNeg) { + return -BigInt(words: newWords) + } + + return BigInt(words: newWords) + } + + @inlinable + public static func *= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs * rhs + } +} + +extension BigInt { + public static func <<= (lhs: inout BigInt, rhs: RHS) where RHS: BinaryInteger { if rhs.signum() < 0 { lhs >>= rhs.magnitude @@ -623,69 +689,6 @@ extension BigInt { lhs = lhs % rhs } - public static func * (lhs: BigInt, rhs: BigInt) -> BigInt { - let lhsIsNeg = lhs.words[lhs.words.endIndex - 1] > Int.max - let rhsIsNeg = rhs.words[rhs.words.endIndex - 1] > Int.max - - let lhsWords = lhsIsNeg ? (-lhs).words : lhs.words - let rhsWords = rhsIsNeg ? (-rhs).words : rhs.words - - 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 - - var lastJ: Int = 0 - for j in i ..< (lhsWords.count + i) { - var (high, low) = rhsWords[i].multipliedFullWidth(by: lhsWords[j - i]) - var isOverflow: Bool - (digit, isOverflow) = low.addingReportingOverflow(newWords[j]) - if isOverflow { - high += 1 - } - - (digit, isOverflow) = digit.addingReportingOverflow(carry) - if isOverflow { - high += 1 - } - - carry = high - newWords[j] = digit - lastJ = j - } - - if carry != 0 { - let isOverflow: Bool - (digit, isOverflow) = newWords[lastJ + 1].addingReportingOverflow(carry) - if isOverflow { - carry = 1 - } - 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() - } else { - break - } - } - - if lhsIsNeg || rhsIsNeg, !(lhsIsNeg && rhsIsNeg) { - return -BigInt(words: newWords) - } - - return BigInt(words: newWords) - } - - @inlinable - public static func *= (lhs: inout BigInt, rhs: BigInt) { - lhs = lhs * rhs - } - public static func &= (lhs: inout BigInt, rhs: BigInt) { var rhsWords = rhs.words BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) From 819ebd90c4735273b6146c15367902d6022ebf59 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:16:00 +0000 Subject: [PATCH 24/92] [BigInt] Move the SignedNumeric conformance --- Sources/BigInt/BigInt.swift | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 64df66dd..60e07f11 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -281,6 +281,24 @@ extension BigInt: Numeric { } } +extension BigInt: SignedNumeric { + + @inlinable + public static prefix func - (x: BigInt) -> BigInt { + var newWords = x.words.map { ~$0 } + let carry: UInt = 1 + for i in 0 ..< newWords.count { + let isOverflow: Bool + (newWords[i], isOverflow) = newWords[i].addingReportingOverflow(carry) + if !isOverflow { + break + } + } + + return BigInt(words: Words(newWords)) + } +} + extension BigInt { public static func <<= (lhs: inout BigInt, rhs: RHS) where RHS: BinaryInteger { @@ -373,21 +391,6 @@ extension BigInt { return BigInt(words: Words(newWords)) } - @inlinable - public static prefix func - (x: BigInt) -> BigInt { - var newWords = x.words.map { ~$0 } - let carry: UInt = 1 - for i in 0 ..< newWords.count { - let isOverflow: Bool - (newWords[i], isOverflow) = newWords[i].addingReportingOverflow(carry) - if !isOverflow { - break - } - } - - return BigInt(words: Words(newWords)) - } - public init(_ source: T) where T: BinaryFloatingPoint { precondition( !(source.isNaN || source.isInfinite), From 19d3491df470e01346d42a8d7638c9eb75fc4525 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:25:37 +0000 Subject: [PATCH 25/92] [BigInt] Move the BinaryInteger initializers --- Sources/BigInt/BigInt.swift | 115 ++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 60e07f11..4cd1cf7e 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -299,6 +299,65 @@ extension BigInt: SignedNumeric { } } +extension BigInt: BinaryInteger { + + public init?(exactly source: T) where T: BinaryFloatingPoint { + if (source.isNaN || source.isInfinite) || + (source.rounded(.towardZero) != source) { + return nil + } + + self.init(source) + } + + public init(_ source: T) where T: BinaryFloatingPoint { + precondition( + !(source.isNaN || source.isInfinite), + "\(type(of: source)) value cannot be converted to BigInt because it is either infinite or NaN" + ) + + let isNegative = source < 0.0 + var float = isNegative ? -source : source + + if let _ = UInt(exactly: T.greatestFiniteMagnitude) { + words = [UInt(float)] + } else { + var words = Words() + let radix = T(sign: .plus, exponent: T.Exponent(UInt.bitWidth), significand: 1) + repeat { + let digit = UInt(float.truncatingRemainder(dividingBy: radix)) + words.append(digit) + float = (float / radix).rounded(.towardZero) + } while float != 0 + + self.words = words + } + + if isNegative { + self = -self + } + } + + public init(_ source: T) where T: BinaryInteger { + if source >= 0, source < BigInt._digits.count { + self = BigInt._digits[Int(source)] + } else { + words = Words(source.words) + if source > Int.max { + words.append(0) + } + } + } + + public init(clamping source: T) where T: BinaryInteger { + self.init(source) + } + + public init(truncatingIfNeeded source: T) where T: BinaryInteger { + words = Words(source.words) + } +} + extension BigInt { public static func <<= (lhs: inout BigInt, rhs: RHS) where RHS: BinaryInteger { @@ -391,62 +450,6 @@ extension BigInt { return BigInt(words: Words(newWords)) } - public init(_ source: T) where T: BinaryFloatingPoint { - precondition( - !(source.isNaN || source.isInfinite), - "\(type(of: source)) value cannot be converted to BigInt because it is either infinite or NaN" - ) - - let isNegative = source < 0.0 - var float = isNegative ? -source : source - - if let _ = UInt(exactly: T.greatestFiniteMagnitude) { - words = [UInt(float)] - } else { - var words = Words() - let radix = T(sign: .plus, exponent: T.Exponent(UInt.bitWidth), significand: 1) - repeat { - let digit = UInt(float.truncatingRemainder(dividingBy: radix)) - words.append(digit) - float = (float / radix).rounded(.towardZero) - } while float != 0 - - self.words = words - } - - if isNegative { - self = -self - } - } - - public init(_ source: T) where T: BinaryInteger { - if source >= 0, source < BigInt._digits.count { - self = BigInt._digits[Int(source)] - } else { - words = Words(source.words) - if source > Int.max { - words.append(0) - } - } - } - - public init(clamping source: T) where T: BinaryInteger { - self.init(source) - } - - public init(truncatingIfNeeded source: T) where T: BinaryInteger { - words = Words(source.words) - } - - public init?(exactly source: T) where T: BinaryFloatingPoint { - if (source.isNaN || source.isInfinite) || - (source.rounded(.towardZero) != source) { - return nil - } - - self.init(source) - } - public var bitWidth: Int { words.count * MemoryLayout.size * 8 } public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } From 11d5225f3b7edb1e2fd0f3f6daba1f181d94437f Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:26:50 +0000 Subject: [PATCH 26/92] [BigInt] Move the BinaryInteger properties --- Sources/BigInt/BigInt.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 4cd1cf7e..4b67e9b2 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -356,6 +356,10 @@ extension BigInt: BinaryInteger { public init(truncatingIfNeeded source: T) where T: BinaryInteger { words = Words(source.words) } + + public var bitWidth: Int { words.count * MemoryLayout.size * 8 } + + public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } } extension BigInt { @@ -450,10 +454,6 @@ extension BigInt { return BigInt(words: Words(newWords)) } - public var bitWidth: Int { words.count * MemoryLayout.size * 8 } - - public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } - @inlinable public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { let (result, _) = _div(lhs: lhs, rhs: rhs) From acd8568864f749990756216c8ed1dab6af0e5f7e Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:36:23 +0000 Subject: [PATCH 27/92] [BigInt] Move the BinaryInteger operators --- Sources/BigInt/BigInt.swift | 128 ++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 4b67e9b2..badfd7db 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -360,9 +360,68 @@ extension BigInt: BinaryInteger { public var bitWidth: Int { words.count * MemoryLayout.size * 8 } public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } -} -extension BigInt { + @inlinable + public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { + let (result, _) = _div(lhs: lhs, rhs: rhs) + return result + } + + @inlinable + public static func /= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs / rhs + } + + @inlinable + public static func % (lhs: BigInt, rhs: BigInt) -> BigInt { + let (_, result) = _div(lhs: lhs, rhs: rhs) + + return result + } + + @inlinable + public static func %= (lhs: inout BigInt, rhs: BigInt) { + lhs = lhs % rhs + } + + @inlinable + public static prefix func ~ (x: BigInt) -> BigInt { + let newWords = x.words.map { ~$0 } + return BigInt(words: Words(newWords)) + } + + public static func &= (lhs: inout BigInt, rhs: BigInt) { + var rhsWords = rhs.words + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + + for i in 0 ..< rhsWords.count { + lhs.words[i] &= rhsWords[i] + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + public static func |= (lhs: inout BigInt, rhs: BigInt) { + var rhsWords = rhs.words + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + + for i in 0 ..< rhsWords.count { + lhs.words[i] |= rhsWords[i] + } + + BigInt._dropExcessWords(words: &lhs.words) + } + + public static func ^= (lhs: inout BigInt, rhs: BigInt) { + var rhsWords = rhs.words + BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) + + for i in 0 ..< rhsWords.count { + lhs.words[i] &= rhsWords[i] + } + + BigInt._dropExcessWords(words: &lhs.words) + } public static func <<= (lhs: inout BigInt, rhs: RHS) where RHS: BinaryInteger { if rhs.signum() < 0 { @@ -431,6 +490,9 @@ extension BigInt { BigInt._dropExcessWords(words: &lhs.words) } +} + +extension BigInt { @inlinable public func quotientAndRemainder(dividingBy rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { @@ -448,18 +510,6 @@ extension BigInt { return 1 } - @inlinable - public static prefix func ~ (x: BigInt) -> BigInt { - let newWords = x.words.map { ~$0 } - return BigInt(words: Words(newWords)) - } - - @inlinable - public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { - let (result, _) = _div(lhs: lhs, rhs: rhs) - return result - } - private static func findQhat(high: UInt, low: UInt.Magnitude, divisor: UInt, nextVdigit: UInt, nextUdigit: UInt) -> UInt { var (qhat, rhat) = divisor.dividingFullWidth((high, low)) @@ -678,56 +728,6 @@ extension BigInt { return (BigInt(words: quot), BigInt(words: rem)) } - @inlinable - public static func /= (lhs: inout BigInt, rhs: BigInt) { - lhs = lhs / rhs - } - - @inlinable - public static func % (lhs: BigInt, rhs: BigInt) -> BigInt { - let (_, result) = _div(lhs: lhs, rhs: rhs) - - return result - } - - @inlinable - public static func %= (lhs: inout BigInt, rhs: BigInt) { - lhs = lhs % rhs - } - - public static func &= (lhs: inout BigInt, rhs: BigInt) { - var rhsWords = rhs.words - BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) - - for i in 0 ..< rhsWords.count { - lhs.words[i] &= rhsWords[i] - } - - BigInt._dropExcessWords(words: &lhs.words) - } - - public static func |= (lhs: inout BigInt, rhs: BigInt) { - var rhsWords = rhs.words - BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) - - for i in 0 ..< rhsWords.count { - lhs.words[i] |= rhsWords[i] - } - - BigInt._dropExcessWords(words: &lhs.words) - } - - public static func ^= (lhs: inout BigInt, rhs: BigInt) { - var rhsWords = rhs.words - BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords) - - for i in 0 ..< rhsWords.count { - lhs.words[i] &= rhsWords[i] - } - - BigInt._dropExcessWords(words: &lhs.words) - } - private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) { let lhsIsNeg = (lhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 let rhsIsNeg = (rhsWords.last ?? 0) >> (UInt.bitWidth - Int(1)) == 1 From 5be3b4e5263baca3457a804d63f0c9098f02db06 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:38:53 +0000 Subject: [PATCH 28/92] [BigInt] Move the BinaryInteger methods --- Sources/BigInt/BigInt.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index badfd7db..c3af628c 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -490,9 +490,6 @@ extension BigInt: BinaryInteger { BigInt._dropExcessWords(words: &lhs.words) } -} - -extension BigInt { @inlinable public func quotientAndRemainder(dividingBy rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) { @@ -509,6 +506,9 @@ extension BigInt { return 1 } +} + +extension BigInt { private static func findQhat(high: UInt, low: UInt.Magnitude, divisor: UInt, nextVdigit: UInt, nextUdigit: UInt) -> UInt { var (qhat, rhat) = divisor.dividingFullWidth((high, low)) From d15400e6ab75eb70eeba92622872f0fc82afe701 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:39:59 +0000 Subject: [PATCH 29/92] [BigInt] Add the Words typealias --- Sources/BigInt/BigInt.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index c3af628c..507f679c 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -10,7 +10,10 @@ //===----------------------------------------------------------------------===// public struct BigInt: SignedInteger { - public private(set) var words: [UInt] + + public typealias Words = [UInt] + + public private(set) var words: Words @usableFromInline internal init(words: Words) { From 831f06ac78098609c8049111646e1e9503f4fee9 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:44:38 +0000 Subject: [PATCH 30/92] [BigInt] Add the MARK and TODO comments --- Sources/BigInt/BigInt.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 507f679c..6ebce49c 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -1,8 +1,8 @@ -//===--- BigInt.swift ----------------------------------------*- swift -*-===// +//===--- BigInt.swift -----------------------------------------*- swift -*-===// // // This source file is part of the Swift Numerics open source project // -// Copyright (c) 2019 Apple Inc. and the Swift Numerics project authors +// Copyright (c) 2019 - 2020 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 @@ -34,6 +34,11 @@ public struct BigInt: SignedInteger { }() } +// MARK: - Basic Behaviors + +// TODO: extension BigInt: Encodable +// TODO: extension BigInt: Decodable + extension BigInt: Equatable { @inlinable @@ -128,6 +133,8 @@ extension BigInt: LosslessStringConvertible { } } +// MARK: - Numeric Protocols + extension BigInt: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int) { @@ -286,6 +293,8 @@ extension BigInt: Numeric { extension BigInt: SignedNumeric { + // TODO: public mutating func negate() + @inlinable public static prefix func - (x: BigInt) -> BigInt { var newWords = x.words.map { ~$0 } @@ -511,6 +520,8 @@ extension BigInt: BinaryInteger { } } +// MARK: - + extension BigInt { private static func findQhat(high: UInt, low: UInt.Magnitude, divisor: UInt, nextVdigit: UInt, nextUdigit: UInt) -> UInt { From 3c916262e012d5d4c13d46ca7aa0098a3963f29a Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 02:46:47 +0000 Subject: [PATCH 31/92] [BigInt] Update bitWidth and magnitude properties --- Sources/BigInt/BigInt.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 6ebce49c..c533d478 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -220,12 +220,7 @@ extension BigInt: Numeric { self.init(source) } - public var magnitude: BigInt { - if _isNegative { - return -self - } - return self - } + public var magnitude: BigInt { _isNegative ? -self : self } public static func * (lhs: BigInt, rhs: BigInt) -> BigInt { let lhsIsNeg = lhs.words[lhs.words.endIndex - 1] > Int.max @@ -369,7 +364,7 @@ extension BigInt: BinaryInteger { words = Words(source.words) } - public var bitWidth: Int { words.count * MemoryLayout.size * 8 } + public var bitWidth: Int { words.count * UInt.bitWidth } public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } From 4d7e4e9b97658a3d9c1108882152204ae6c897f1 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 14 Jan 2020 04:44:44 +0000 Subject: [PATCH 32/92] [BigInt] Rename private _findQhat method --- Sources/BigInt/BigInt.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index c533d478..9c3c4196 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -519,7 +519,13 @@ extension BigInt: BinaryInteger { extension BigInt { - private static func findQhat(high: UInt, low: UInt.Magnitude, divisor: UInt, nextVdigit: UInt, nextUdigit: UInt) -> UInt { + private static func _findQhat( + high: UInt, + low: UInt.Magnitude, + divisor: UInt, + nextVdigit: UInt, + nextUdigit: UInt + ) -> UInt { var (qhat, rhat) = divisor.dividingFullWidth((high, low)) if high >= divisor { // This means qhat >= b @@ -677,7 +683,12 @@ extension BigInt { var quot = Words(repeating: 0, count: resultSize) for j in (0 ... m).reversed() { - let qhat = findQhat(high: ln[j + n], low: UInt.Magnitude(ln[j + n - 1]), divisor: rn[n - 1], nextVdigit: rn[n - 2], nextUdigit: ln[j + n - 2]) + let qhat = _findQhat( + high: ln[j + n], + low: UInt.Magnitude(ln[j + n - 1]), + divisor: rn[n - 1], + nextVdigit: rn[n - 2], + nextUdigit: ln[j + n - 2]) var carry: UInt = 0 var isOverflow = false From 7c6e2ce0d8a20435946d99e989b4584a589f06d6 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 14 Jan 2020 15:00:38 -0500 Subject: [PATCH 33/92] Explicitly implement `public mutating func negate()` As per the TODO put in by @benrimmington. I'm not 100% sure this is actually better than the default implementation provided by `SignedNumeric`, but there's a chance it is. --- Sources/BigInt/BigInt.swift | 11 ++++++++++- Tests/BigIntTests/BigIntTests.swift | 11 +++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 9c3c4196..fdd6137b 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -288,7 +288,16 @@ extension BigInt: Numeric { extension BigInt: SignedNumeric { - // TODO: public mutating func negate() + public mutating func negate() { + var isOverflow = true + for i in 0 ..< words.count { + if isOverflow { + (words[i], isOverflow) = (~words[i]).addingReportingOverflow(1) + } else { + words[i] = ~words[i] + } + } + } @inlinable public static prefix func - (x: BigInt) -> BigInt { diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index d79da0f6..56357953 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -98,4 +98,15 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(dict[foo]!, "Hello") XCTAssertEqual(dict[bar]!, "World") } + + func testNegation() { + let foo = BigInt("1234567890123456789012345678901234567890")! + let bar = BigInt(0) - foo + + XCTAssertEqual(-foo, bar) + + var baz = foo + baz.negate() + XCTAssertEqual(baz, bar) + } } From 036ab5fe94c520c506e6a9d4ab31146c27c2e0fc Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 14 Jan 2020 15:07:08 -0500 Subject: [PATCH 34/92] Conform to Codable with default implementation Since we have only a single stored property which itself already conforms to `Codable`, there's no need to go down the rabbit hole of explicitly implementing it. If someone wants to do it to produce a more human-readable JSON (eg, as a string instead of an array of integers), then you're welcome to. --- Sources/BigInt/BigInt.swift | 3 +-- Tests/BigIntTests/BigIntTests.swift | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index fdd6137b..3821ad5f 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -36,8 +36,7 @@ public struct BigInt: SignedInteger { // MARK: - Basic Behaviors -// TODO: extension BigInt: Encodable -// TODO: extension BigInt: Decodable +extension BigInt: Codable { } extension BigInt: Equatable { diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 56357953..7f0afc7a 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -109,4 +109,18 @@ final class BigIntTests: XCTestCase { baz.negate() XCTAssertEqual(baz, bar) } + + func testCodable() { + let foo = BigInt("1234567890123456789012345678901234567890")! + guard let fooData = try? JSONEncoder().encode(foo) else { + XCTFail("Failed to encode \(foo)") + return + } + guard let bar = try? JSONDecoder().decode(BigInt.self, from: fooData) else { + XCTFail("Failed to decode \(String(data: fooData, encoding: .utf8) ?? "")") + return + } + + XCTAssertEqual(foo, bar) + } } From f6a3d2d4e4d65bdd0289b0663395d24ad515055c Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 00:17:17 +0000 Subject: [PATCH 35/92] [BigIntTests] Add testMinMaxDescriptions() method This currently fails for: BigInt(Int64.min) - 2 BigInt(Int64.min) - 1 BigInt(Int64.max) + 1 BigInt(Int64.max) + 2 --- Tests/BigIntTests/BigIntTests.swift | 45 +++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 7f0afc7a..fd7619d4 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -1,8 +1,8 @@ -//===--- BigIntTests.swift -----------------------*- swift -*-===// +//===--- BigIntTests.swift ------------------------------------*- swift -*-===// // // This source file is part of the Swift Numerics open source project // -// Copyright (c) 2019 Apple Inc. and the Swift Numerics project authors +// Copyright (c) 2019 - 2020 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 @@ -24,6 +24,7 @@ func fac(_ n: BigInt) -> BigInt { } final class BigIntTests: XCTestCase { + func testExample() throws { let bar = BigInt(exactly: -100) XCTAssertNotNil(bar) @@ -123,4 +124,44 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(foo, bar) } + + func testMinMaxDescriptions() { + let keyValuePairs: KeyValuePairs = [ + BigInt(UInt64.min): [ + "-2", + "-1", + "0", + "1", + "2" + ], + BigInt(UInt64.max): [ + "18446744073709551613", + "18446744073709551614", + "18446744073709551615", + "18446744073709551616", + "18446744073709551617", + ], + BigInt(Int64.min): [ + "-9223372036854775810", + "-9223372036854775809", + "-9223372036854775808", + "-9223372036854775807", + "-9223372036854775806", + ], + BigInt(Int64.max): [ + "9223372036854775805", + "9223372036854775806", + "9223372036854775807", + "9223372036854775808", + "9223372036854775809", + ], + ] + for (expectedNumber, expectedStrings) in keyValuePairs { + let expectedNumbers: [BigInt] = (-2 ... 2).map({ expectedNumber + $0 }) + let actualNumbers: [BigInt] = expectedStrings.compactMap({ BigInt($0) }) + let actualStrings: [String] = actualNumbers.map({ $0.description }) + XCTAssertEqual(actualNumbers, expectedNumbers, "Numbers: actual, expected") + XCTAssertEqual(actualStrings, expectedStrings, "Strings: actual, expected") + } + } } From 3cf0b0cf7400744ffb6f62b8876dbd361d557977 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 00:47:04 +0000 Subject: [PATCH 36/92] [BigInt] Update the LosslessStringConvertible init This performs 6x faster (measured with a 1000-digit string). But it doesn't fix the testMinMaxDescriptions() failures. --- Sources/BigInt/BigInt.swift | 46 +++++++++++++++++++---------- Tests/BigIntTests/BigIntTests.swift | 23 +++++++++++++++ 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 3821ad5f..9eff36da 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -29,9 +29,9 @@ public struct BigInt: SignedInteger { words[words.endIndex - 1] > Int.max } - private static let _digits = { - (0 ... 10).map { BigInt(words: [UInt(bitPattern: $0)]) } - }() + private static let _digits: [BigInt] = (0 ... 36).map { + BigInt(words: [UInt(bitPattern: $0)]) + } } // MARK: - Basic Behaviors @@ -109,26 +109,40 @@ extension BigInt: CustomStringConvertible { extension BigInt: LosslessStringConvertible { public init?(_ description: String) { - let isNegative = description.hasPrefix("-") - let hasPrefixOperator = isNegative || description.hasPrefix("+") + self.init(description, radix: 10) + } - let unprefixedDescription = hasPrefixOperator ? description.dropFirst() : description[...] - guard !unprefixedDescription.isEmpty else { return nil } + public init?(_ description: T, radix: Int = 10) where T: StringProtocol { + precondition(2 ... 36 ~= radix, "Radix not in range 2 ... 36") - let digits = Set("0123456789") - guard unprefixedDescription.allSatisfy({ digits.contains($0) }) else { return nil } + self = 0 - var result: BigInt = 0 - for (i, char) in unprefixedDescription.drop(while: { $0 == "0" }).reversed().enumerated() { - // We're ok to force unwrap here because of the guard above. - result += BigInt(char.wholeNumberValue!) * pow(10, BigInt(i)) + let isNegative = description.hasPrefix("-") + let hasPrefix = isNegative || description.hasPrefix("+") + let utf8 = description.utf8.dropFirst(hasPrefix ? 1 : 0) + guard !utf8.isEmpty else { return nil } + + 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 + } + guard byte < radix else { return nil } + self *= BigInt._digits[radix] + self += BigInt._digits[Int(byte)] } if isNegative { - result = -result + self.negate() } - - words = result.words } } diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index fd7619d4..e37be272 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -164,4 +164,27 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(actualStrings, expectedStrings, "Strings: actual, expected") } } + + func testRandomDescriptions() { + for _ in 0 ..< 100 { + let expectedNumber = BigInt(Int.random(in: .min ... .max)) + for radix in 2 ... 36 { + for uppercase in [false, true] { + let expectedString = String(expectedNumber, + radix: radix, + uppercase: uppercase) + let actualNumber = BigInt(expectedString, radix: radix) + XCTAssertNotNil(actualNumber) + if let actualNumber = actualNumber { + XCTAssertEqual(actualNumber, expectedNumber, + "Numbers: actual, expected") + if radix == 10 { + XCTAssertEqual(actualNumber.description, expectedString, + "Strings: actual, expected") + } + } + } + } + } + } } From 8c378528de4e3c86fe7823d7fa298bf9b922e075 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 00:59:46 +0000 Subject: [PATCH 37/92] [BigInt] Update the Codable conformance --- Sources/BigInt/BigInt.swift | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 9eff36da..ba9eacc0 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -36,8 +36,6 @@ public struct BigInt: SignedInteger { // MARK: - Basic Behaviors -extension BigInt: Codable { } - extension BigInt: Equatable { @inlinable @@ -146,6 +144,28 @@ extension BigInt: LosslessStringConvertible { } } +extension BigInt: Decodable { + + public init(from decoder: Decoder) throws { + let singleValueContainer = try decoder.singleValueContainer() + let description = try singleValueContainer.decode(String.self) + guard let result = BigInt(description) else { + throw DecodingError.dataCorruptedError( + in: singleValueContainer, + debugDescription: "BigInt(\(description.debugDescription)) failed") + } + self = result + } +} + +extension BigInt: Encodable { + + public func encode(to encoder: Encoder) throws { + var singleValueContainer = encoder.singleValueContainer() + try singleValueContainer.encode(description) + } +} + // MARK: - Numeric Protocols extension BigInt: ExpressibleByIntegerLiteral { From 4ec520a7262ff3700888c2c242bc16ae6fc4ec5d Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 03:30:50 +0000 Subject: [PATCH 38/92] [BigIntTests] Update the testCodable() method Top-level JSON fragments might fail on Linux, so encode Range instead. --- Tests/BigIntTests/BigIntTests.swift | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index e37be272..19cb9e9d 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -111,18 +111,17 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(baz, bar) } - func testCodable() { - let foo = BigInt("1234567890123456789012345678901234567890")! - guard let fooData = try? JSONEncoder().encode(foo) else { - XCTFail("Failed to encode \(foo)") - return - } - guard let bar = try? JSONDecoder().decode(BigInt.self, from: fooData) else { - XCTFail("Failed to decode \(String(data: fooData, encoding: .utf8) ?? "")") - return - } - - XCTAssertEqual(foo, bar) + func testCodable() throws { + let lowerBound = BigInt("-1234567890123456789012345678901234567890")! + let upperBound = BigInt("+1234567890123456789012345678901234567890")! + let expectedRange: Range = lowerBound ..< upperBound + + let encoder = JSONEncoder() + let decoder = JSONDecoder() + let data = try encoder.encode(expectedRange) + let actualRange = try decoder.decode(Range.self, from: data) + + XCTAssertEqual(actualRange, expectedRange) } func testMinMaxDescriptions() { From c9d72fc3ae727fe6c5750080981c067b41ccc4d9 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 03:34:14 +0000 Subject: [PATCH 39/92] [BigIntTests] Trim trailing whitespace --- Tests/BigIntTests/BigIntTests.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 19cb9e9d..25fa11ad 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -85,32 +85,32 @@ final class BigIntTests: XCTestCase { let barz = BigInt(1) << 64 XCTAssertEqual(barz, BigInt(UInt.max) + 1) } - - func testHashable(){ + + func testHashable() { let foo = BigInt("1234567890123456789012345678901234567890")! let bar = BigInt("1234567890123456789112345678901234567890")! let baz: BigInt = 153 - + let dict = [ foo: "Hello", bar: "World", baz: "!" ] - + let hash = foo.hashValue print(hash) - + XCTAssertEqual(dict[foo]!, "Hello") XCTAssertEqual(dict[bar]!, "World") } - + func testNegation() { let foo = BigInt("1234567890123456789012345678901234567890")! let bar = BigInt(0) - foo - + XCTAssertEqual(-foo, bar) - + var baz = foo baz.negate() XCTAssertEqual(baz, bar) } - + func testCodable() throws { let lowerBound = BigInt("-1234567890123456789012345678901234567890")! let upperBound = BigInt("+1234567890123456789012345678901234567890")! From 1cb69383a5dbe71f10cf0e02fdb8950f81bb314a Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 03:53:16 +0000 Subject: [PATCH 40/92] [BigIntTests] Wrap to 80 characters --- Tests/BigIntTests/BigIntTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 25fa11ad..4533cb45 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -33,7 +33,8 @@ final class BigIntTests: XCTestCase { XCTAssert(-(bar!) > 0) XCTAssertEqual(-(bar!), BigInt(100)) - XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, BigInt("1234567890123456789012345678901234567890")!) + XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, + BigInt("1234567890123456789012345678901234567890")!) } func testFloatingConversion() { From b39dd05db31c37db6f8cc4ede786330fd66f94de Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 06:44:41 +0000 Subject: [PATCH 41/92] [BigIntTests] Update the testExample() method --- Tests/BigIntTests/BigIntTests.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 4533cb45..ab3dde18 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -25,16 +25,16 @@ func fac(_ n: BigInt) -> BigInt { final class BigIntTests: XCTestCase { - func testExample() throws { + func testExample() { let bar = BigInt(exactly: -100) XCTAssertNotNil(bar) - XCTAssert(bar! < 0) - - XCTAssert(-(bar!) > 0) - XCTAssertEqual(-(bar!), BigInt(100)) - + if let bar = bar { + XCTAssertLessThan(bar, 0) + XCTAssertGreaterThan(-bar, 0) + XCTAssertEqual(-bar, BigInt(100)) + } XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, - BigInt("1234567890123456789012345678901234567890")!) + +BigInt("+1234567890123456789012345678901234567890")!) } func testFloatingConversion() { From 9ddc664ef17c552005062e017e88909b0a5975b8 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Wed, 15 Jan 2020 06:51:21 +0000 Subject: [PATCH 42/92] [BigIntTests] Remove messages from XCTAssert calls --- Tests/BigIntTests/BigIntTests.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index ab3dde18..ae43cfc6 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -160,8 +160,8 @@ final class BigIntTests: XCTestCase { let expectedNumbers: [BigInt] = (-2 ... 2).map({ expectedNumber + $0 }) let actualNumbers: [BigInt] = expectedStrings.compactMap({ BigInt($0) }) let actualStrings: [String] = actualNumbers.map({ $0.description }) - XCTAssertEqual(actualNumbers, expectedNumbers, "Numbers: actual, expected") - XCTAssertEqual(actualStrings, expectedStrings, "Strings: actual, expected") + XCTAssertEqual(actualNumbers, expectedNumbers) + XCTAssertEqual(actualStrings, expectedStrings) } } @@ -176,11 +176,9 @@ final class BigIntTests: XCTestCase { let actualNumber = BigInt(expectedString, radix: radix) XCTAssertNotNil(actualNumber) if let actualNumber = actualNumber { - XCTAssertEqual(actualNumber, expectedNumber, - "Numbers: actual, expected") + XCTAssertEqual(actualNumber, expectedNumber) if radix == 10 { - XCTAssertEqual(actualNumber.description, expectedString, - "Strings: actual, expected") + XCTAssertEqual(actualNumber.description, expectedString) } } } From 0e58379b4f31ada99278f8d7f9d8f0c46f1b2672 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 15 Jan 2020 14:15:27 -0500 Subject: [PATCH 43/92] explicitly call out TAOCP by Knuth --- Sources/BigInt/BigInt.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 3821ad5f..8dff56cf 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -527,6 +527,7 @@ extension BigInt: BinaryInteger { extension BigInt { + /// See _The Art of Computer Programming_ volume 2 by Donald Knuth, Section 4.3.1: The Classical Algorithms private static func _findQhat( high: UInt, low: UInt.Magnitude, @@ -570,6 +571,7 @@ extension BigInt { return qhat } + /// 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!") From 0b121fd7742f4beef522c3651f814de408bef449 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 15 Jan 2020 15:12:31 -0500 Subject: [PATCH 44/92] Fix failing tests (@benrimmington) We were not correctly handling the case where two single-word operands added up to a number > Int.max. Also, negate (and `-`) was not removing excess words, resulting in identical numbers not comparing equal! --- Sources/BigInt/BigInt.swift | 73 +++-------------------------- Tests/BigIntTests/BigIntTests.swift | 12 ++++- 2 files changed, 18 insertions(+), 67 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 9f7327b3..b484ba05 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -195,7 +195,7 @@ extension BigInt: AdditiveArithmetic { let (result, isOverflow) = lhsWord.addingReportingOverflow(rhsWord) - if !isOverflow { + if !isOverflow && result < Int.max { lhs.words[0] = result return } @@ -330,6 +330,8 @@ extension BigInt: SignedNumeric { words[i] = ~words[i] } } + + BigInt._dropExcessWords(words: &words) } @inlinable @@ -344,6 +346,7 @@ extension BigInt: SignedNumeric { } } + BigInt._dropExcessWords(words: &newWords) return BigInt(words: Words(newWords)) } } @@ -631,69 +634,6 @@ extension BigInt { rhsWords.append(0) } - func normalize(x: UInt) -> UInt { - #if arch(arm64) || arch(x86_64) - if x == 0 { - return 64 - } - var x = UInt64(x) - var n: UInt = 0 - if x <= 0x0000_0000_FFFF_FFFF { - n += 32 - x <<= 32 - } - if x <= 0x0000_FFFF_FFFF_FFFF { - n += 16 - x <<= 16 - } - if x <= 0x00FF_FFFF_FFFF_FFFF { - n += 8 - x <<= 8 - } - if x <= 0x0FFF_FFFF_FFFF_FFFF { - n += 4 - x <<= 4 - } - if x <= 0x3FFF_FFFF_FFFF_FFFF { - n += 2 - x <<= 2 - } - if x <= 0x7FFF_FFFF_FFFF_FFFF { - n += 1 - } - - return n - #else - if x == 0 { - return 32 - } - - var x = x - var n: UInt = 0 - if x <= 0x0000_FFFF { - n += 16 - x <<= 16 - } - if x <= 0x00FF_FFFF { - n += 8 - x <<= 8 - } - if x <= 0x0FFF_FFFF { - n += 4 - x <<= 4 - } - if x <= 0x3FFF_FFFF { - n += 2 - x <<= 2 - } - if x <= 0x7FFF_FFFF { - n += 1 - } - - return n - #endif - } - if lhsWords.count < rhsWords.count { for _ in 0 ..< (rhsWords.count - lhsWords.count) { lhsWords.append(0) @@ -705,7 +645,7 @@ extension BigInt { let bitWidth = UInt(UInt.bitWidth) - let s = normalize(x: rhsWords[n - 1]) + let s = UInt(rhsWords[n - 1].leadingZeroBitCount) let rn = UnsafeMutablePointer.allocate(capacity: n) rn.initialize(repeating: 0, count: n) defer { rn.deallocate() } @@ -807,7 +747,8 @@ extension BigInt { } } - private static func _dropExcessWords(words: inout Words) { + @usableFromInline + internal static func _dropExcessWords(words: inout Words) { while words.count > 1, words[words.endIndex - 1] == 0 { if words[words.endIndex - 2] <= Int.max { words.removeLast() diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index ae43cfc6..7995faef 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -160,7 +160,10 @@ final class BigIntTests: XCTestCase { let expectedNumbers: [BigInt] = (-2 ... 2).map({ expectedNumber + $0 }) let actualNumbers: [BigInt] = expectedStrings.compactMap({ BigInt($0) }) let actualStrings: [String] = actualNumbers.map({ $0.description }) - XCTAssertEqual(actualNumbers, expectedNumbers) +// XCTAssertEqual(actualNumbers, expectedNumbers) + for (actualNumber, expectedNumber) in zip(actualNumbers, expectedNumbers) { + XCTAssertEqual(actualNumber, expectedNumber) + } XCTAssertEqual(actualStrings, expectedStrings) } } @@ -185,4 +188,11 @@ final class BigIntTests: XCTestCase { } } } + + func testDivision() { + let foo = BigInt("12345678901234567890123456789012345678901234567890123456789012345678901234567890")! + let bar = BigInt("351235231535161613134135135135")! + let baz = foo / bar + XCTAssertEqual(baz, BigInt("35149318157164029155780432046477458820396117503007")!) + } } From fd8049e481c0e26fcef491cd0e9bacff5f13844f Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 15 Jan 2020 15:25:11 -0500 Subject: [PATCH 45/92] remove debugging code from test --- Tests/BigIntTests/BigIntTests.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 7995faef..29fe1417 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -160,10 +160,7 @@ final class BigIntTests: XCTestCase { let expectedNumbers: [BigInt] = (-2 ... 2).map({ expectedNumber + $0 }) let actualNumbers: [BigInt] = expectedStrings.compactMap({ BigInt($0) }) let actualStrings: [String] = actualNumbers.map({ $0.description }) -// XCTAssertEqual(actualNumbers, expectedNumbers) - for (actualNumber, expectedNumber) in zip(actualNumbers, expectedNumbers) { - XCTAssertEqual(actualNumber, expectedNumber) - } + XCTAssertEqual(actualNumbers, expectedNumbers) XCTAssertEqual(actualStrings, expectedStrings) } } From 830927ecbb2ff44d5b6d3746a148786df8214b7c Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Thu, 16 Jan 2020 12:19:33 +0000 Subject: [PATCH 46/92] [BigIntTests] Rewrite testMinMaxDescriptions() --- Tests/BigIntTests/BigIntTests.swift | 88 ++++++++++++++++------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 29fe1417..866d1836 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -125,44 +125,56 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(actualRange, expectedRange) } - func testMinMaxDescriptions() { - let keyValuePairs: KeyValuePairs = [ - BigInt(UInt64.min): [ - "-2", - "-1", - "0", - "1", - "2" - ], - BigInt(UInt64.max): [ - "18446744073709551613", - "18446744073709551614", - "18446744073709551615", - "18446744073709551616", - "18446744073709551617", - ], - BigInt(Int64.min): [ - "-9223372036854775810", - "-9223372036854775809", - "-9223372036854775808", - "-9223372036854775807", - "-9223372036854775806", - ], - BigInt(Int64.max): [ - "9223372036854775805", - "9223372036854775806", - "9223372036854775807", - "9223372036854775808", - "9223372036854775809", - ], - ] - for (expectedNumber, expectedStrings) in keyValuePairs { - let expectedNumbers: [BigInt] = (-2 ... 2).map({ expectedNumber + $0 }) - let actualNumbers: [BigInt] = expectedStrings.compactMap({ BigInt($0) }) - let actualStrings: [String] = actualNumbers.map({ $0.description }) - XCTAssertEqual(actualNumbers, expectedNumbers) - XCTAssertEqual(actualStrings, expectedStrings) - } + func testCustomStringConvertible() { + XCTAssertEqual("\(BigInt(UInt64.min) - 2)", "-2") + XCTAssertEqual("\(BigInt(UInt64.min) - 1)", "-1") + XCTAssertEqual("\(BigInt(UInt64.min) + 0)", "0") + XCTAssertEqual("\(BigInt(UInt64.min) + 1)", "1") + XCTAssertEqual("\(BigInt(UInt64.min) + 2)", "2") + + XCTAssertEqual("\(BigInt(UInt64.max) - 2)", "18446744073709551613") + XCTAssertEqual("\(BigInt(UInt64.max) - 1)", "18446744073709551614") + XCTAssertEqual("\(BigInt(UInt64.max) + 0)", "18446744073709551615") + XCTAssertEqual("\(BigInt(UInt64.max) + 1)", "18446744073709551616") + XCTAssertEqual("\(BigInt(UInt64.max) + 2)", "18446744073709551617") + + XCTAssertEqual("\(BigInt(Int64.min) - 2)", "-9223372036854775810") + XCTAssertEqual("\(BigInt(Int64.min) - 1)", "-9223372036854775809") + XCTAssertEqual("\(BigInt(Int64.min) + 0)", "-9223372036854775808") + XCTAssertEqual("\(BigInt(Int64.min) + 1)", "-9223372036854775807") + XCTAssertEqual("\(BigInt(Int64.min) + 2)", "-9223372036854775806") + + XCTAssertEqual("\(BigInt(Int64.max) - 2)", "9223372036854775805") + XCTAssertEqual("\(BigInt(Int64.max) - 1)", "9223372036854775806") + XCTAssertEqual("\(BigInt(Int64.max) + 0)", "9223372036854775807") + XCTAssertEqual("\(BigInt(Int64.max) + 1)", "9223372036854775808") + XCTAssertEqual("\(BigInt(Int64.max) + 2)", "9223372036854775809") + } + + func testLosslessStringConvertible() { + XCTAssertEqual(BigInt(UInt64.min) - 2, BigInt("-2")) + XCTAssertEqual(BigInt(UInt64.min) - 1, BigInt("-1")) + XCTAssertEqual(BigInt(UInt64.min) + 0, BigInt("0")) + XCTAssertEqual(BigInt(UInt64.min) + 1, BigInt("1")) + XCTAssertEqual(BigInt(UInt64.min) + 2, BigInt("2")) + + XCTAssertEqual(BigInt(UInt64.max) - 2, BigInt("18446744073709551613")) + XCTAssertEqual(BigInt(UInt64.max) - 1, BigInt("18446744073709551614")) + XCTAssertEqual(BigInt(UInt64.max) + 0, BigInt("18446744073709551615")) + XCTAssertEqual(BigInt(UInt64.max) + 1, BigInt("18446744073709551616")) + XCTAssertEqual(BigInt(UInt64.max) + 2, BigInt("18446744073709551617")) + + XCTAssertEqual(BigInt(Int64.min) - 2, BigInt("-9223372036854775810")) + XCTAssertEqual(BigInt(Int64.min) - 1, BigInt("-9223372036854775809")) + XCTAssertEqual(BigInt(Int64.min) + 0, BigInt("-9223372036854775808")) + XCTAssertEqual(BigInt(Int64.min) + 1, BigInt("-9223372036854775807")) + XCTAssertEqual(BigInt(Int64.min) + 2, BigInt("-9223372036854775806")) + + XCTAssertEqual(BigInt(Int64.max) - 2, BigInt("9223372036854775805")) + XCTAssertEqual(BigInt(Int64.max) - 1, BigInt("9223372036854775806")) + XCTAssertEqual(BigInt(Int64.max) + 0, BigInt("9223372036854775807")) + XCTAssertEqual(BigInt(Int64.max) + 1, BigInt("9223372036854775808")) + XCTAssertEqual(BigInt(Int64.max) + 2, BigInt("9223372036854775809")) } func testRandomDescriptions() { From fe212356fdc6dbde14d207c28d1fb6900c845a3a Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Thu, 16 Jan 2020 12:40:02 +0000 Subject: [PATCH 47/92] [BigIntTests] Rename testRandomDescriptions() --- Tests/BigIntTests/BigIntTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 866d1836..c6cc08df 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -177,7 +177,7 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(BigInt(Int64.max) + 2, BigInt("9223372036854775809")) } - func testRandomDescriptions() { + func testRadicesAndNumerals() { for _ in 0 ..< 100 { let expectedNumber = BigInt(Int.random(in: .min ... .max)) for radix in 2 ... 36 { From 7ae61f775bcc6eb215ade6af3eb3a26ed0269b96 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Thu, 16 Jan 2020 13:04:39 +0000 Subject: [PATCH 48/92] [BigIntTests] Update testLosslessStringConvertible --- Tests/BigIntTests/BigIntTests.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index c6cc08df..686fe881 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -152,6 +152,13 @@ final class BigIntTests: XCTestCase { } func testLosslessStringConvertible() { + XCTAssertNil(BigInt("")) + XCTAssertNil(BigInt("-")) + XCTAssertNil(BigInt("+")) + XCTAssertNil(BigInt("A")) + XCTAssertNil(BigInt(" 0")) + XCTAssertNil(BigInt("0 ")) + XCTAssertEqual(BigInt(UInt64.min) - 2, BigInt("-2")) XCTAssertEqual(BigInt(UInt64.min) - 1, BigInt("-1")) XCTAssertEqual(BigInt(UInt64.min) + 0, BigInt("0")) From 2410717a17ed50ee0872a069bd64938eff2b0727 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Thu, 16 Jan 2020 13:12:29 +0000 Subject: [PATCH 49/92] [BigInt] Update the SignedNumeric prefix operator --- Sources/BigInt/BigInt.swift | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index b484ba05..32d9c4a8 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -336,18 +336,9 @@ extension BigInt: SignedNumeric { @inlinable public static prefix func - (x: BigInt) -> BigInt { - var newWords = x.words.map { ~$0 } - let carry: UInt = 1 - for i in 0 ..< newWords.count { - let isOverflow: Bool - (newWords[i], isOverflow) = newWords[i].addingReportingOverflow(carry) - if !isOverflow { - break - } - } - - BigInt._dropExcessWords(words: &newWords) - return BigInt(words: Words(newWords)) + var result = x + result.negate() + return result } } From a6ecb7442a9d20cf48b2d2379acabb775cc110a7 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Thu, 16 Jan 2020 14:50:19 +0000 Subject: [PATCH 50/92] [BigIntTests] Update testRadicesAndNumerals() --- Tests/BigIntTests/BigIntTests.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 686fe881..62e341db 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -185,26 +185,25 @@ final class BigIntTests: XCTestCase { } func testRadicesAndNumerals() { - for _ in 0 ..< 100 { - let expectedNumber = BigInt(Int.random(in: .min ... .max)) - for radix in 2 ... 36 { - for uppercase in [false, true] { + for radix in 2 ... 36 { + for uppercase in [false, true] { + for _ in 0 ..< 100 { + let expectedNumber = BigInt(Int.random(in: .min ... .max)) let expectedString = String(expectedNumber, radix: radix, uppercase: uppercase) let actualNumber = BigInt(expectedString, radix: radix) XCTAssertNotNil(actualNumber) - if let actualNumber = actualNumber { - XCTAssertEqual(actualNumber, expectedNumber) - if radix == 10 { - XCTAssertEqual(actualNumber.description, expectedString) - } + XCTAssertEqual(actualNumber, expectedNumber) + if radix == 10 { + let actualString = expectedNumber.description + XCTAssertEqual(actualString, expectedString) } } } } } - + func testDivision() { let foo = BigInt("12345678901234567890123456789012345678901234567890123456789012345678901234567890")! let bar = BigInt("351235231535161613134135135135")! From 1ddbbfe0d1730c54d46b908a8be1bca8cd8cd3d2 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Thu, 16 Jan 2020 14:24:24 -0500 Subject: [PATCH 51/92] Rename internal init as per @lorentey --- Sources/BigInt/BigInt.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 32d9c4a8..d5f0e28c 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -16,8 +16,8 @@ public struct BigInt: SignedInteger { public private(set) var words: Words @usableFromInline - internal init(words: Words) { - self.words = words + internal init(_uncheckedWords: Words) { + self.words = _uncheckedWords } public init(bitPattern source: T) where T: BinaryInteger { @@ -30,7 +30,7 @@ public struct BigInt: SignedInteger { } private static let _digits: [BigInt] = (0 ... 36).map { - BigInt(words: [UInt(bitPattern: $0)]) + BigInt(_uncheckedWords: [UInt(bitPattern: $0)]) } } @@ -307,10 +307,10 @@ extension BigInt: Numeric { } if lhsIsNeg || rhsIsNeg, !(lhsIsNeg && rhsIsNeg) { - return -BigInt(words: newWords) + return -BigInt(_uncheckedWords: newWords) } - return BigInt(words: newWords) + return BigInt(_uncheckedWords: newWords) } @inlinable @@ -430,7 +430,7 @@ extension BigInt: BinaryInteger { @inlinable public static prefix func ~ (x: BigInt) -> BigInt { let newWords = x.words.map { ~$0 } - return BigInt(words: Words(newWords)) + return BigInt(_uncheckedWords: Words(newWords)) } public static func &= (lhs: inout BigInt, rhs: BigInt) { @@ -606,7 +606,7 @@ extension BigInt { 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(words: [UInt(bitPattern: quot)]), BigInt(words: [UInt(bitPattern: rem)])) + return (BigInt(_uncheckedWords: [UInt(bitPattern: quot)]), BigInt(_uncheckedWords: [UInt(bitPattern: rem)])) } let lhsIsNeg = lhs._isNegative @@ -720,7 +720,7 @@ extension BigInt { BigInt._dropExcessWords(words: ") BigInt._dropExcessWords(words: &rem) - return (BigInt(words: quot), BigInt(words: rem)) + return (BigInt(_uncheckedWords: quot), BigInt(_uncheckedWords: rem)) } private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) { From 7ca51a3e50f1b203d872c22d546e61737b3260b0 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Thu, 16 Jan 2020 14:25:46 -0500 Subject: [PATCH 52/92] normalize words in the `BinaryInteger` bitPattern init as per @lorentey --- Sources/BigInt/BigInt.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index d5f0e28c..8d330d15 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -22,6 +22,7 @@ public struct BigInt: SignedInteger { public init(bitPattern source: T) where T: BinaryInteger { words = Words(source.words) + BigInt._dropExcessWords(words: &words) } @usableFromInline From fac716fdb2b34ecdb04fc995a8af8698a36a07e6 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Thu, 16 Jan 2020 15:15:37 -0500 Subject: [PATCH 53/92] Fix implementation of `<` Previously, all BigInts were less than themselves. This is no longer true. Discovered thanks to @lorentey and @benrimmington re: discussion on explicit implementation of `!=`. --- Sources/BigInt/BigInt.swift | 8 ++++++-- Tests/BigIntTests/BigIntTests.swift | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 8d330d15..b24ba702 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -77,11 +77,15 @@ extension BigInt: Comparable { } for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { - if lhs.words[i] > rhs.words[i] { return false } + if lhs.words[i] > rhs.words[i] { + return false + } else if lhs.words[i] < rhs.words[i] { + return true + } } } - return true + return false } } diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 62e341db..e860f675 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -111,6 +111,24 @@ final class BigIntTests: XCTestCase { baz.negate() XCTAssertEqual(baz, bar) } + + func testComparable() { + let foo = BigInt("1234567890123456789012345678901234567890")! + let bar = foo * foo + + XCTAssertLessThan(foo, bar) + XCTAssertFalse(foo < foo) + XCTAssertFalse(bar < bar) + XCTAssertFalse(foo > foo) + XCTAssertFalse(bar > bar) + XCTAssertGreaterThan(bar, foo) + + let baz = bar * -1 + + XCTAssertLessThan(baz, foo) + XCTAssertNotEqual(bar, baz) + XCTAssertFalse(baz < baz) + } func testCodable() throws { let lowerBound = BigInt("-1234567890123456789012345678901234567890")! From d36d9ce02eb2ce198a5eab35609a2a5524a263be Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Thu, 16 Jan 2020 17:08:52 -0500 Subject: [PATCH 54/92] Remove superfluous `CustomStringConvertible` impl --- Sources/BigInt/BigInt.swift | 20 -------------------- Tests/BigIntTests/BigIntTests.swift | 5 +++++ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index b24ba702..82f10dbb 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -89,26 +89,6 @@ extension BigInt: Comparable { } } -extension BigInt: CustomStringConvertible { - - public var description: String { - var result = "" - - if words.count == 1 { - return Int64(bitPattern: UInt64(words[0])).description - } else { - var next = abs(self) - while next != 0 { - let digit: BigInt - (next, digit) = BigInt._div(lhs: next, rhs: 10) - result += "\(digit.words[0])" - } - } - - return (self < 0 ? "-" : "") + String(result.reversed()) - } -} - extension BigInt: LosslessStringConvertible { public init?(_ description: String) { diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index e860f675..ddbeff12 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -112,6 +112,11 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(baz, bar) } + func testThing() { + let foo = BigInt("-1234567890123456789012345678901234567890")! + XCTAssertTrue(foo != 0) + } + func testComparable() { let foo = BigInt("1234567890123456789012345678901234567890")! let bar = foo * foo From 6f4374cd997e93e51412c37c6710522ac9ae8036 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Fri, 17 Jan 2020 07:32:52 +0000 Subject: [PATCH 55/92] [BigIntTests] Add min and max Int1024 descriptions --- Tests/BigIntTests/BigIntTests.swift | 37 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index ddbeff12..ae9ea1af 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -25,6 +25,24 @@ func fac(_ n: BigInt) -> BigInt { final class BigIntTests: XCTestCase { + static let descriptionInt1024Min: String = + """ + -89884656743115795386465259539451236680898848947115328636715040578866337902\ + 750481566354238661203768010560056939935696678829394884407208311246423715319\ + 737062188883946712432742638151109800623047059726541476042502884419075341171\ + 231440736956555270413618581675255342293149119973622969239858152417678164812\ + 112068608 + """ + + static let descriptionInt1024Max: String = + """ + 89884656743115795386465259539451236680898848947115328636715040578866337902\ + 750481566354238661203768010560056939935696678829394884407208311246423715319\ + 737062188883946712432742638151109800623047059726541476042502884419075341171\ + 231440736956555270413618581675255342293149119973622969239858152417678164812\ + 112068607 + """ + func testExample() { let bar = BigInt(exactly: -100) XCTAssertNotNil(bar) @@ -111,25 +129,20 @@ final class BigIntTests: XCTestCase { baz.negate() XCTAssertEqual(baz, bar) } - - func testThing() { - let foo = BigInt("-1234567890123456789012345678901234567890")! - XCTAssertTrue(foo != 0) - } - + func testComparable() { let foo = BigInt("1234567890123456789012345678901234567890")! let bar = foo * foo - + XCTAssertLessThan(foo, bar) XCTAssertFalse(foo < foo) XCTAssertFalse(bar < bar) XCTAssertFalse(foo > foo) XCTAssertFalse(bar > bar) XCTAssertGreaterThan(bar, foo) - + let baz = bar * -1 - + XCTAssertLessThan(baz, foo) XCTAssertNotEqual(bar, baz) XCTAssertFalse(baz < baz) @@ -172,6 +185,9 @@ final class BigIntTests: XCTestCase { XCTAssertEqual("\(BigInt(Int64.max) + 0)", "9223372036854775807") XCTAssertEqual("\(BigInt(Int64.max) + 1)", "9223372036854775808") XCTAssertEqual("\(BigInt(Int64.max) + 2)", "9223372036854775809") + + XCTAssertEqual("\(-(BigInt(1) << 1023))", Self.descriptionInt1024Min) + XCTAssertEqual("\(+(BigInt(1) << 1023) - 1)", Self.descriptionInt1024Max) } func testLosslessStringConvertible() { @@ -205,6 +221,9 @@ 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)) } func testRadicesAndNumerals() { From b03af80076bc65eb5387928fb1e1bf1d11c163b6 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Fri, 17 Jan 2020 07:33:36 +0000 Subject: [PATCH 56/92] [BigInt] Change `self = -self` to `self.negate()` --- Sources/BigInt/BigInt.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 82f10dbb..17499caf 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -315,7 +315,7 @@ extension BigInt: SignedNumeric { words[i] = ~words[i] } } - + BigInt._dropExcessWords(words: &words) } @@ -362,7 +362,7 @@ extension BigInt: BinaryInteger { } if isNegative { - self = -self + self.negate() } } From e632de88c4d7ad97f6b65f4ad2b60977e8d5ab9a Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Fri, 17 Jan 2020 13:51:21 +0000 Subject: [PATCH 57/92] [BigIntTests] Add testFactorial() --- Tests/BigIntTests/BigIntTests.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index ae9ea1af..b5a8b2a2 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -25,6 +25,7 @@ func fac(_ n: BigInt) -> BigInt { final class BigIntTests: XCTestCase { + /// Python: `bitWidth = 1024; -(2 ** (bitWidth - 1))` static let descriptionInt1024Min: String = """ -89884656743115795386465259539451236680898848947115328636715040578866337902\ @@ -34,6 +35,7 @@ final class BigIntTests: XCTestCase { 112068608 """ + /// Python: `bitWidth = 1024; (2 ** (bitWidth - 1)) - 1` static let descriptionInt1024Max: String = """ 89884656743115795386465259539451236680898848947115328636715040578866337902\ @@ -43,6 +45,29 @@ final class BigIntTests: XCTestCase { 112068607 """ + /// Python: `numpy.base_repr(math.factorial(512), base=36)` + static let descriptionFactorial512Radix36: String = + """ + 7FA5Y7EHR9XHMQ519MBHGYOF8XDYMUX8OZHO9WF1KCM0SSPXV2V45UA73BAFRYM2PFB8CZLTODV\ + OS3QWA7PYFJ7WAFBI4VF371E27N6XZ4LGWHMFDS4ZH1O3DGNFG4YABUE1G90ORGRTIOGSQVZLSQ\ + 4TKHKHIQ262JVQ0J6LSKAPN5I65AJD33XODVHRNWJ1VSO0Q2FBOUNCPGQG2SFQKR17XHF1OLTV2\ + MVNJVTDAIYWVJ9ZH7KXT0EPS00IGIVC7MNCU25HFWE37KNMSJQUL5ALUCE5XZVPFQCQGEVEB93B\ + GA8LKG67PVZ7Q9QMQKIVNIMPT2973MVDTD1D1A0A4QT6NBZYR0TGSZXBV1PD0CHW4SKZJSLBS4Z\ + W5WCKDY8BCQCE17KKADVLCTVSQL1BZ2PL52DDPB8S5L0ZEG2ZAZF9V4TNJJNO1D9U9JU7B264QZ\ + 5GLHC3Q0Y3BTECGTI8GRENQ2FV4HSEZKPM9OG302KLSY9MBCSOO0FN229AST84TT87LYWOOS71C\ + 54RPJ9RTO9875Z9DE3HPH89EW5I3SV219O04UU09A4KME7DD7IH49ABO79NR4EXFX1VLL4YOHSA\ + 7AHD1LS5YKZ66F4UPG0RCBGG000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000 + """ + + func testFactorial() { + let factorial512 = fac(BigInt(512)) + XCTAssertEqual(String(factorial512, radix: 36, uppercase: true), + Self.descriptionFactorial512Radix36) + XCTAssertEqual(BigInt(Self.descriptionFactorial512Radix36, radix: 36), + factorial512) + } + func testExample() { let bar = BigInt(exactly: -100) XCTAssertNotNil(bar) From 937c292898f20509f29ec30404de1da5c6bcf0dd Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sat, 18 Jan 2020 19:38:05 +0000 Subject: [PATCH 58/92] [BigIntTests] Add testFloatingPoint_...() methods The testFloatingPoint_greatestFiniteMagnitude() method currently fails. --- Tests/BigIntTests/BigIntTests.swift | 131 +++++++++++++++++++++++++--- 1 file changed, 119 insertions(+), 12 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index b5a8b2a2..9c826c96 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -12,6 +12,8 @@ import BigInt import XCTest +typealias FloatXX = CLongDouble // Float64 or Float80 + func fac(_ n: BigInt) -> BigInt { var result: BigInt = 1 var count = n @@ -45,6 +47,16 @@ final class BigIntTests: XCTestCase { 112068607 """ + /// Python: `int(sys.float_info.max)` + static let descriptionFloat64_greatestFiniteMagnitude: String = + """ + 179769313486231570814527423731704356798070567525844996598917476803157260780\ + 028538760589558632766878171540458953514382464234321326889464182768467546703\ + 537516986049910576551282076245490090389328944075868508455133942304583236903\ + 222948165808559332123348274797826204144723168738177180919299881250404026184\ + 124858368 + """ + /// Python: `numpy.base_repr(math.factorial(512), base=36)` static let descriptionFactorial512Radix36: String = """ @@ -80,19 +92,111 @@ final class BigIntTests: XCTestCase { +BigInt("+1234567890123456789012345678901234567890")!) } - func testFloatingConversion() { - let bar = BigInt(3.14159) - XCTAssertEqual(bar, BigInt(3)) - let foo = BigInt(exactly: 3.14159) - XCTAssertNil(foo) + func testFloatingPoint_greatestFiniteMagnitude() { + XCTAssertEqual(BigInt(exactly: -Float64.greatestFiniteMagnitude), + BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + XCTAssertEqual(BigInt(exactly: +Float64.greatestFiniteMagnitude), + BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + + XCTAssertEqual(BigInt(-Float64.greatestFiniteMagnitude), + BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + XCTAssertEqual(BigInt(+Float64.greatestFiniteMagnitude), + BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + } + + func testFloatingPoint_infinity() { + XCTAssertNil(BigInt(exactly: -Float32.infinity)) + XCTAssertNil(BigInt(exactly: -Float64.infinity)) + XCTAssertNil(BigInt(exactly: -FloatXX.infinity)) + + XCTAssertNil(BigInt(exactly: +Float32.infinity)) + XCTAssertNil(BigInt(exactly: +Float64.infinity)) + XCTAssertNil(BigInt(exactly: +FloatXX.infinity)) + } + + func testFloatingPoint_leastNonzeroMagnitude() { + XCTAssertNil(BigInt(exactly: -Float32.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: -Float64.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: -FloatXX.leastNonzeroMagnitude)) + + XCTAssertNil(BigInt(exactly: +Float32.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: +Float64.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: +FloatXX.leastNonzeroMagnitude)) + + XCTAssertEqual(BigInt(-Float32.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(-Float64.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(-FloatXX.leastNonzeroMagnitude), 0) + + XCTAssertEqual(BigInt(+Float32.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(+Float64.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(+FloatXX.leastNonzeroMagnitude), 0) + } + + func testFloatingPoint_leastNormalMagnitude() { + XCTAssertNil(BigInt(exactly: -Float32.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: -Float64.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: -FloatXX.leastNormalMagnitude)) + + XCTAssertNil(BigInt(exactly: +Float32.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: +Float64.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: +FloatXX.leastNormalMagnitude)) + + XCTAssertEqual(BigInt(-Float32.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(-Float64.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(-FloatXX.leastNormalMagnitude), 0) + + XCTAssertEqual(BigInt(+Float32.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(+Float64.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(+FloatXX.leastNormalMagnitude), 0) + } + + func testFloatingPoint_nan() { + XCTAssertNil(BigInt(exactly: Float32.nan)) + XCTAssertNil(BigInt(exactly: Float64.nan)) + XCTAssertNil(BigInt(exactly: FloatXX.nan)) + } + + func testFloatingPoint_pi() { + XCTAssertNil(BigInt(exactly: -Float32.pi)) + XCTAssertNil(BigInt(exactly: -Float64.pi)) + XCTAssertNil(BigInt(exactly: -FloatXX.pi)) + + XCTAssertNil(BigInt(exactly: +Float32.pi)) + XCTAssertNil(BigInt(exactly: +Float64.pi)) + XCTAssertNil(BigInt(exactly: +FloatXX.pi)) + + XCTAssertEqual(BigInt(-Float32.pi), -3) + XCTAssertEqual(BigInt(-Float64.pi), -3) + XCTAssertEqual(BigInt(-FloatXX.pi), -3) + + XCTAssertEqual(BigInt(+Float32.pi), +3) + XCTAssertEqual(BigInt(+Float64.pi), +3) + XCTAssertEqual(BigInt(+FloatXX.pi), +3) + } + + func testFloatingPoint_random() { + for _ in 0 ..< 1000 { + let random = Float64.random(in: -0x1p52 ... +0x1p52) + XCTAssertEqual(BigInt(random), BigInt(Int64(random))) + } + } + + func testFloatingPoint_zero() { + XCTAssertEqual(BigInt(exactly: -Float32.zero), 0) + XCTAssertEqual(BigInt(exactly: -Float64.zero), 0) + XCTAssertEqual(BigInt(exactly: -FloatXX.zero), 0) - let baz = BigInt(exactly: 2.4e39) - XCTAssertNotNil(baz) - let equal = (baz ?? 0) / BigInt(1e38) == BigInt(24) - XCTAssertEqual(equal, true) + XCTAssertEqual(BigInt(exactly: +Float32.zero), 0) + XCTAssertEqual(BigInt(exactly: +Float64.zero), 0) + XCTAssertEqual(BigInt(exactly: +FloatXX.zero), 0) - let infinite = BigInt(exactly: Double.infinity) - XCTAssertNil(infinite) + XCTAssertEqual(BigInt(-Float32.zero), 0) + XCTAssertEqual(BigInt(-Float64.zero), 0) + XCTAssertEqual(BigInt(-FloatXX.zero), 0) + + XCTAssertEqual(BigInt(+Float32.zero), 0) + XCTAssertEqual(BigInt(+Float64.zero), 0) + XCTAssertEqual(BigInt(+FloatXX.zero), 0) } func testUIntConversion() { @@ -260,7 +364,6 @@ final class BigIntTests: XCTestCase { radix: radix, uppercase: uppercase) let actualNumber = BigInt(expectedString, radix: radix) - XCTAssertNotNil(actualNumber) XCTAssertEqual(actualNumber, expectedNumber) if radix == 10 { let actualString = expectedNumber.description @@ -276,5 +379,9 @@ final class BigIntTests: XCTestCase { let bar = BigInt("351235231535161613134135135135")! let baz = foo / bar XCTAssertEqual(baz, BigInt("35149318157164029155780432046477458820396117503007")!) + + XCTAssertNotNil(BigInt(exactly: 2.4e39)) + XCTAssertNotNil(BigInt(exactly: 1e38)) + XCTAssertEqual(BigInt(2.4e39) / BigInt(1e38), BigInt(24)) } } From 1b733de54a269f3c6576c8a265f7d88867718354 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sat, 18 Jan 2020 19:44:27 +0000 Subject: [PATCH 59/92] [BigInt] Fix initializers with BinaryFloatingPoint --- Sources/BigInt/BigInt.swift | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 17499caf..ab51d60d 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -330,37 +330,29 @@ extension BigInt: SignedNumeric { extension BigInt: BinaryInteger { public init?(exactly source: T) where T: BinaryFloatingPoint { - if (source.isNaN || source.isInfinite) || - (source.rounded(.towardZero) != source) { + guard source.isFinite, source == source.rounded(.towardZero) else { return nil } - self.init(source) } public init(_ source: T) where T: BinaryFloatingPoint { - precondition( - !(source.isNaN || source.isInfinite), - "\(type(of: source)) value cannot be converted to BigInt because it is either infinite or NaN" - ) - - let isNegative = source < 0.0 - var float = isNegative ? -source : source + precondition(source.isFinite, "BigInt(\(source)) failed") + precondition(T.significandBitCount < .max) - if let _ = UInt(exactly: T.greatestFiniteMagnitude) { - words = [UInt(float)] - } else { - var words = Words() - let radix = T(sign: .plus, exponent: T.Exponent(UInt.bitWidth), significand: 1) - repeat { - let digit = UInt(float.truncatingRemainder(dividingBy: radix)) - words.append(digit) - float = (float / radix).rounded(.towardZero) - } while float != 0 + let source = source.rounded(.towardZero) + let isNegative = source < 0 - self.words = words + guard !source.isZero else { + self = 0 + return } + self = BigInt(source.significandBitPattern) + self |= BigInt(1) << T.significandBitCount + self *= BigInt(1) << source.exponent + self >>= T.significandBitCount + if isNegative { self.negate() } From 506ea363314afdf3396b9cf28223954e1bfb75aa Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sun, 19 Jan 2020 09:37:20 +0000 Subject: [PATCH 60/92] [BigIntTests] Update testFloatingPoint_random() --- Tests/BigIntTests/BigIntTests.swift | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 9c826c96..ae8236ff 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -175,9 +175,28 @@ final class BigIntTests: XCTestCase { } func testFloatingPoint_random() { - for _ in 0 ..< 1000 { - let random = Float64.random(in: -0x1p52 ... +0x1p52) - XCTAssertEqual(BigInt(random), BigInt(Int64(random))) + for _ in 0 ..< 100 { + let small = Float32.random(in: -10 ... +10) + XCTAssertEqual(BigInt(small), BigInt(Int64(small))) + + let large = Float32.random(in: -0x1p23 ... +0x1p23) + XCTAssertEqual(BigInt(large), BigInt(Int64(large))) + } + + for _ in 0 ..< 100 { + let small = Float64.random(in: -10 ... +10) + XCTAssertEqual(BigInt(small), BigInt(Int64(small))) + + let large = Float64.random(in: -0x1p52 ... +0x1p52) + XCTAssertEqual(BigInt(large), BigInt(Int64(large))) + } + + for _ in 0 ..< 100 { + let small = FloatXX.random(in: -10 ... +10) + XCTAssertEqual(BigInt(small), BigInt(Int64(small))) + + let large = FloatXX.random(in: -0x1p52 ... +0x1p52) + XCTAssertEqual(BigInt(large), BigInt(Int64(large))) } } From 98023c3575cf2da7fa8f3dafd987dd674f25084e Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sun, 19 Jan 2020 10:31:39 +0000 Subject: [PATCH 61/92] [BigInt] Combine `*=` and `<<` into simpler `<<=` --- Sources/BigInt/BigInt.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index ab51d60d..bed183ab 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -350,7 +350,7 @@ extension BigInt: BinaryInteger { self = BigInt(source.significandBitPattern) self |= BigInt(1) << T.significandBitCount - self *= BigInt(1) << source.exponent + self <<= source.exponent self >>= T.significandBitCount if isNegative { From cfc2cd7c9770fe9f500b513eeb9a444608f88f07 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sun, 19 Jan 2020 12:32:11 +0000 Subject: [PATCH 62/92] [BigIntTests] Add `measure {}` to testFactorial() --- Tests/BigIntTests/BigIntTests.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index ae8236ff..c05ba11c 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -73,11 +73,18 @@ final class BigIntTests: XCTestCase { """ func testFactorial() { - let factorial512 = fac(BigInt(512)) - XCTAssertEqual(String(factorial512, radix: 36, uppercase: true), - Self.descriptionFactorial512Radix36) - XCTAssertEqual(BigInt(Self.descriptionFactorial512Radix36, radix: 36), - factorial512) + var expectedNumber: BigInt! + var actualNumber: BigInt! + var actualString: String! + + measure { + expectedNumber = BigInt(Self.descriptionFactorial512Radix36, radix: 36) + actualNumber = fac(BigInt(512)) + actualString = String(actualNumber, radix: 36, uppercase: true) + } + + XCTAssertEqual(actualNumber, expectedNumber) + XCTAssertEqual(actualString, Self.descriptionFactorial512Radix36) } func testExample() { From 05a45984d9631d3ae0cca4b8b3194997d2c5148d Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sun, 19 Jan 2020 19:48:07 +0000 Subject: [PATCH 63/92] [BigInt] Update signum(), add testSignum() --- Sources/BigInt/BigInt.swift | 10 +--------- Tests/BigIntTests/BigIntTests.swift | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index bed183ab..ba114ca2 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -517,15 +517,7 @@ extension BigInt: BinaryInteger { } @inlinable - public func signum() -> BigInt { - if _isNegative { - return -1 - } else if self == 0 { - return 0 - } - - return 1 - } + public func signum() -> BigInt { _isNegative ? -1 : (self == 0) ? 0 : 1 } } // MARK: - diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index c05ba11c..4aa85194 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -410,4 +410,20 @@ final class BigIntTests: XCTestCase { XCTAssertNotNil(BigInt(exactly: 1e38)) XCTAssertEqual(BigInt(2.4e39) / BigInt(1e38), BigInt(24)) } + + func testSignum() { + XCTAssertEqual(BigInt(-0x1p1023).signum(), -1) + XCTAssertEqual(BigInt(Int64.min).signum(), -1) + XCTAssertEqual(BigInt(Int32.min).signum(), -1) + XCTAssertEqual(BigInt(Int16.min).signum(), -1) + XCTAssertEqual(BigInt(Int8.min).signum(), -1) + XCTAssertEqual(BigInt(-1).signum(), -1) + XCTAssertEqual(BigInt(0).signum(), 0) + XCTAssertEqual(BigInt(+1).signum(), +1) + XCTAssertEqual(BigInt(Int8.max).signum(), +1) + XCTAssertEqual(BigInt(Int16.max).signum(), +1) + XCTAssertEqual(BigInt(Int32.max).signum(), +1) + XCTAssertEqual(BigInt(Int64.max).signum(), +1) + XCTAssertEqual(BigInt(+0x1p1023).signum(), +1) + } } From 01c30961ef30458d2648438f6fd515810cb32763 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sun, 19 Jan 2020 20:31:22 +0000 Subject: [PATCH 64/92] [BigIntTests] Move the typealias of CLongDouble --- Tests/BigIntTests/BigIntTests.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 4aa85194..eadf8f2a 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -12,8 +12,6 @@ import BigInt import XCTest -typealias FloatXX = CLongDouble // Float64 or Float80 - func fac(_ n: BigInt) -> BigInt { var result: BigInt = 1 var count = n @@ -27,6 +25,9 @@ func fac(_ n: BigInt) -> BigInt { final class BigIntTests: XCTestCase { + /// Float64 or Float80 (or Float128) + typealias FloatXX = CLongDouble + /// Python: `bitWidth = 1024; -(2 ** (bitWidth - 1))` static let descriptionInt1024Min: String = """ From cfc63e8adcb195f65582b239042a7618d65330ce Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Mon, 20 Jan 2020 10:30:16 +0000 Subject: [PATCH 65/92] [BigIntTests] Move testFloatingPoint_...() methods --- Tests/BigIntTests/BigIntTests.swift | 258 ++++++++++++++-------------- 1 file changed, 132 insertions(+), 126 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index eadf8f2a..9a846696 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -100,132 +100,6 @@ final class BigIntTests: XCTestCase { +BigInt("+1234567890123456789012345678901234567890")!) } - func testFloatingPoint_greatestFiniteMagnitude() { - XCTAssertEqual(BigInt(exactly: -Float64.greatestFiniteMagnitude), - BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) - XCTAssertEqual(BigInt(exactly: +Float64.greatestFiniteMagnitude), - BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) - - XCTAssertEqual(BigInt(-Float64.greatestFiniteMagnitude), - BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) - XCTAssertEqual(BigInt(+Float64.greatestFiniteMagnitude), - BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) - } - - func testFloatingPoint_infinity() { - XCTAssertNil(BigInt(exactly: -Float32.infinity)) - XCTAssertNil(BigInt(exactly: -Float64.infinity)) - XCTAssertNil(BigInt(exactly: -FloatXX.infinity)) - - XCTAssertNil(BigInt(exactly: +Float32.infinity)) - XCTAssertNil(BigInt(exactly: +Float64.infinity)) - XCTAssertNil(BigInt(exactly: +FloatXX.infinity)) - } - - func testFloatingPoint_leastNonzeroMagnitude() { - XCTAssertNil(BigInt(exactly: -Float32.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: -Float64.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: -FloatXX.leastNonzeroMagnitude)) - - XCTAssertNil(BigInt(exactly: +Float32.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: +Float64.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: +FloatXX.leastNonzeroMagnitude)) - - XCTAssertEqual(BigInt(-Float32.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(-Float64.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(-FloatXX.leastNonzeroMagnitude), 0) - - XCTAssertEqual(BigInt(+Float32.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(+Float64.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(+FloatXX.leastNonzeroMagnitude), 0) - } - - func testFloatingPoint_leastNormalMagnitude() { - XCTAssertNil(BigInt(exactly: -Float32.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: -Float64.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: -FloatXX.leastNormalMagnitude)) - - XCTAssertNil(BigInt(exactly: +Float32.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: +Float64.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: +FloatXX.leastNormalMagnitude)) - - XCTAssertEqual(BigInt(-Float32.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(-Float64.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(-FloatXX.leastNormalMagnitude), 0) - - XCTAssertEqual(BigInt(+Float32.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(+Float64.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(+FloatXX.leastNormalMagnitude), 0) - } - - func testFloatingPoint_nan() { - XCTAssertNil(BigInt(exactly: Float32.nan)) - XCTAssertNil(BigInt(exactly: Float64.nan)) - XCTAssertNil(BigInt(exactly: FloatXX.nan)) - } - - func testFloatingPoint_pi() { - XCTAssertNil(BigInt(exactly: -Float32.pi)) - XCTAssertNil(BigInt(exactly: -Float64.pi)) - XCTAssertNil(BigInt(exactly: -FloatXX.pi)) - - XCTAssertNil(BigInt(exactly: +Float32.pi)) - XCTAssertNil(BigInt(exactly: +Float64.pi)) - XCTAssertNil(BigInt(exactly: +FloatXX.pi)) - - XCTAssertEqual(BigInt(-Float32.pi), -3) - XCTAssertEqual(BigInt(-Float64.pi), -3) - XCTAssertEqual(BigInt(-FloatXX.pi), -3) - - XCTAssertEqual(BigInt(+Float32.pi), +3) - XCTAssertEqual(BigInt(+Float64.pi), +3) - XCTAssertEqual(BigInt(+FloatXX.pi), +3) - } - - func testFloatingPoint_random() { - for _ in 0 ..< 100 { - let small = Float32.random(in: -10 ... +10) - XCTAssertEqual(BigInt(small), BigInt(Int64(small))) - - let large = Float32.random(in: -0x1p23 ... +0x1p23) - XCTAssertEqual(BigInt(large), BigInt(Int64(large))) - } - - for _ in 0 ..< 100 { - let small = Float64.random(in: -10 ... +10) - XCTAssertEqual(BigInt(small), BigInt(Int64(small))) - - let large = Float64.random(in: -0x1p52 ... +0x1p52) - XCTAssertEqual(BigInt(large), BigInt(Int64(large))) - } - - for _ in 0 ..< 100 { - let small = FloatXX.random(in: -10 ... +10) - XCTAssertEqual(BigInt(small), BigInt(Int64(small))) - - let large = FloatXX.random(in: -0x1p52 ... +0x1p52) - XCTAssertEqual(BigInt(large), BigInt(Int64(large))) - } - } - - func testFloatingPoint_zero() { - XCTAssertEqual(BigInt(exactly: -Float32.zero), 0) - XCTAssertEqual(BigInt(exactly: -Float64.zero), 0) - XCTAssertEqual(BigInt(exactly: -FloatXX.zero), 0) - - XCTAssertEqual(BigInt(exactly: +Float32.zero), 0) - XCTAssertEqual(BigInt(exactly: +Float64.zero), 0) - XCTAssertEqual(BigInt(exactly: +FloatXX.zero), 0) - - XCTAssertEqual(BigInt(-Float32.zero), 0) - XCTAssertEqual(BigInt(-Float64.zero), 0) - XCTAssertEqual(BigInt(-FloatXX.zero), 0) - - XCTAssertEqual(BigInt(+Float32.zero), 0) - XCTAssertEqual(BigInt(+Float64.zero), 0) - XCTAssertEqual(BigInt(+FloatXX.zero), 0) - } - func testUIntConversion() { let foo = BigInt(UInt.max) XCTAssertNotEqual(foo, BigInt(-1)) @@ -304,6 +178,8 @@ final class BigIntTests: XCTestCase { XCTAssertFalse(baz < baz) } + // MARK: - Converting to/from textual representations + func testCodable() throws { let lowerBound = BigInt("-1234567890123456789012345678901234567890")! let upperBound = BigInt("+1234567890123456789012345678901234567890")! @@ -401,6 +277,136 @@ final class BigIntTests: XCTestCase { } } + // MARK: - Converting from floating-point binary types + + func testFloatingPoint_greatestFiniteMagnitude() { + XCTAssertEqual(BigInt(exactly: -Float64.greatestFiniteMagnitude), + BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + XCTAssertEqual(BigInt(exactly: +Float64.greatestFiniteMagnitude), + BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + + XCTAssertEqual(BigInt(-Float64.greatestFiniteMagnitude), + BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + XCTAssertEqual(BigInt(+Float64.greatestFiniteMagnitude), + BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + } + + func testFloatingPoint_infinity() { + XCTAssertNil(BigInt(exactly: -Float32.infinity)) + XCTAssertNil(BigInt(exactly: -Float64.infinity)) + XCTAssertNil(BigInt(exactly: -FloatXX.infinity)) + + XCTAssertNil(BigInt(exactly: +Float32.infinity)) + XCTAssertNil(BigInt(exactly: +Float64.infinity)) + XCTAssertNil(BigInt(exactly: +FloatXX.infinity)) + } + + func testFloatingPoint_leastNonzeroMagnitude() { + XCTAssertNil(BigInt(exactly: -Float32.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: -Float64.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: -FloatXX.leastNonzeroMagnitude)) + + XCTAssertNil(BigInt(exactly: +Float32.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: +Float64.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: +FloatXX.leastNonzeroMagnitude)) + + XCTAssertEqual(BigInt(-Float32.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(-Float64.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(-FloatXX.leastNonzeroMagnitude), 0) + + XCTAssertEqual(BigInt(+Float32.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(+Float64.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(+FloatXX.leastNonzeroMagnitude), 0) + } + + func testFloatingPoint_leastNormalMagnitude() { + XCTAssertNil(BigInt(exactly: -Float32.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: -Float64.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: -FloatXX.leastNormalMagnitude)) + + XCTAssertNil(BigInt(exactly: +Float32.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: +Float64.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: +FloatXX.leastNormalMagnitude)) + + XCTAssertEqual(BigInt(-Float32.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(-Float64.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(-FloatXX.leastNormalMagnitude), 0) + + XCTAssertEqual(BigInt(+Float32.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(+Float64.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(+FloatXX.leastNormalMagnitude), 0) + } + + func testFloatingPoint_nan() { + XCTAssertNil(BigInt(exactly: Float32.nan)) + XCTAssertNil(BigInt(exactly: Float64.nan)) + XCTAssertNil(BigInt(exactly: FloatXX.nan)) + } + + func testFloatingPoint_pi() { + XCTAssertNil(BigInt(exactly: -Float32.pi)) + XCTAssertNil(BigInt(exactly: -Float64.pi)) + XCTAssertNil(BigInt(exactly: -FloatXX.pi)) + + XCTAssertNil(BigInt(exactly: +Float32.pi)) + XCTAssertNil(BigInt(exactly: +Float64.pi)) + XCTAssertNil(BigInt(exactly: +FloatXX.pi)) + + XCTAssertEqual(BigInt(-Float32.pi), -3) + XCTAssertEqual(BigInt(-Float64.pi), -3) + XCTAssertEqual(BigInt(-FloatXX.pi), -3) + + XCTAssertEqual(BigInt(+Float32.pi), +3) + XCTAssertEqual(BigInt(+Float64.pi), +3) + XCTAssertEqual(BigInt(+FloatXX.pi), +3) + } + + func testFloatingPoint_random() { + for _ in 0 ..< 100 { + let small = Float32.random(in: -10 ... +10) + XCTAssertEqual(BigInt(small), BigInt(Int64(small))) + + let large = Float32.random(in: -0x1p23 ... +0x1p23) + XCTAssertEqual(BigInt(large), BigInt(Int64(large))) + } + + for _ in 0 ..< 100 { + let small = Float64.random(in: -10 ... +10) + XCTAssertEqual(BigInt(small), BigInt(Int64(small))) + + let large = Float64.random(in: -0x1p52 ... +0x1p52) + XCTAssertEqual(BigInt(large), BigInt(Int64(large))) + } + + for _ in 0 ..< 100 { + let small = FloatXX.random(in: -10 ... +10) + XCTAssertEqual(BigInt(small), BigInt(Int64(small))) + + let large = FloatXX.random(in: -0x1p52 ... +0x1p52) + XCTAssertEqual(BigInt(large), BigInt(Int64(large))) + } + } + + func testFloatingPoint_zero() { + XCTAssertEqual(BigInt(exactly: -Float32.zero), 0) + XCTAssertEqual(BigInt(exactly: -Float64.zero), 0) + XCTAssertEqual(BigInt(exactly: -FloatXX.zero), 0) + + XCTAssertEqual(BigInt(exactly: +Float32.zero), 0) + XCTAssertEqual(BigInt(exactly: +Float64.zero), 0) + XCTAssertEqual(BigInt(exactly: +FloatXX.zero), 0) + + XCTAssertEqual(BigInt(-Float32.zero), 0) + XCTAssertEqual(BigInt(-Float64.zero), 0) + XCTAssertEqual(BigInt(-FloatXX.zero), 0) + + XCTAssertEqual(BigInt(+Float32.zero), 0) + XCTAssertEqual(BigInt(+Float64.zero), 0) + XCTAssertEqual(BigInt(+FloatXX.zero), 0) + } + + // MARK: - Basic arithmetic + func testDivision() { let foo = BigInt("12345678901234567890123456789012345678901234567890123456789012345678901234567890")! let bar = BigInt("351235231535161613134135135135")! From 0dee6d092735b3771a3bbcffaee6430a889391fb Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Mon, 20 Jan 2020 11:05:12 +0000 Subject: [PATCH 66/92] [BigIntTests] Move other test methods --- Tests/BigIntTests/BigIntTests.swift | 152 ++++++++++++++-------------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 9a846696..a2b402a9 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -73,6 +73,19 @@ final class BigIntTests: XCTestCase { 000000000000000000000000000000000000000000000000000000000000000000000000000 """ + // MARK: - Basic arithmetic + + func testDivision() { + let foo = BigInt("12345678901234567890123456789012345678901234567890123456789012345678901234567890")! + let bar = BigInt("351235231535161613134135135135")! + let baz = foo / bar + XCTAssertEqual(baz, BigInt("35149318157164029155780432046477458820396117503007")!) + + XCTAssertNotNil(BigInt(exactly: 2.4e39)) + XCTAssertNotNil(BigInt(exactly: 1e38)) + XCTAssertEqual(BigInt(2.4e39) / BigInt(1e38), BigInt(24)) + } + func testFactorial() { var expectedNumber: BigInt! var actualNumber: BigInt! @@ -88,38 +101,6 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(actualString, Self.descriptionFactorial512Radix36) } - func testExample() { - let bar = BigInt(exactly: -100) - XCTAssertNotNil(bar) - if let bar = bar { - XCTAssertLessThan(bar, 0) - XCTAssertGreaterThan(-bar, 0) - XCTAssertEqual(-bar, BigInt(100)) - } - XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, - +BigInt("+1234567890123456789012345678901234567890")!) - } - - func testUIntConversion() { - let foo = BigInt(UInt.max) - XCTAssertNotEqual(foo, BigInt(-1)) - - let bar = BigInt(bitPattern: UInt.max) - XCTAssertEqual(bar, BigInt(-1)) - } - - func testComparison() { - let foo = BigInt(-10) - let bar = BigInt(-20) - - XCTAssert(foo > bar) - XCTAssert(bar < foo) - XCTAssert(foo == BigInt(-10)) - - let baz = pow(foo, -bar) - XCTAssertEqual(baz, BigInt("100000000000000000000")!) - } - func testMath() { let foo = pow(BigInt(10), 20) let bar = BigInt("1234567890123456789012345678901234567890")! @@ -135,20 +116,6 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(barz, BigInt(UInt.max) + 1) } - func testHashable() { - let foo = BigInt("1234567890123456789012345678901234567890")! - let bar = BigInt("1234567890123456789112345678901234567890")! - let baz: BigInt = 153 - - let dict = [ foo: "Hello", bar: "World", baz: "!" ] - - let hash = foo.hashValue - print(hash) - - XCTAssertEqual(dict[foo]!, "Hello") - XCTAssertEqual(dict[bar]!, "World") - } - func testNegation() { let foo = BigInt("1234567890123456789012345678901234567890")! let bar = BigInt(0) - foo @@ -160,6 +127,24 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(baz, bar) } + func testSignum() { + XCTAssertEqual(BigInt(-0x1p1023).signum(), -1) + XCTAssertEqual(BigInt(Int64.min).signum(), -1) + XCTAssertEqual(BigInt(Int32.min).signum(), -1) + XCTAssertEqual(BigInt(Int16.min).signum(), -1) + XCTAssertEqual(BigInt(Int8.min).signum(), -1) + XCTAssertEqual(BigInt(-1).signum(), -1) + XCTAssertEqual(BigInt(0).signum(), 0) + XCTAssertEqual(BigInt(+1).signum(), +1) + XCTAssertEqual(BigInt(Int8.max).signum(), +1) + XCTAssertEqual(BigInt(Int16.max).signum(), +1) + XCTAssertEqual(BigInt(Int32.max).signum(), +1) + XCTAssertEqual(BigInt(Int64.max).signum(), +1) + XCTAssertEqual(BigInt(+0x1p1023).signum(), +1) + } + + // MARK: - Comparing and hashing + func testComparable() { let foo = BigInt("1234567890123456789012345678901234567890")! let bar = foo * foo @@ -178,6 +163,52 @@ final class BigIntTests: XCTestCase { XCTAssertFalse(baz < baz) } + func testComparison() { + let foo = BigInt(-10) + let bar = BigInt(-20) + + XCTAssert(foo > bar) + XCTAssert(bar < foo) + XCTAssert(foo == BigInt(-10)) + + let baz = pow(foo, -bar) + XCTAssertEqual(baz, BigInt("100000000000000000000")!) + } + + func testExample() { + let bar = BigInt(exactly: -100) + XCTAssertNotNil(bar) + if let bar = bar { + XCTAssertLessThan(bar, 0) + XCTAssertGreaterThan(-bar, 0) + XCTAssertEqual(-bar, BigInt(100)) + } + XCTAssertEqual(-BigInt("-1234567890123456789012345678901234567890")!, + +BigInt("+1234567890123456789012345678901234567890")!) + } + + func testHashable() { + let foo = BigInt("1234567890123456789012345678901234567890")! + let bar = BigInt("1234567890123456789112345678901234567890")! + let baz: BigInt = 153 + + let dict = [ foo: "Hello", bar: "World", baz: "!" ] + + let hash = foo.hashValue + print(hash) + + XCTAssertEqual(dict[foo]!, "Hello") + XCTAssertEqual(dict[bar]!, "World") + } + + func testUIntConversion() { + let foo = BigInt(UInt.max) + XCTAssertNotEqual(foo, BigInt(-1)) + + let bar = BigInt(bitPattern: UInt.max) + XCTAssertEqual(bar, BigInt(-1)) + } + // MARK: - Converting to/from textual representations func testCodable() throws { @@ -404,33 +435,4 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(BigInt(+Float64.zero), 0) XCTAssertEqual(BigInt(+FloatXX.zero), 0) } - - // MARK: - Basic arithmetic - - func testDivision() { - let foo = BigInt("12345678901234567890123456789012345678901234567890123456789012345678901234567890")! - let bar = BigInt("351235231535161613134135135135")! - let baz = foo / bar - XCTAssertEqual(baz, BigInt("35149318157164029155780432046477458820396117503007")!) - - XCTAssertNotNil(BigInt(exactly: 2.4e39)) - XCTAssertNotNil(BigInt(exactly: 1e38)) - XCTAssertEqual(BigInt(2.4e39) / BigInt(1e38), BigInt(24)) - } - - func testSignum() { - XCTAssertEqual(BigInt(-0x1p1023).signum(), -1) - XCTAssertEqual(BigInt(Int64.min).signum(), -1) - XCTAssertEqual(BigInt(Int32.min).signum(), -1) - XCTAssertEqual(BigInt(Int16.min).signum(), -1) - XCTAssertEqual(BigInt(Int8.min).signum(), -1) - XCTAssertEqual(BigInt(-1).signum(), -1) - XCTAssertEqual(BigInt(0).signum(), 0) - XCTAssertEqual(BigInt(+1).signum(), +1) - XCTAssertEqual(BigInt(Int8.max).signum(), +1) - XCTAssertEqual(BigInt(Int16.max).signum(), +1) - XCTAssertEqual(BigInt(Int32.max).signum(), +1) - XCTAssertEqual(BigInt(Int64.max).signum(), +1) - XCTAssertEqual(BigInt(+0x1p1023).signum(), +1) - } } From fc7b3bf44ff3a1b9e3e19e5b04be2595cdedb70d Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Mon, 20 Jan 2020 11:48:46 +0000 Subject: [PATCH 67/92] [BigIntTests] Update expected descriptions --- Tests/BigIntTests/BigIntTests.swift | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index a2b402a9..1d750fd2 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -48,18 +48,17 @@ final class BigIntTests: XCTestCase { 112068607 """ - /// Python: `int(sys.float_info.max)` - static let descriptionFloat64_greatestFiniteMagnitude: String = + /// Python: `hex(int(sys.float_info.max))` + static let descriptionFloat64Max_radix16: String = """ - 179769313486231570814527423731704356798070567525844996598917476803157260780\ - 028538760589558632766878171540458953514382464234321326889464182768467546703\ - 537516986049910576551282076245490090389328944075868508455133942304583236903\ - 222948165808559332123348274797826204144723168738177180919299881250404026184\ - 124858368 + FFFFFFFFFFFFF80000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000 """ /// Python: `numpy.base_repr(math.factorial(512), base=36)` - static let descriptionFactorial512Radix36: String = + static let descriptionFactorial512_radix36: String = """ 7FA5Y7EHR9XHMQ519MBHGYOF8XDYMUX8OZHO9WF1KCM0SSPXV2V45UA73BAFRYM2PFB8CZLTODV\ OS3QWA7PYFJ7WAFBI4VF371E27N6XZ4LGWHMFDS4ZH1O3DGNFG4YABUE1G90ORGRTIOGSQVZLSQ\ @@ -92,13 +91,13 @@ final class BigIntTests: XCTestCase { var actualString: String! measure { - expectedNumber = BigInt(Self.descriptionFactorial512Radix36, radix: 36) + expectedNumber = BigInt(Self.descriptionFactorial512_radix36, radix: 36) actualNumber = fac(BigInt(512)) actualString = String(actualNumber, radix: 36, uppercase: true) } XCTAssertEqual(actualNumber, expectedNumber) - XCTAssertEqual(actualString, Self.descriptionFactorial512Radix36) + XCTAssertEqual(actualString, Self.descriptionFactorial512_radix36) } func testMath() { @@ -312,14 +311,14 @@ final class BigIntTests: XCTestCase { func testFloatingPoint_greatestFiniteMagnitude() { XCTAssertEqual(BigInt(exactly: -Float64.greatestFiniteMagnitude), - BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + BigInt("-\(Self.descriptionFloat64Max_radix16)", radix: 16)) XCTAssertEqual(BigInt(exactly: +Float64.greatestFiniteMagnitude), - BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + BigInt("+\(Self.descriptionFloat64Max_radix16)", radix: 16)) XCTAssertEqual(BigInt(-Float64.greatestFiniteMagnitude), - BigInt("-\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + BigInt("-\(Self.descriptionFloat64Max_radix16)", radix: 16)) XCTAssertEqual(BigInt(+Float64.greatestFiniteMagnitude), - BigInt("+\(Self.descriptionFloat64_greatestFiniteMagnitude)")) + BigInt("+\(Self.descriptionFloat64Max_radix16)", radix: 16)) } func testFloatingPoint_infinity() { From 4c425513cd561e84413a781a8cfae830cce6d43b Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Mon, 20 Jan 2020 13:05:49 +0000 Subject: [PATCH 68/92] [BigIntTests] Use `fac(_:)` to test Strideable --- Tests/BigIntTests/BigIntTests.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 1d750fd2..e57cea7c 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -13,14 +13,7 @@ import BigInt import XCTest func fac(_ n: BigInt) -> BigInt { - var result: BigInt = 1 - var count = n - while count >= 1 { - result *= count - count -= 1 - } - - return result + return stride(from: n, to: 1, by: -1).reduce(into: 1, { $0 *= $1 }) } final class BigIntTests: XCTestCase { From bf9afea2ebbb138a6c92aa38b94822f98c2ff6ac Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 21 Jan 2020 08:36:48 +0000 Subject: [PATCH 69/92] [BigIntTests] Add testFloatingPoint_signalingNaN() --- Tests/BigIntTests/BigIntTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index e57cea7c..e61cf410 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -410,6 +410,12 @@ final class BigIntTests: XCTestCase { } } + func testFloatingPoint_signalingNaN() { + XCTAssertNil(BigInt(exactly: Float32.signalingNaN)) + XCTAssertNil(BigInt(exactly: Float64.signalingNaN)) + XCTAssertNil(BigInt(exactly: FloatXX.signalingNaN)) + } + func testFloatingPoint_zero() { XCTAssertEqual(BigInt(exactly: -Float32.zero), 0) XCTAssertEqual(BigInt(exactly: -Float64.zero), 0) From ffdaf86868f6c547c4ed89b5dc3c037f4ee52f5e Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 21 Jan 2020 13:30:21 +0000 Subject: [PATCH 70/92] [BigIntTests] Change `fac(BigInt(512))` to `BigInt.fac(512)` --- Tests/BigIntTests/BigIntTests.swift | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index e61cf410..e4bd4768 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -12,8 +12,12 @@ import BigInt import XCTest -func fac(_ n: BigInt) -> BigInt { - return stride(from: n, to: 1, by: -1).reduce(into: 1, { $0 *= $1 }) +extension BigInt { + + static func fac(_ n: BigInt) -> BigInt { + precondition(n >= 0, "Factorial of a negative integer is undefined") + return stride(from: n, to: 1, by: -1).reduce(into: 1, { $0 *= $1 }) + } } final class BigIntTests: XCTestCase { @@ -79,18 +83,29 @@ final class BigIntTests: XCTestCase { } func testFactorial() { - var expectedNumber: BigInt! + var expectedNumber: BigInt? var actualNumber: BigInt! var actualString: String! measure { expectedNumber = BigInt(Self.descriptionFactorial512_radix36, radix: 36) - actualNumber = fac(BigInt(512)) + actualNumber = BigInt.fac(512) actualString = String(actualNumber, radix: 36, uppercase: true) } XCTAssertEqual(actualNumber, expectedNumber) XCTAssertEqual(actualString, Self.descriptionFactorial512_radix36) + + XCTAssertEqual(BigInt.fac(0), 1) + XCTAssertEqual(BigInt.fac(1), 1) + XCTAssertEqual(BigInt.fac(2), 2) + XCTAssertEqual(BigInt.fac(3), 6) + XCTAssertEqual(BigInt.fac(4), 24) + XCTAssertEqual(BigInt.fac(5), 120) + XCTAssertEqual(BigInt.fac(6), 720) + XCTAssertEqual(BigInt.fac(7), 5040) + XCTAssertEqual(BigInt.fac(8), 40320) + XCTAssertEqual(BigInt.fac(9), 362880) } func testMath() { From ca75d2d1244445bb01d8a3738b226399633d9524 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 21 Jan 2020 16:24:51 +0000 Subject: [PATCH 71/92] [BigInt] Move `pow(_:_:)` into test-only extension --- Sources/BigInt/BigInt.swift | 25 ------------------------- Tests/BigIntTests/BigIntTests.swift | 29 +++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index ba114ca2..a8b9b8ad 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -726,28 +726,3 @@ extension BigInt { } } } - -// inspired by https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms -public func pow(_ lhs: BigInt, _ rhs: BigInt) -> BigInt { - let bits_of_n = { - (n: BigInt) -> [Int] in - var bits: [Int] = [] - var n = n - while n != 0 { - bits.append(Int(n % 2)) - n /= 2 - } - - return bits - } - - var r: BigInt = 1 - for bit in bits_of_n(rhs).reversed() { - r *= r - if bit == 1 { - r *= lhs - } - } - - return r -} diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index e4bd4768..f565307d 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -18,6 +18,31 @@ extension BigInt { precondition(n >= 0, "Factorial of a negative integer is undefined") return stride(from: n, to: 1, by: -1).reduce(into: 1, { $0 *= $1 }) } + + // inspired by https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms + static func pow(_ lhs: BigInt, _ rhs: BigInt) -> BigInt { + let bits_of_n = { + (n: BigInt) -> [Int] in + var bits: [Int] = [] + var n = n + while n != 0 { + bits.append(Int(n % 2)) + n /= 2 + } + + return bits + } + + var r: BigInt = 1 + for bit in bits_of_n(rhs).reversed() { + r *= r + if bit == 1 { + r *= lhs + } + } + + return r + } } final class BigIntTests: XCTestCase { @@ -109,7 +134,7 @@ final class BigIntTests: XCTestCase { } func testMath() { - let foo = pow(BigInt(10), 20) + let foo = BigInt.pow(10, 20) let bar = BigInt("1234567890123456789012345678901234567890")! let baz = foo + bar @@ -178,7 +203,7 @@ final class BigIntTests: XCTestCase { XCTAssert(bar < foo) XCTAssert(foo == BigInt(-10)) - let baz = pow(foo, -bar) + let baz = BigInt.pow(foo, -bar) XCTAssertEqual(baz, BigInt("100000000000000000000")!) } From 3501fa7c61033ffd56ff3804580ed67aea58ca43 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 21 Jan 2020 17:30:47 +0000 Subject: [PATCH 72/92] [BigInt] Restore precondition message for inf/nan --- Sources/BigInt/BigInt.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index a8b9b8ad..618ab4e0 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -337,7 +337,12 @@ extension BigInt: BinaryInteger { } public init(_ source: T) where T: BinaryFloatingPoint { - precondition(source.isFinite, "BigInt(\(source)) failed") + precondition( + source.isFinite, + """ + \(type(of: source)) value cannot be converted to BigInt because it is \ + either infinite or NaN + """) precondition(T.significandBitCount < .max) let source = source.rounded(.towardZero) From ceee280ebe9b5bfed7cd332fe844558dde119faa Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 21 Jan 2020 18:09:39 +0000 Subject: [PATCH 73/92] [BigIntTests] Don't use the CLongDouble typealias --- Tests/BigIntTests/BigIntTests.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index f565307d..2c440c91 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -47,8 +47,11 @@ extension BigInt { final class BigIntTests: XCTestCase { - /// Float64 or Float80 (or Float128) - typealias FloatXX = CLongDouble + #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + typealias FloatXX = Float80 + #else + typealias FloatXX = Float64 + #endif /// Python: `bitWidth = 1024; -(2 ** (bitWidth - 1))` static let descriptionInt1024Min: String = From 6e85aa2bbde250dcbc051f76c64d24195ae9fdc1 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Tue, 21 Jan 2020 19:47:19 +0000 Subject: [PATCH 74/92] [BigInt] Revert the BinaryFloatingPoint conversion --- Sources/BigInt/BigInt.swift | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 618ab4e0..3d1006df 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -343,20 +343,23 @@ extension BigInt: BinaryInteger { \(type(of: source)) value cannot be converted to BigInt because it is \ either infinite or NaN """) - precondition(T.significandBitCount < .max) - let source = source.rounded(.towardZero) - let isNegative = source < 0 + let isNegative = source < 0.0 + var float = isNegative ? -source : source - guard !source.isZero else { - self = 0 - return - } + if let _ = UInt(exactly: T.greatestFiniteMagnitude) { + words = [UInt(float)] + } else { + var words = Words() + let radix = T(sign: .plus, exponent: T.Exponent(UInt.bitWidth), significand: 1) + repeat { + let digit = UInt(float.truncatingRemainder(dividingBy: radix)) + words.append(digit) + float = (float / radix).rounded(.towardZero) + } while float != 0 - self = BigInt(source.significandBitPattern) - self |= BigInt(1) << T.significandBitCount - self <<= source.exponent - self >>= T.significandBitCount + self.words = words + } if isNegative { self.negate() From 90d5ddd1f65d8af2c76b14c490c0d2cbf108b3ab Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 21 Jan 2020 16:59:28 -0500 Subject: [PATCH 75/92] Fix failing tests (@benrimminton: it turned out simple) If the last computed digit of a BinaryFloatingPoint source is > Int.max, we need to add an extra zero digit to the end so we're not negative! --- Sources/BigInt/BigInt.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 3d1006df..ec8ebae4 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -358,6 +358,9 @@ extension BigInt: BinaryInteger { float = (float / radix).rounded(.towardZero) } while float != 0 + if let last = words.last, last >= Int.max { + words.append(0) + } self.words = words } From e3d01dd6804aca414c29e1f0b7130827dbb43514 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 22 Jan 2020 10:16:01 -0500 Subject: [PATCH 76/92] First step to increasing test coverage --- Tests/BigIntTests/BigIntTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 2c440c91..df5a9068 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -235,6 +235,10 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(dict[foo]!, "Hello") XCTAssertEqual(dict[bar]!, "World") } + + func testClampingConversion() { + XCTAssertEqual(BigInt(clamping: UInt.max), BigInt(UInt(18446744073709551615))) + } func testUIntConversion() { let foo = BigInt(UInt.max) From 5919d3f48f51ec82bdb26e7925de1b759b9d07ef Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Mon, 27 Jan 2020 15:17:43 +0000 Subject: [PATCH 77/92] [BigIntTests] Use generic testBinaryFloatingPoint --- Tests/BigIntTests/BigIntTests.swift | 172 +++++++++------------------- 1 file changed, 51 insertions(+), 121 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 2c440c91..4f31ee8c 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -47,12 +47,6 @@ extension BigInt { final class BigIntTests: XCTestCase { - #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) - typealias FloatXX = Float80 - #else - typealias FloatXX = Float64 - #endif - /// Python: `bitWidth = 1024; -(2 ** (bitWidth - 1))` static let descriptionInt1024Min: String = """ @@ -73,15 +67,6 @@ final class BigIntTests: XCTestCase { 112068607 """ - /// Python: `hex(int(sys.float_info.max))` - static let descriptionFloat64Max_radix16: String = - """ - FFFFFFFFFFFFF80000000000000000000000000000000000000000000000000000000000000\ - 000000000000000000000000000000000000000000000000000000000000000000000000000\ - 000000000000000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000 - """ - /// Python: `numpy.base_repr(math.factorial(512), base=36)` static let descriptionFactorial512_radix36: String = """ @@ -345,89 +330,56 @@ final class BigIntTests: XCTestCase { // MARK: - Converting from floating-point binary types - func testFloatingPoint_greatestFiniteMagnitude() { - XCTAssertEqual(BigInt(exactly: -Float64.greatestFiniteMagnitude), - BigInt("-\(Self.descriptionFloat64Max_radix16)", radix: 16)) - XCTAssertEqual(BigInt(exactly: +Float64.greatestFiniteMagnitude), - BigInt("+\(Self.descriptionFloat64Max_radix16)", radix: 16)) - - XCTAssertEqual(BigInt(-Float64.greatestFiniteMagnitude), - BigInt("-\(Self.descriptionFloat64Max_radix16)", radix: 16)) - XCTAssertEqual(BigInt(+Float64.greatestFiniteMagnitude), - BigInt("+\(Self.descriptionFloat64Max_radix16)", radix: 16)) - } - - func testFloatingPoint_infinity() { - XCTAssertNil(BigInt(exactly: -Float32.infinity)) - XCTAssertNil(BigInt(exactly: -Float64.infinity)) - XCTAssertNil(BigInt(exactly: -FloatXX.infinity)) - - XCTAssertNil(BigInt(exactly: +Float32.infinity)) - XCTAssertNil(BigInt(exactly: +Float64.infinity)) - XCTAssertNil(BigInt(exactly: +FloatXX.infinity)) + func testBinaryFloatingPoint(_ type: T.Type) where T: BinaryFloatingPoint { + var expected = BigInt(T.greatestFiniteMagnitude.significandBitPattern) + expected |= BigInt(1) << T.significandBitCount + expected <<= T.greatestFiniteMagnitude.exponent + expected >>= T.significandBitCount + + XCTAssertEqual(BigInt(exactly: -T.greatestFiniteMagnitude), -expected) + XCTAssertEqual(BigInt(exactly: +T.greatestFiniteMagnitude), +expected) + XCTAssertEqual(BigInt(-T.greatestFiniteMagnitude), -expected) + XCTAssertEqual(BigInt(+T.greatestFiniteMagnitude), +expected) + + XCTAssertNil(BigInt(exactly: -T.infinity)) + XCTAssertNil(BigInt(exactly: +T.infinity)) + + XCTAssertNil(BigInt(exactly: -T.leastNonzeroMagnitude)) + XCTAssertNil(BigInt(exactly: +T.leastNonzeroMagnitude)) + XCTAssertEqual(BigInt(-T.leastNonzeroMagnitude), 0) + XCTAssertEqual(BigInt(+T.leastNonzeroMagnitude), 0) + + XCTAssertNil(BigInt(exactly: -T.leastNormalMagnitude)) + XCTAssertNil(BigInt(exactly: +T.leastNormalMagnitude)) + XCTAssertEqual(BigInt(-T.leastNormalMagnitude), 0) + XCTAssertEqual(BigInt(+T.leastNormalMagnitude), 0) + + XCTAssertNil(BigInt(exactly: T.nan)) + XCTAssertNil(BigInt(exactly: T.signalingNaN)) + + XCTAssertNil(BigInt(exactly: -T.pi)) + XCTAssertNil(BigInt(exactly: +T.pi)) + XCTAssertEqual(BigInt(-T.pi), -3) + XCTAssertEqual(BigInt(+T.pi), +3) + + XCTAssertNil(BigInt(exactly: -T.ulpOfOne)) + XCTAssertNil(BigInt(exactly: +T.ulpOfOne)) + XCTAssertEqual(BigInt(-T.ulpOfOne), 0) + XCTAssertEqual(BigInt(+T.ulpOfOne), 0) + + XCTAssertEqual(BigInt(exactly: -T.zero), 0) + XCTAssertEqual(BigInt(exactly: +T.zero), 0) + XCTAssertEqual(BigInt(-T.zero), 0) + XCTAssertEqual(BigInt(+T.zero), 0) } - func testFloatingPoint_leastNonzeroMagnitude() { - XCTAssertNil(BigInt(exactly: -Float32.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: -Float64.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: -FloatXX.leastNonzeroMagnitude)) + func testBinaryFloatingPoint() { + testBinaryFloatingPoint(Float32.self) + testBinaryFloatingPoint(Float64.self) + #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) + testBinaryFloatingPoint(Float80.self) + #endif - XCTAssertNil(BigInt(exactly: +Float32.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: +Float64.leastNonzeroMagnitude)) - XCTAssertNil(BigInt(exactly: +FloatXX.leastNonzeroMagnitude)) - - XCTAssertEqual(BigInt(-Float32.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(-Float64.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(-FloatXX.leastNonzeroMagnitude), 0) - - XCTAssertEqual(BigInt(+Float32.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(+Float64.leastNonzeroMagnitude), 0) - XCTAssertEqual(BigInt(+FloatXX.leastNonzeroMagnitude), 0) - } - - func testFloatingPoint_leastNormalMagnitude() { - XCTAssertNil(BigInt(exactly: -Float32.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: -Float64.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: -FloatXX.leastNormalMagnitude)) - - XCTAssertNil(BigInt(exactly: +Float32.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: +Float64.leastNormalMagnitude)) - XCTAssertNil(BigInt(exactly: +FloatXX.leastNormalMagnitude)) - - XCTAssertEqual(BigInt(-Float32.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(-Float64.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(-FloatXX.leastNormalMagnitude), 0) - - XCTAssertEqual(BigInt(+Float32.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(+Float64.leastNormalMagnitude), 0) - XCTAssertEqual(BigInt(+FloatXX.leastNormalMagnitude), 0) - } - - func testFloatingPoint_nan() { - XCTAssertNil(BigInt(exactly: Float32.nan)) - XCTAssertNil(BigInt(exactly: Float64.nan)) - XCTAssertNil(BigInt(exactly: FloatXX.nan)) - } - - func testFloatingPoint_pi() { - XCTAssertNil(BigInt(exactly: -Float32.pi)) - XCTAssertNil(BigInt(exactly: -Float64.pi)) - XCTAssertNil(BigInt(exactly: -FloatXX.pi)) - - XCTAssertNil(BigInt(exactly: +Float32.pi)) - XCTAssertNil(BigInt(exactly: +Float64.pi)) - XCTAssertNil(BigInt(exactly: +FloatXX.pi)) - - XCTAssertEqual(BigInt(-Float32.pi), -3) - XCTAssertEqual(BigInt(-Float64.pi), -3) - XCTAssertEqual(BigInt(-FloatXX.pi), -3) - - XCTAssertEqual(BigInt(+Float32.pi), +3) - XCTAssertEqual(BigInt(+Float64.pi), +3) - XCTAssertEqual(BigInt(+FloatXX.pi), +3) - } - - func testFloatingPoint_random() { for _ in 0 ..< 100 { let small = Float32.random(in: -10 ... +10) XCTAssertEqual(BigInt(small), BigInt(Int64(small))) @@ -444,36 +396,14 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(BigInt(large), BigInt(Int64(large))) } + #if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) for _ in 0 ..< 100 { - let small = FloatXX.random(in: -10 ... +10) + let small = Float80.random(in: -10 ... +10) XCTAssertEqual(BigInt(small), BigInt(Int64(small))) - let large = FloatXX.random(in: -0x1p52 ... +0x1p52) + let large = Float80.random(in: -0x1p63 ..< +0x1p63) XCTAssertEqual(BigInt(large), BigInt(Int64(large))) } - } - - func testFloatingPoint_signalingNaN() { - XCTAssertNil(BigInt(exactly: Float32.signalingNaN)) - XCTAssertNil(BigInt(exactly: Float64.signalingNaN)) - XCTAssertNil(BigInt(exactly: FloatXX.signalingNaN)) - } - - func testFloatingPoint_zero() { - XCTAssertEqual(BigInt(exactly: -Float32.zero), 0) - XCTAssertEqual(BigInt(exactly: -Float64.zero), 0) - XCTAssertEqual(BigInt(exactly: -FloatXX.zero), 0) - - XCTAssertEqual(BigInt(exactly: +Float32.zero), 0) - XCTAssertEqual(BigInt(exactly: +Float64.zero), 0) - XCTAssertEqual(BigInt(exactly: +FloatXX.zero), 0) - - XCTAssertEqual(BigInt(-Float32.zero), 0) - XCTAssertEqual(BigInt(-Float64.zero), 0) - XCTAssertEqual(BigInt(-FloatXX.zero), 0) - - XCTAssertEqual(BigInt(+Float32.zero), 0) - XCTAssertEqual(BigInt(+Float64.zero), 0) - XCTAssertEqual(BigInt(+FloatXX.zero), 0) + #endif } } From 7026eb40854712fe2cac9ed32dae45199776d1c5 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Mon, 27 Jan 2020 12:31:26 -0500 Subject: [PATCH 78/92] more tests for division --- Sources/BigInt/BigInt.swift | 123 +++++++++++++++++++--------- Tests/BigIntTests/BigIntTests.swift | 19 ++++- 2 files changed, 104 insertions(+), 38 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index ec8ebae4..70b0d374 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -33,6 +33,8 @@ public struct BigInt: SignedInteger { private static let _digits: [BigInt] = (0 ... 36).map { BigInt(_uncheckedWords: [UInt(bitPattern: $0)]) } + + private static let _digitRadix = BigInt(_uncheckedWords: [0, 1]) } // MARK: - Basic Behaviors @@ -543,40 +545,72 @@ extension BigInt { nextVdigit: UInt, nextUdigit: UInt ) -> UInt { - var (qhat, rhat) = divisor.dividingFullWidth((high, low)) - - if high >= divisor { // This means qhat >= b - qhat = UInt.max - } - - let (tempHigh, tempLow) = qhat.multipliedFullWidth(by: nextVdigit) - var (rtempHigh, rtempLow) = rhat.multipliedFullWidth(by: UInt.max) - var overflow = false - (rtempLow, overflow) = rtempLow.addingReportingOverflow(1) - if overflow { - rtempHigh += 1 - } - - (rtempLow, overflow) = rtempLow.addingReportingOverflow(nextUdigit) - if overflow { - rtempHigh += 1 +// var (qhat, rhat) = divisor.dividingFullWidth((high, low)) +// +// if high >= divisor { // This means qhat >= b +// qhat = UInt.max +// } +// +// let (tempHigh, tempLow) = qhat.multipliedFullWidth(by: nextVdigit) +// var (rtempHigh, rtempLow) = rhat.multipliedFullWidth(by: UInt.max) +// var overflow = false +// (rtempLow, overflow) = rtempLow.addingReportingOverflow(rhat) +// if overflow { +// rtempHigh += 1 +// } +// +// (rtempLow, overflow) = rtempLow.addingReportingOverflow(nextUdigit) +// if overflow { +// rtempHigh += 1 +// } +// +// while true { +// if (tempHigh > rtempHigh) || ((tempHigh == rtempHigh) && (tempLow > rtempLow)) { +// qhat -= 1 +// (rhat, overflow) = rhat.addingReportingOverflow(divisor) +// if !overflow { +// continue +// } else { +// break +// } +// } else { +// break +// } +// } +// +// return qhat + + var qhat: BigInt + var rhat: BigInt + if high >= divisor { + let v = divisor + let u = [low, high] + var r: UInt = 0 + var quot = Words(repeating: 0, count: u.count) + for j in (0...(u.count - 1)).reversed() { + let uj = u[j] + (quot[j], r) = v.dividingFullWidth((r, uj)) + } + + BigInt._dropExcessWords(words: ") + qhat = BigInt(_uncheckedWords: quot) + rhat = BigInt(r) + } else { + let (qhatWord, rhatWord) = divisor.dividingFullWidth((high, low)) + qhat = BigInt(qhatWord) + rhat = BigInt(rhatWord) } - - while true { - if (tempHigh > rtempHigh) || ((tempHigh == rtempHigh) && (tempLow > rtempLow)) { + + repeat { + if (high >= divisor) || (qhat * BigInt(nextVdigit) < BigInt(_uncheckedWords: [nextUdigit, rhat.words[0]])) { qhat -= 1 - (rhat, overflow) = rhat.addingReportingOverflow(divisor) - if !overflow { - continue - } else { - break - } + rhat += BigInt(divisor) } else { break } - } - - return qhat + } while rhat < BigInt._digitRadix + + return qhat.words[0] } /// See _The Art of Computer Programming_ volume 2 by Donald Knuth, Section 4.3.1: The Classical Algorithms @@ -594,6 +628,21 @@ extension BigInt { var lhsWords = lhsIsNeg ? (-lhs).words : lhs.words var rhsWords = rhsIsNeg ? (-rhs).words : rhs.words + + // See the answer to exercise 16 in Section 4.3.1 of TAOCP + if rhsWords.count == 1 { + let v = rhsWords[0] + let u = lhsWords + var r: UInt = 0 + var quot = Words(repeating: 0, count: u.count) + for j in (0...(u.count - 1)).reversed() { + let uj = u[j] + (quot[j], r) = v.dividingFullWidth((r, uj)) + } + + BigInt._dropExcessWords(words: ") + return (quotient: BigInt(_uncheckedWords: quot), remainder: BigInt(r)) + } while rhsWords[rhsWords.endIndex - 1] == 0, rhsWords.count > 2 { rhsWords.removeLast() @@ -605,13 +654,13 @@ extension BigInt { rhsWords.append(0) } - if lhsWords.count < rhsWords.count { - for _ in 0 ..< (rhsWords.count - lhsWords.count) { + if lhsWords.count <= rhsWords.count { + for _ in 0 ... (rhsWords.count - lhsWords.count) { lhsWords.append(0) } } - let m = lhsWords.count + let m = lhsWords.count - rhsWords.count let n = rhsWords.count let bitWidth = UInt(UInt.bitWidth) @@ -623,16 +672,16 @@ extension BigInt { for i in (1 ... (n - 1)).reversed() { rn[i] = (rhsWords[i] << s) | (rhsWords[i - 1] >> (bitWidth - s)) } - rn[0] <<= s + rn[0] = rhsWords[0] << s let ln = UnsafeMutablePointer.allocate(capacity: m + n + 1) ln.initialize(repeating: 0, count: m + n + 1) defer { ln.deallocate() } - ln[m] = lhsWords[m - 1] >> (bitWidth - s) - for i in (1 ... (m - 1)).reversed() { + ln[m + n] = lhsWords[m + n - 1] >> (bitWidth - s) + for i in (1 ... (m + n - 1)).reversed() { ln[i] = (lhsWords[i] << s) | (lhsWords[i - 1] >> (bitWidth - s)) } - ln[0] <<= s + ln[0] = lhsWords[0] << s let resultSize = m + 1 var quot = Words(repeating: 0, count: resultSize) @@ -682,7 +731,7 @@ extension BigInt { (ln[i + j], isOverflow) = total.addingReportingOverflow(rn[i]) if carry == 0 { carry = isOverflow ? 1 : 0 } } - ln[j + n] += carry + (ln[j + n], _) = ln[j + n].addingReportingOverflow(carry) quot[j] = newQhat } else { diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index df5a9068..f958c09b 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -100,10 +100,27 @@ final class BigIntTests: XCTestCase { // MARK: - Basic arithmetic func testDivision() { + let num1 = BigInt("18446744073709551616")! + let den1 = BigInt(123) + let expected1 = BigInt(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 - XCTAssertEqual(baz, BigInt("35149318157164029155780432046477458820396117503007")!) + XCTAssertEqual(baz, BigInt("35149318157164029153358504918339691272847595997760")!) XCTAssertNotNil(BigInt(exactly: 2.4e39)) XCTAssertNotNil(BigInt(exactly: 1e38)) From e4f5257d0a1ba4adeb9ac95bb83bc86210d947be Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 28 Jan 2020 14:05:22 -0500 Subject: [PATCH 79/92] fix division Re-write _findQhat from scratch, add check for division by single digit number in _div --- Sources/BigInt/BigInt.swift | 112 ++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 70b0d374..f8ec4516 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -545,72 +545,84 @@ extension BigInt { nextVdigit: UInt, nextUdigit: UInt ) -> UInt { -// var (qhat, rhat) = divisor.dividingFullWidth((high, low)) -// -// if high >= divisor { // This means qhat >= b -// qhat = UInt.max -// } -// -// let (tempHigh, tempLow) = qhat.multipliedFullWidth(by: nextVdigit) -// var (rtempHigh, rtempLow) = rhat.multipliedFullWidth(by: UInt.max) -// var overflow = false -// (rtempLow, overflow) = rtempLow.addingReportingOverflow(rhat) -// if overflow { -// rtempHigh += 1 -// } -// -// (rtempLow, overflow) = rtempLow.addingReportingOverflow(nextUdigit) -// if overflow { -// rtempHigh += 1 -// } -// -// while true { -// if (tempHigh > rtempHigh) || ((tempHigh == rtempHigh) && (tempLow > rtempLow)) { -// qhat -= 1 -// (rhat, overflow) = rhat.addingReportingOverflow(divisor) -// if !overflow { -// continue -// } else { -// break -// } -// } else { -// break -// } -// } -// -// return qhat - - var qhat: BigInt - var rhat: BigInt + var qhat: Array + var rhat: Array if high >= divisor { let v = divisor let u = [low, high] var r: UInt = 0 - var quot = Words(repeating: 0, count: u.count) - for j in (0...(u.count - 1)).reversed() { + qhat = Words(repeating: 0, count: 2) + for j in (0...1).reversed() { let uj = u[j] - (quot[j], r) = v.dividingFullWidth((r, uj)) + (qhat[j], r) = v.dividingFullWidth((r, uj)) } - BigInt._dropExcessWords(words: ") - qhat = BigInt(_uncheckedWords: quot) - rhat = BigInt(r) + BigInt._dropExcessWords(words: &qhat) + rhat = [r] } else { let (qhatWord, rhatWord) = divisor.dividingFullWidth((high, low)) - qhat = BigInt(qhatWord) - rhat = BigInt(rhatWord) + qhat = [qhatWord] + rhat = [rhatWord] } repeat { - if (high >= divisor) || (qhat * BigInt(nextVdigit) < BigInt(_uncheckedWords: [nextUdigit, rhat.words[0]])) { - qhat -= 1 - rhat += BigInt(divisor) + // All of the following is computing and checking qhat*v_n-2 > rhat*b + u_j+n-2 + // from TAoCP Volume 2 Section 4.3.1, Algorithm D, step D3 + let (comp_lhs_lohi, comp_lhs_lolo): (UInt, UInt) + let (comp_lhs_hihi, comp_lhs_hilo): (UInt, UInt) + var comp_lhs_result = Array(repeating: 0, count: 3) + + (comp_lhs_lohi, comp_lhs_lolo) = qhat[0].multipliedFullWidth(by: nextVdigit) + comp_lhs_result[0] = comp_lhs_lolo + + if qhat.count > 1 { + (comp_lhs_hihi, comp_lhs_hilo) = qhat[1].multipliedFullWidth(by: nextVdigit) + } else { + (comp_lhs_hihi, comp_lhs_hilo) = (0, 0) + let overflow: Bool + (comp_lhs_result[1], overflow) = comp_lhs_lohi.addingReportingOverflow(comp_lhs_hilo) + (comp_lhs_result[2], _) = comp_lhs_hihi.addingReportingOverflow(overflow ? 1 : 0) + } + + var lhsLarger = false + if comp_lhs_result[2] > 0 { + lhsLarger = true + } else if comp_lhs_result[1] > rhat[0] { + lhsLarger = true + } else if comp_lhs_result[1] == rhat[0] && comp_lhs_result[0] > nextUdigit { + lhsLarger = true + } + + // high >= divisor is standing in for the test qhat >= b from Algorithm D step D3 + if (high >= divisor) || lhsLarger { + // begin qhat -= 1 + if qhat.count == 1 { + qhat[0] -= 1 + } else { + let (qlow, underflow) = qhat[0].subtractingReportingOverflow(1) + qhat[0] = qlow + if qhat[1] > 0 { + qhat[1] -= (underflow ? 1 : 0) + } + } + // end qhat -= 1 + + // begin rhat += divisor + let (rhatResult, overflow) : (UInt, Bool) + if rhat.count == 1 || rhat[1] == 0 { + (rhatResult, overflow) = rhat[0].addingReportingOverflow(divisor) + rhat[0] = rhatResult + if overflow { + rhat.append(1) + } + } // we don't need an else because rhat is already larger than BigInt._digitRadix + // end rhat += divisor } else { break } - } while rhat < BigInt._digitRadix + } while rhat.count == 1 // equivalent to rhat < b - return qhat.words[0] + return qhat[0] } /// See _The Art of Computer Programming_ volume 2 by Donald Knuth, Section 4.3.1: The Classical Algorithms From 7d6e5cda3ee5308d22f2cad40ce1bad303a6cc7c Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 28 Jan 2020 16:02:30 -0500 Subject: [PATCH 80/92] Clean up division a bit more --- Sources/BigInt/BigInt.swift | 52 +++++++++++------------------ Tests/BigIntTests/BigIntTests.swift | 2 +- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index f8ec4516..67dcb0e7 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -566,43 +566,35 @@ extension BigInt { } repeat { - // All of the following is computing and checking qhat*v_n-2 > rhat*b + u_j+n-2 - // from TAoCP Volume 2 Section 4.3.1, Algorithm D, step D3 - let (comp_lhs_lohi, comp_lhs_lolo): (UInt, UInt) - let (comp_lhs_hihi, comp_lhs_hilo): (UInt, UInt) - var comp_lhs_result = Array(repeating: 0, count: 3) + var qhatTooLarge = false - (comp_lhs_lohi, comp_lhs_lolo) = qhat[0].multipliedFullWidth(by: nextVdigit) - comp_lhs_result[0] = comp_lhs_lolo - if qhat.count > 1 { - (comp_lhs_hihi, comp_lhs_hilo) = qhat[1].multipliedFullWidth(by: nextVdigit) + qhatTooLarge = true } else { - (comp_lhs_hihi, comp_lhs_hilo) = (0, 0) - let overflow: Bool - (comp_lhs_result[1], overflow) = comp_lhs_lohi.addingReportingOverflow(comp_lhs_hilo) - (comp_lhs_result[2], _) = comp_lhs_hihi.addingReportingOverflow(overflow ? 1 : 0) - } - - var lhsLarger = false - if comp_lhs_result[2] > 0 { - lhsLarger = true - } else if comp_lhs_result[1] > rhat[0] { - lhsLarger = true - } else if comp_lhs_result[1] == rhat[0] && comp_lhs_result[0] > nextUdigit { - lhsLarger = true + // All of the following is computing and checking qhat*v_n-2 > rhat*b + u_j+n-2 + // from TAoCP Volume 2 Section 4.3.1, Algorithm D, step D3 + let (comp_lhs_hi, comp_lhs_lo) = qhat[0].multipliedFullWidth(by: nextVdigit) + + if comp_lhs_hi > rhat[0] { + qhatTooLarge = true + } else if comp_lhs_hi == rhat[0] && comp_lhs_lo > nextUdigit { + qhatTooLarge = true + } } // high >= divisor is standing in for the test qhat >= b from Algorithm D step D3 - if (high >= divisor) || lhsLarger { + if qhatTooLarge { // begin qhat -= 1 if qhat.count == 1 { qhat[0] -= 1 } else { let (qlow, underflow) = qhat[0].subtractingReportingOverflow(1) qhat[0] = qlow - if qhat[1] > 0 { - qhat[1] -= (underflow ? 1 : 0) + if qhat[1] > 0 && underflow { + qhat[1] -= 1 + if qhat[1] == 0 { + qhat.remove(at: 1) + } } } // end qhat -= 1 @@ -642,7 +634,7 @@ extension BigInt { var rhsWords = rhsIsNeg ? (-rhs).words : rhs.words // See the answer to exercise 16 in Section 4.3.1 of TAOCP - if rhsWords.count == 1 { + if rhsWords.count == 1 || (rhsWords.count == 2 && rhsWords[1] == 0) { let v = rhsWords[0] let u = lhsWords var r: UInt = 0 @@ -651,21 +643,17 @@ extension BigInt { let uj = u[j] (quot[j], r) = v.dividingFullWidth((r, uj)) } - + BigInt._dropExcessWords(words: ") return (quotient: BigInt(_uncheckedWords: quot), remainder: BigInt(r)) } - while rhsWords[rhsWords.endIndex - 1] == 0, rhsWords.count > 2 { + while rhsWords[rhsWords.endIndex - 1] == 0 { rhsWords.removeLast() } if rhsWords.count > lhsWords.count { return (0, lhs) } - if rhsWords.count == 1 { - rhsWords.append(0) - } - if lhsWords.count <= rhsWords.count { for _ in 0 ... (rhsWords.count - lhsWords.count) { lhsWords.append(0) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index b173b835..c6e201d0 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import BigInt +@testable import BigInt import XCTest extension BigInt { From 6e0f0094dc46b40782690cfb47fb1ea76ed11b66 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 28 Jan 2020 16:09:32 -0500 Subject: [PATCH 81/92] fix tests compiling on arm32 --- Tests/BigIntTests/BigIntTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index c6e201d0..fea03c21 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -87,7 +87,7 @@ final class BigIntTests: XCTestCase { func testDivision() { let num1 = BigInt("18446744073709551616")! let den1 = BigInt(123) - let expected1 = BigInt(149973529054549200) + let expected1 = BigInt(UInt64(149973529054549200)) XCTAssertEqual(num1 / den1, expected1) let num2 = BigInt.pow(BigInt(10), 100) @@ -239,7 +239,7 @@ final class BigIntTests: XCTestCase { } func testClampingConversion() { - XCTAssertEqual(BigInt(clamping: UInt.max), BigInt(UInt(18446744073709551615))) + XCTAssertEqual(BigInt(clamping: UInt.max), BigInt(UInt64(18446744073709551615))) } func testUIntConversion() { From 87900161d66e8cfe4b1fcc5e7528530f7472f7f4 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 28 Jan 2020 17:36:55 -0500 Subject: [PATCH 82/92] Normalize the remainder! --- Sources/BigInt/BigInt.swift | 4 ++++ Tests/BigIntTests/BigIntTests.swift | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 67dcb0e7..6c690149 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -745,6 +745,10 @@ extension BigInt { rem[i] = (ln[i] >> s) | ln[i + 1] << (bitWidth - s) } rem[n - 1] = ln[n - 1] >> s + + if rem[n - 1] > UInt(Int.max) { + rem.append(0) + } BigInt._dropExcessWords(words: ") BigInt._dropExcessWords(words: &rem) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index fea03c21..603adce0 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -110,6 +110,28 @@ 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)) + let (quotient, remainder) = expected.quotientAndRemainder(dividingBy: divisor) + let actual = divisor * quotient + remainder + XCTAssertEqual(quotient, expected / divisor) + XCTAssertEqual(remainder, expected % divisor) + XCTAssertEqual( + actual, expected, + """ + ## FAILURE ## + ~~~~~~~~~~~~~ + actual: \(actual) + != expected: \(expected) + ~~~~~~~~~~~~~ + divisor: \(divisor) + * quotient: \(quotient) + + remainder: \(remainder) + ~~~~~~~~~~~~~ + """) + } } func testFactorial() { From 19951768df0a80d37d8d7cd67e2fab3e05962dbe Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 28 Jan 2020 17:51:45 -0500 Subject: [PATCH 83/92] Another case where we'd get wrong negatives possibly --- Sources/BigInt/BigInt.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 6c690149..9d7a43f8 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -644,6 +644,10 @@ extension BigInt { (quot[j], r) = v.dividingFullWidth((r, uj)) } + if quot[1] > UInt(Int.max) { + quot.append(0) + } + BigInt._dropExcessWords(words: ") return (quotient: BigInt(_uncheckedWords: quot), remainder: BigInt(r)) } From feeac395067e88ceeb0f10e2a1c3610e324bf95c Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 28 Jan 2020 17:53:23 -0500 Subject: [PATCH 84/92] Whoops, this is better --- Sources/BigInt/BigInt.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 9d7a43f8..5d9fd307 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -644,7 +644,7 @@ extension BigInt { (quot[j], r) = v.dividingFullWidth((r, uj)) } - if quot[1] > UInt(Int.max) { + if quot[u.count - 1] > UInt(Int.max) { quot.append(0) } From cc5f7e5d18a219ffd65a5ee1ffaa8a2e57dc96f7 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Tue, 28 Jan 2020 17:55:39 -0500 Subject: [PATCH 85/92] Apparently the @testable was only working in Xcode --- Tests/BigIntTests/BigIntTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 603adce0..bed3083b 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@testable import BigInt +import BigInt import XCTest extension BigInt { From caeeaaa9ddf4fa2613425c557bd7c179df94950f Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 29 Jan 2020 09:55:10 -0500 Subject: [PATCH 86/92] Another fix for arm32 and actually for anything with multi-word ints UInt64, Int64, etc have more than one word on 32-bit architectures --- Sources/BigInt/BigInt.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index 5d9fd307..fe260c68 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -376,7 +376,7 @@ extension BigInt: BinaryInteger { self = BigInt._digits[Int(source)] } else { words = Words(source.words) - if source > Int.max { + if source.words[source.words.endIndex - 1] > Int.max { words.append(0) } } From 52e7e70caf642b0f15ad11000d46b9a460f55dd9 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 29 Jan 2020 10:07:34 -0500 Subject: [PATCH 87/92] Actual fix for multi-word integer types --- Sources/BigInt/BigInt.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index fe260c68..ea54a0b6 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -376,7 +376,7 @@ extension BigInt: BinaryInteger { self = BigInt._digits[Int(source)] } else { words = Words(source.words) - if source.words[source.words.endIndex - 1] > Int.max { + if source > 0 && source.words[source.words.endIndex - 1] > Int.max { words.append(0) } } From aaeb9029e920a1eb4fca4a5cddf81dfa0286b1f2 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 29 Jan 2020 10:42:52 -0500 Subject: [PATCH 88/92] Another fix for conversion of multi-word Ints --- Sources/BigInt/BigInt.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index ea54a0b6..b236864e 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -379,6 +379,9 @@ extension BigInt: BinaryInteger { if source > 0 && source.words[source.words.endIndex - 1] > Int.max { words.append(0) } + // needed to handle sign-extended multi-word numbers that + // actually fit in a single word + BigInt._dropExcessWords(words: &words) } } From cd88f161eb430b295958e794ee130352c8e845ef Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 29 Jan 2020 10:48:46 -0500 Subject: [PATCH 89/92] Fix two tests for arm32, should all pass now --- Tests/BigIntTests/BigIntTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index bed3083b..684c79d1 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -172,7 +172,7 @@ final class BigIntTests: XCTestCase { XCTAssertEqual(fooz, foo / 1024) let barz = BigInt(1) << 64 - XCTAssertEqual(barz, BigInt(UInt.max) + 1) + XCTAssertEqual(barz, BigInt(UInt64.max) + 1) } func testNegation() { @@ -261,7 +261,7 @@ final class BigIntTests: XCTestCase { } func testClampingConversion() { - XCTAssertEqual(BigInt(clamping: UInt.max), BigInt(UInt64(18446744073709551615))) + XCTAssertEqual(BigInt(clamping: UInt64.max), BigInt(UInt64(18446744073709551615))) } func testUIntConversion() { From b74c1ce1332d0d71d1e1cced6f6db4b739be1867 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Fri, 14 Feb 2020 16:23:37 -0500 Subject: [PATCH 90/92] Fix `trailingZeroBitCount` as per @nixberg Also add a couple tests for it --- Sources/BigInt/BigInt.swift | 12 +++++++++++- Tests/BigIntTests/BigIntTests.swift | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index b236864e..b4b11ea1 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -395,7 +395,17 @@ extension BigInt: BinaryInteger { public var bitWidth: Int { words.count * UInt.bitWidth } - public var trailingZeroBitCount: Int { words.first?.trailingZeroBitCount ?? 0 } + public var trailingZeroBitCount: Int { + var totalZeros = 0 + for word in words { + let zeros = word.trailingZeroBitCount + totalZeros += zeros + if zeros < UInt.bitWidth { + break + } + } + return totalZeros + } @inlinable public static func / (lhs: BigInt, rhs: BigInt) -> BigInt { diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 684c79d1..264683f5 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -201,6 +201,14 @@ 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) + } // MARK: - Comparing and hashing From 5ea7711e02b3fc5c2eac0b35f3294caf18bed0c7 Mon Sep 17 00:00:00 2001 From: Robert Thompson Date: Wed, 19 Feb 2020 15:15:42 -0500 Subject: [PATCH 91/92] Probably a slightly faster way to get trailingZeroBitCOunt --- Sources/BigInt/BigInt.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigInt/BigInt.swift index b4b11ea1..2e2721c6 100644 --- a/Sources/BigInt/BigInt.swift +++ b/Sources/BigInt/BigInt.swift @@ -398,9 +398,10 @@ extension BigInt: BinaryInteger { public var trailingZeroBitCount: Int { var totalZeros = 0 for word in words { - let zeros = word.trailingZeroBitCount - totalZeros += zeros - if zeros < UInt.bitWidth { + if word == 0 { + totalZeros += UInt.bitWidth + } else { + totalZeros += word.trailingZeroBitCount break } } From 2f60891517e2d0552a14587a106f5b5252d337d7 Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Fri, 24 Apr 2020 12:39:49 +0100 Subject: [PATCH 92/92] [BigInt] Rename module See also: apple/swift-numerics#97 --- Package.swift | 10 +++++----- Sources/{BigInt => BigIntModule}/BigInt.swift | 0 Tests/BigIntTests/BigIntTests.swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename Sources/{BigInt => BigIntModule}/BigInt.swift (100%) diff --git a/Package.swift b/Package.swift index 5063c8a7..0dd64e47 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ // // This source file is part of the Swift Numerics open source project // -// Copyright (c) 2019 Apple Inc. and the Swift Numerics project authors +// Copyright (c) 2019 - 2020 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 @@ -15,7 +15,7 @@ import PackageDescription let package = Package( name: "swift-numerics", products: [ - .library(name: "BigInt", targets: ["BigInt"]), + .library(name: "BigIntModule", targets: ["BigIntModule"]), .library(name: "Complex", targets: ["Complex"]), .library(name: "Numerics", targets: ["Numerics"]), .library(name: "Real", targets: ["Real"]), @@ -23,13 +23,13 @@ let package = Package( dependencies: [ ], targets: [ - .target(name: "BigInt", dependencies: []), + .target(name: "BigIntModule", dependencies: []), .target(name: "Complex", dependencies: ["Real"]), - .target(name: "Numerics", dependencies: ["BigInt", "Complex", "Real"]), + .target(name: "Numerics", dependencies: ["BigIntModule", "Complex", "Real"]), .target(name: "NumericsShims", dependencies: []), .target(name: "Real", dependencies: ["NumericsShims"]), - .testTarget(name: "BigIntTests", dependencies: ["BigInt"]), + .testTarget(name: "BigIntTests", dependencies: ["BigIntModule"]), .testTarget(name: "ComplexTests", dependencies: ["Complex", "NumericsShims"]), .testTarget(name: "RealTests", dependencies: ["Real"]), ] diff --git a/Sources/BigInt/BigInt.swift b/Sources/BigIntModule/BigInt.swift similarity index 100% rename from Sources/BigInt/BigInt.swift rename to Sources/BigIntModule/BigInt.swift diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 264683f5..444ae38a 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import BigInt +import BigIntModule import XCTest extension BigInt {