diff --git a/Package.swift b/Package.swift index eba63bcd..249551a3 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-2021 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 @@ -71,7 +71,7 @@ let package = Package( .testTarget( name: "IntegerUtilitiesTests", - dependencies: ["IntegerUtilities"], + dependencies: ["IntegerUtilities", "_TestSupport"], exclude: ["CMakeLists.txt"] ), diff --git a/Sources/_TestSupport/CMakeLists.txt b/Sources/_TestSupport/CMakeLists.txt index 83b233f5..48b85d12 100644 --- a/Sources/_TestSupport/CMakeLists.txt +++ b/Sources/_TestSupport/CMakeLists.txt @@ -1,13 +1,14 @@ #[[ This source file is part of the Swift Numerics open source project -Copyright (c) 2019-2020 Apple Inc. and the Swift Numerics project authors +Copyright (c) 2019-2021 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 #]] add_library(_TestSupport + DoubleWidth.swift Error.swift Interval.swift RealTestSupport.swift) diff --git a/Sources/_TestSupport/DoubleWidth.swift b/Sources/_TestSupport/DoubleWidth.swift new file mode 100644 index 00000000..b41d9c31 --- /dev/null +++ b/Sources/_TestSupport/DoubleWidth.swift @@ -0,0 +1,787 @@ +//===--- DoubleWidth.swift ------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Numerics open source project +// +// Copyright (c) 2017-2021 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 +// +//===----------------------------------------------------------------------===// + +/// A fixed-width integer that has twice the bit width of its base type. +/// +/// You can use the `DoubleWidth` type to continue calculations with the result +/// of a full width arithmetic operation. Normally, when you perform a full +/// width operation, the result is a tuple of the high and low parts of the +/// result. +/// +/// let a = 2241543570477705381 +/// let b = 186319822866995413 +/// let c = a.multipliedFullWidth(by: b) +/// // c == (high: 22640526660490081, low: 7959093232766896457) +/// +/// The tuple `c` can't be used in any further comparisons or calculations. To +/// use this value, create a `DoubleWidth` instance from the result. You can +/// use the `DoubleWidth` instance in the same way that you would use any other +/// integer type. +/// +/// let d = DoubleWidth(a.multipliedFullWidth(by: b)) +/// // d == 417644001000058515200174966092417353 +/// +/// // Check the calculation: +/// print(d / DoubleWidth(a) == b) +/// // Prints "true" +/// +/// if d > Int.max { +/// print("Too big to be an 'Int'!") +/// } else { +/// print("Small enough to fit in an 'Int'") +/// } +/// // Prints "Too big to be an 'Int'!" +/// +/// The `DoubleWidth` type is not intended as a replacement for a variable-width +/// integer type. Nesting `DoubleWidth` instances, in particular, may result in +/// undesirable performance. +public struct DoubleWidth { + public typealias High = Base + public typealias Low = Base.Magnitude + +#if _endian(big) + internal var _storage: (high: High, low: Low) +#else + internal var _storage: (low: Low, high: High) +#endif + + /// The high part of the value. + public var high: High { + return _storage.high + } + + /// The low part of the value. + public var low: Low { + return _storage.low + } + + /// Creates a new instance from the given tuple of high and low parts. + /// + /// - Parameter value: The tuple to use as the source of the new instance's + /// high and low parts. + public init(_ value: (high: High, low: Low)) { +#if _endian(big) + self._storage = (high: value.0, low: value.1) +#else + self._storage = (low: value.1, high: value.0) +#endif + } + + // We expect users to invoke the public initializer above as demonstrated in + // the documentation (that is, by passing in the result of a full width + // operation). + // + // Internally, we'll need to create new instances by supplying high and low + // parts directly; ((double parentheses)) greatly impair readability, + // especially when nested: + // + // DoubleWidth((DoubleWidth((0, 0)), DoubleWidth((0, 0)))) + // + // For that reason, we'll include an internal initializer that takes two + // separate arguments. + internal init(_ _high: High, _ low: Low) { + self.init((_high, low)) + } + + public init() { + self.init(0, 0) + } +} + +extension DoubleWidth : CustomStringConvertible { + public var description: String { + return String(self, radix: 10) + } +} + +extension DoubleWidth : CustomDebugStringConvertible { + public var debugDescription: String { + return "(\(_storage.high), \(_storage.low))" + } +} + +extension DoubleWidth : Equatable { + public static func ==(lhs: DoubleWidth, rhs: DoubleWidth) -> Bool { + return lhs._storage.low == rhs._storage.low + && lhs._storage.high == rhs._storage.high + } +} + +extension DoubleWidth : Comparable { + public static func <(lhs: DoubleWidth, rhs: DoubleWidth) -> Bool { + if lhs._storage.high < rhs._storage.high { return true } + else if lhs._storage.high > rhs._storage.high { return false } + else { return lhs._storage.low < rhs._storage.low } + } +} + +extension DoubleWidth : Hashable { + public var hashValue: Int { + return _hashValue(for: self) + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(low) + hasher.combine(high) + } +} + +extension DoubleWidth : Numeric { + public typealias Magnitude = DoubleWidth + + public var magnitude: Magnitude { + let result = Magnitude(Low(truncatingIfNeeded: _storage.high), _storage.low) + if Base.isSigned && _storage.high < (0 as High) { + return ~result &+ 1 + } else { + return result + } + } + + internal init(_ _magnitude: Magnitude) { + self.init(High(_magnitude._storage.high), _magnitude._storage.low) + } + + public init(_ source: T) { + guard let result = DoubleWidth(exactly: source) else { + preconditionFailure("Value is outside the representable range") + } + self = result + } + + public init?(exactly source: T) { + // Can't represent a negative 'source' if Base is unsigned. + guard DoubleWidth.isSigned || source >= 0 else { return nil } + + // Is 'source' entirely representable in Low? + if let low = Low(exactly: source.magnitude) { + self.init(source < (0 as T) ? (~0, ~low &+ 1) : (0, low)) + } else { + // At this point we know source.bitWidth > Base.bitWidth, or else we + // would've taken the first branch. + let lowInT = source & T(~0 as Low) + let highInT = source >> Low.bitWidth + + let low = Low(lowInT) + guard let high = High(exactly: highInT) else { return nil } + self.init(high, low) + } + } +} + +#if false + +// This conformance is only possible once the type is in the stdlib, as it uses +// Builtin +extension DoubleWidth: _ExpressibleByBuiltinIntegerLiteral { + public init(_builtinIntegerLiteral _x: Builtin.IntegerLiteral) { + var _x = _x + self = DoubleWidth() + + // If we can capture the entire literal in a single Int64, stop there. + // This avoids some potential deep recursion due to literal expressions in + // other DoubleWidth methods. + let (_value, _overflow) = Builtin.s_to_s_checked_trunc_IntLiteral_Int64(_x) + if !Bool(_overflow) { + self = DoubleWidth(Int64(_value)) + return + } + + // Convert all but the most significant 64 bits as unsigned integers. + let _shift = Builtin.sext_Int64_IntLiteral((64 as Int64)._value) + let lowWordCount = (bitWidth - 1) / 64 + for i in 0..) { + // Multiples of word size only. + guard Base.bitWidth == Base.Magnitude.bitWidth && + (UInt.bitWidth % Base.bitWidth == 0 || + Base.bitWidth % UInt.bitWidth == 0) else { + fatalError("Access to words is not supported on this type") + } + self._high = value._storage.high.words + self._low = value._storage.low.words + assert(!_low.isEmpty) + } + } +} + +extension DoubleWidth.Words: RandomAccessCollection { + public typealias Index = Int + + public var startIndex: Index { + return 0 + } + + public var endIndex: Index { + return count + } + + public var count: Int { + if Base.bitWidth < UInt.bitWidth { return 1 } + return _low.count + _high.count + } + + public subscript(_ i: Index) -> UInt { + if Base.bitWidth < UInt.bitWidth { + precondition(i == 0, "Invalid index") + assert(2 * Base.bitWidth <= UInt.bitWidth) + return _low.first! | (_high.first! &<< Base.bitWidth._lowWord) + } + if i < _low.count { + return _low[i + _low.startIndex] + } + + return _high[i - _low.count + _high.startIndex] + } +} + +extension DoubleWidth : FixedWidthInteger { + public var words: Words { + return Words(self) + } + + public static var isSigned: Bool { + return Base.isSigned + } + + public static var max: DoubleWidth { + return self.init(High.max, Low.max) + } + + public static var min: DoubleWidth { + return self.init(High.min, Low.min) + } + + public static var bitWidth: Int { + return High.bitWidth + Low.bitWidth + } + + public func addingReportingOverflow(_ rhs: DoubleWidth) + -> (partialValue: DoubleWidth, overflow: Bool) { + let (low, lowOverflow) = + _storage.low.addingReportingOverflow(rhs._storage.low) + let (high, highOverflow) = + _storage.high.addingReportingOverflow(rhs._storage.high) + let result = (high &+ (lowOverflow ? 1 : 0), low) + let overflow = highOverflow || + high == Base.max && lowOverflow + return (partialValue: DoubleWidth(result), overflow: overflow) + } + public func subtractingReportingOverflow(_ rhs: DoubleWidth) + -> (partialValue: DoubleWidth, overflow: Bool) { + let (low, lowOverflow) = + _storage.low.subtractingReportingOverflow(rhs._storage.low) + let (high, highOverflow) = + _storage.high.subtractingReportingOverflow(rhs._storage.high) + let result = (high &- (lowOverflow ? 1 : 0), low) + let overflow = highOverflow || + high == Base.min && lowOverflow + return (partialValue: DoubleWidth(result), overflow: overflow) + } + + public func multipliedReportingOverflow( + by rhs: DoubleWidth + ) -> (partialValue: DoubleWidth, overflow: Bool) { + let (carry, product) = multipliedFullWidth(by: rhs) + let result = DoubleWidth(truncatingIfNeeded: product) + + let isNegative = DoubleWidth.isSigned && + (self < (0 as DoubleWidth)) != (rhs < (0 as DoubleWidth)) + let didCarry = isNegative + ? carry != ~(0 as DoubleWidth) + : carry != (0 as DoubleWidth) + let hadPositiveOverflow = + DoubleWidth.isSigned && !isNegative && product.leadingZeroBitCount == 0 + + return (result, didCarry || hadPositiveOverflow) + } + + public func quotientAndRemainder( + dividingBy other: DoubleWidth + ) -> (quotient: DoubleWidth, remainder: DoubleWidth) { + let (quotient, remainder) = + Magnitude._divide(self.magnitude, by: other.magnitude) + guard DoubleWidth.isSigned else { + return (DoubleWidth(quotient), DoubleWidth(remainder)) + } + let isNegative = (self.high < (0 as High)) != (other.high < (0 as High)) + let quotient_ = isNegative + ? quotient == DoubleWidth.min.magnitude + ? DoubleWidth.min + : 0 - DoubleWidth(quotient) + : DoubleWidth(quotient) + let remainder_ = self.high < (0 as High) + ? 0 - DoubleWidth(remainder) + : DoubleWidth(remainder) + return (quotient_, remainder_) + } + + public func dividedReportingOverflow( + by other: DoubleWidth + ) -> (partialValue: DoubleWidth, overflow: Bool) { + if other == (0 as DoubleWidth) { return (self, true) } + if DoubleWidth.isSigned && other == -1 && self == .min { + return (self, true) + } + return (quotientAndRemainder(dividingBy: other).quotient, false) + } + + public func remainderReportingOverflow( + dividingBy other: DoubleWidth + ) -> (partialValue: DoubleWidth, overflow: Bool) { + if other == (0 as DoubleWidth) { return (self, true) } + if DoubleWidth.isSigned && other == -1 && self == .min { return (0, true) } + return (quotientAndRemainder(dividingBy: other).remainder, false) + } + + public func multipliedFullWidth( + by other: DoubleWidth + ) -> (high: DoubleWidth, low: DoubleWidth.Magnitude) { + let isNegative = DoubleWidth.isSigned && + (self < (0 as DoubleWidth)) != (other < (0 as DoubleWidth)) + + func mul(_ x: Low, _ y: Low) -> (partial: Low, carry: Low) { + let (high, low) = x.multipliedFullWidth(by: y) + return (low, high) + } + + func sum(_ x: Low, _ y: Low, _ z: Low) -> (partial: Low, carry: Low) { + let (sum1, overflow1) = x.addingReportingOverflow(y) + let (sum2, overflow2) = sum1.addingReportingOverflow(z) + let carry: Low = (overflow1 ? 1 : 0) + (overflow2 ? 1 : 0) + return (sum2, carry) + } + + let lhs = self.magnitude + let rhs = other.magnitude + + let a = mul(rhs._storage.low, lhs._storage.low) + let b = mul(rhs._storage.low, lhs._storage.high) + let c = mul(rhs._storage.high, lhs._storage.low) + let d = mul(rhs._storage.high, lhs._storage.high) + + let mid1 = sum(a.carry, b.partial, c.partial) + let mid2 = sum(b.carry, c.carry, d.partial) + + let low = + DoubleWidth(mid1.partial, a.partial) + let high = + DoubleWidth(High(mid2.carry + d.carry), mid1.carry + mid2.partial) + + if isNegative { + let (lowComplement, overflow) = (~low).addingReportingOverflow(1) + return (~high + (overflow ? 1 : 0 as DoubleWidth), lowComplement) + } else { + return (high, low) + } + } + + public func dividingFullWidth( + _ dividend: (high: DoubleWidth, low: DoubleWidth.Magnitude) + ) -> (quotient: DoubleWidth, remainder: DoubleWidth) { + let other = DoubleWidth(dividend) + let (quotient, remainder) = + Magnitude._divide(other.magnitude, by: self.magnitude) + guard DoubleWidth.isSigned else { + return (DoubleWidth(quotient), DoubleWidth(remainder)) + } + let isNegative = + (self.high < (0 as High)) != (other.high.high < (0 as High)) + let quotient_ = isNegative + ? quotient == DoubleWidth.min.magnitude + ? DoubleWidth.min + : 0 - DoubleWidth(quotient) + : DoubleWidth(quotient) + let remainder_ = other.high.high < (0 as High) + ? 0 - DoubleWidth(remainder) + : DoubleWidth(remainder) + return (quotient_, remainder_) + } + + public static func &=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + lhs._storage.low &= rhs._storage.low + lhs._storage.high &= rhs._storage.high + } + public static func |=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + lhs._storage.low |= rhs._storage.low + lhs._storage.high |= rhs._storage.high + } + public static func ^=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + lhs._storage.low ^= rhs._storage.low + lhs._storage.high ^= rhs._storage.high + } + + public static func <<=(lhs: inout DoubleWidth, rhs: DoubleWidth) { + if DoubleWidth.isSigned && rhs < (0 as DoubleWidth) { + lhs >>= 0 - rhs + return + } + + // Shift is larger than this type's bit width. + if rhs._storage.high != (0 as High) || + rhs._storage.low >= DoubleWidth.bitWidth + { + lhs = 0 + return + } + + lhs &<<= rhs + } + + public static func >>=(lhs: inout DoubleWidth, rhs: DoubleWidth) { + if DoubleWidth.isSigned && rhs < (0 as DoubleWidth) { + lhs <<= 0 - rhs + return + } + + // Shift is larger than this type's bit width. + if rhs._storage.high != (0 as High) || + rhs._storage.low >= DoubleWidth.bitWidth + { + lhs = lhs < (0 as DoubleWidth) ? ~0 : 0 + return + } + + lhs &>>= rhs + } + + /// Returns this value "masked" by its bit width. + /// + /// "Masking" notionally involves repeatedly incrementing or decrementing this + /// value by `self.bitWidth` until the result is contained in the range + /// `0.. DoubleWidth { + // FIXME(integers): test types with bit widths that aren't powers of 2 + if DoubleWidth.bitWidth.nonzeroBitCount == 1 { + return self & DoubleWidth(DoubleWidth.bitWidth &- 1) + } + if DoubleWidth.isSigned && self._storage.high < (0 as High) { + return self % DoubleWidth(DoubleWidth.bitWidth) + self + } + return self % DoubleWidth(DoubleWidth.bitWidth) + } + + public static func &<<=(lhs: inout DoubleWidth, rhs: DoubleWidth) { + let rhs = rhs._masked() + + guard rhs._storage.low < Base.bitWidth else { + lhs._storage.high = High( + truncatingIfNeeded: lhs._storage.low &<< + (rhs._storage.low &- Low(Base.bitWidth))) + lhs._storage.low = 0 + return + } + + guard rhs._storage.low != (0 as Low) else { return } + lhs._storage.high &<<= High(rhs._storage.low) + lhs._storage.high |= High( + truncatingIfNeeded: lhs._storage.low &>> + (Low(Base.bitWidth) &- rhs._storage.low)) + lhs._storage.low &<<= rhs._storage.low + } + + public static func &>>=(lhs: inout DoubleWidth, rhs: DoubleWidth) { + let rhs = rhs._masked() + + guard rhs._storage.low < Base.bitWidth else { + lhs._storage.low = Low( + truncatingIfNeeded: lhs._storage.high &>> + High(rhs._storage.low &- Low(Base.bitWidth))) + lhs._storage.high = lhs._storage.high < (0 as High) ? ~0 : 0 + return + } + + guard rhs._storage.low != (0 as Low) else { return } + lhs._storage.low &>>= rhs._storage.low + lhs._storage.low |= Low( + truncatingIfNeeded: lhs._storage.high &<< + High(Low(Base.bitWidth) &- rhs._storage.low)) + lhs._storage.high &>>= High(rhs._storage.low) + } + + + // FIXME(integers): remove this once the operators are back to Numeric + public static func + ( + lhs: DoubleWidth, rhs: DoubleWidth + ) -> DoubleWidth { + var lhs = lhs + lhs += rhs + return lhs + } + + public static func +=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + let (result, overflow) = lhs.addingReportingOverflow(rhs) + precondition(!overflow, "Overflow in +=") + lhs = result + } + + // FIXME(integers): remove this once the operators are back to Numeric + public static func - ( + lhs: DoubleWidth, rhs: DoubleWidth + ) -> DoubleWidth { + var lhs = lhs + lhs -= rhs + return lhs + } + + public static func -=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + let (result, overflow) = lhs.subtractingReportingOverflow(rhs) + precondition(!overflow, "Overflow in -=") + lhs = result + } + + // FIXME(integers): remove this once the operators are back to Numeric + public static func * ( + lhs: DoubleWidth, rhs: DoubleWidth + ) -> DoubleWidth { + var lhs = lhs + lhs *= rhs + return lhs + } + + public static func *=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + let (result, overflow) = lhs.multipliedReportingOverflow(by:rhs) + precondition(!overflow, "Overflow in *=") + lhs = result + } + + // FIXME(integers): remove this once the operators are back to Numeric + public static func / ( + lhs: DoubleWidth, rhs: DoubleWidth + ) -> DoubleWidth { + var lhs = lhs + lhs /= rhs + return lhs + } + + public static func /=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + let (result, overflow) = lhs.dividedReportingOverflow(by:rhs) + precondition(!overflow, "Overflow in /=") + lhs = result + } + + // FIXME(integers): remove this once the operators are back to Numeric + public static func % ( + lhs: DoubleWidth, rhs: DoubleWidth + ) -> DoubleWidth { + var lhs = lhs + lhs %= rhs + return lhs + } + + public static func %=( + lhs: inout DoubleWidth, rhs: DoubleWidth + ) { + let (result, overflow) = lhs.remainderReportingOverflow(dividingBy:rhs) + precondition(!overflow, "Overflow in %=") + lhs = result + } + + public init(_truncatingBits bits: UInt) { + _storage.low = Low(_truncatingBits: bits) + _storage.high = High(_truncatingBits: bits >> UInt(Low.bitWidth)) + } + + public init(integerLiteral x: Int) { + self.init(x) + } + public var leadingZeroBitCount: Int { + return high == (0 as High) + ? High.bitWidth + low.leadingZeroBitCount + : high.leadingZeroBitCount + } + + public var trailingZeroBitCount: Int { + return low == (0 as Low) + ? Low.bitWidth + high.trailingZeroBitCount + : low.trailingZeroBitCount + } + + public var nonzeroBitCount: Int { + return high.nonzeroBitCount + low.nonzeroBitCount + } + + public var byteSwapped: DoubleWidth { + return DoubleWidth( + High(truncatingIfNeeded: low.byteSwapped), + Low(truncatingIfNeeded: high.byteSwapped)) + } +} + +extension DoubleWidth : UnsignedInteger where Base : UnsignedInteger { + /// Returns the quotient and remainder after dividing a triple-width magnitude + /// `lhs` by a double-width magnitude `rhs`. + /// + /// This operation is conceptually that described by Burnikel and Ziegler + /// (1998). + internal static func _divide( + _ lhs: (high: Low, mid: Low, low: Low), by rhs: Magnitude + ) -> (quotient: Low, remainder: Magnitude) { + // The following invariants are guaranteed to hold by dividingFullWidth or + // quotientAndRemainder before this method is invoked: + assert(lhs.high != (0 as Low)) + assert(rhs.leadingZeroBitCount == 0) + assert(Magnitude(lhs.high, lhs.mid) < rhs) + + // Estimate the quotient. + var quotient = lhs.high == rhs.high + ? Low.max + : rhs.high.dividingFullWidth((lhs.high, lhs.mid)).quotient + // Compute quotient * rhs. + // TODO: This could be performed more efficiently. + var product = + DoubleWidth( + 0, Magnitude(quotient.multipliedFullWidth(by: rhs.low))) + let (x, y) = quotient.multipliedFullWidth(by: rhs.high) + product += DoubleWidth(Magnitude(0, x), Magnitude(y, 0)) + // Compute the remainder after decrementing quotient as necessary. + var remainder = + DoubleWidth( + Magnitude(0, lhs.high), Magnitude(lhs.mid, lhs.low)) + while remainder < product { + quotient = quotient &- 1 + remainder += DoubleWidth(0, rhs) + } + remainder -= product + + return (quotient, remainder.low) + } + + /// Returns the quotient and remainder after dividing a quadruple-width + /// magnitude `lhs` by a double-width magnitude `rhs`. + internal static func _divide( + _ lhs: DoubleWidth, by rhs: Magnitude + ) -> (quotient: Magnitude, remainder: Magnitude) { + guard _fastPath(rhs > (0 as Magnitude)) else { + fatalError("Division by zero") + } + guard _fastPath(rhs >= lhs.high) else { + fatalError("Division results in an overflow") + } + + if lhs.high == (0 as Magnitude) { + return lhs.low.quotientAndRemainder(dividingBy: rhs) + } + + if rhs.high == (0 as Low) { + let a = lhs.high.high % rhs.low + let b = a == (0 as Low) + ? lhs.high.low % rhs.low + : rhs.low.dividingFullWidth((a, lhs.high.low)).remainder + let (x, c) = b == (0 as Low) + ? lhs.low.high.quotientAndRemainder(dividingBy: rhs.low) + : rhs.low.dividingFullWidth((b, lhs.low.high)) + let (y, d) = c == (0 as Low) + ? lhs.low.low.quotientAndRemainder(dividingBy: rhs.low) + : rhs.low.dividingFullWidth((c, lhs.low.low)) + return (Magnitude(x, y), Magnitude(0, d)) + } + + // Left shift both rhs and lhs, then divide and right shift the remainder. + let shift = rhs.leadingZeroBitCount + let rhs = rhs &<< shift + let lhs = lhs &<< shift + if lhs.high.high == (0 as Low) + && Magnitude(lhs.high.low, lhs.low.high) < rhs { + let (quotient, remainder) = + Magnitude._divide((lhs.high.low, lhs.low.high, lhs.low.low), by: rhs) + return (Magnitude(0, quotient), remainder &>> shift) + } + let (x, a) = + Magnitude._divide((lhs.high.high, lhs.high.low, lhs.low.high), by: rhs) + let (y, b) = + Magnitude._divide((a.high, a.low, lhs.low.low), by: rhs) + return (Magnitude(x, y), b &>> shift) + } + + /// Returns the quotient and remainder after dividing a double-width + /// magnitude `lhs` by a double-width magnitude `rhs`. + internal static func _divide( + _ lhs: Magnitude, by rhs: Magnitude + ) -> (quotient: Magnitude, remainder: Magnitude) { + guard _fastPath(rhs > (0 as Magnitude)) else { + fatalError("Division by zero") + } + guard rhs < lhs else { + if _fastPath(rhs > lhs) { return (0, lhs) } + return (1, 0) + } + + if lhs.high == (0 as Low) { + let (quotient, remainder) = + lhs.low.quotientAndRemainder(dividingBy: rhs.low) + return (Magnitude(quotient), Magnitude(remainder)) + } + + if rhs.high == (0 as Low) { + let (x, a) = lhs.high.quotientAndRemainder(dividingBy: rhs.low) + let (y, b) = a == (0 as Low) + ? lhs.low.quotientAndRemainder(dividingBy: rhs.low) + : rhs.low.dividingFullWidth((a, lhs.low)) + return (Magnitude(x, y), Magnitude(0, b)) + } + + // Left shift both rhs and lhs, then divide and right shift the remainder. + let shift = rhs.leadingZeroBitCount + let rhs = rhs &<< shift + let high = (lhs &>> (Magnitude.bitWidth &- shift)).low + let lhs = lhs &<< shift + let (quotient, remainder) = + Magnitude._divide((high, lhs.high, lhs.low), by: rhs) + return (Magnitude(0, quotient), remainder &>> shift) + } +} + +extension DoubleWidth : SignedNumeric, SignedInteger + where Base : SignedInteger {} diff --git a/Tests/IntegerUtilitiesTests/CMakeLists.txt b/Tests/IntegerUtilitiesTests/CMakeLists.txt index 194e9fe2..a6522d2d 100644 --- a/Tests/IntegerUtilitiesTests/CMakeLists.txt +++ b/Tests/IntegerUtilitiesTests/CMakeLists.txt @@ -9,6 +9,7 @@ See https://swift.org/LICENSE.txt for license information add_library(IntegerUtilitiesTests DivideTests.swift + DoubleWidthTests.swift GCDTests.swift RotateTests.swift ShiftTests.swift) @@ -17,5 +18,5 @@ target_compile_options(IntegerUtilitiesTests PRIVATE target_link_libraries(IntegerUtilitiesTests PUBLIC $<$>:Foundation> Numerics + _TestSupport XCTest) - diff --git a/Tests/IntegerUtilitiesTests/DoubleWidthTests.swift b/Tests/IntegerUtilitiesTests/DoubleWidthTests.swift new file mode 100644 index 00000000..8e685b25 --- /dev/null +++ b/Tests/IntegerUtilitiesTests/DoubleWidthTests.swift @@ -0,0 +1,497 @@ +//===--- DoubleWidthTests.swift -------------------------------*- swift -*-===// +// +// This source file is part of the Swift Numerics open source project +// +// Copyright (c) 2017-2021 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 _TestSupport +import XCTest + +final class DoubleWidthTests: XCTestCase { + + typealias _Int16 = DoubleWidth + typealias _Int128 = DoubleWidth + typealias _Int256 = DoubleWidth<_Int128> + typealias _Int512 = DoubleWidth<_Int256> + typealias _Int1024 = DoubleWidth<_Int512> + + typealias _UInt16 = DoubleWidth + typealias _UInt128 = DoubleWidth + typealias _UInt256 = DoubleWidth<_UInt128> + typealias _UInt512 = DoubleWidth<_UInt256> + typealias _UInt1024 = DoubleWidth<_UInt512> + + // + // Swift 4.x took over 15 minutes to compile this test code. + // Swift 5.5 takes less than one second (`-Onone` or `-O` or `-Osize`). + func testCompileTime_SR_6947() { + typealias _Int32 = DoubleWidth> + typealias _Int64 = DoubleWidth>> + + var sum = 0 + let (q, r) = (_Int128(Int64.max) * 16).quotientAndRemainder(dividingBy: 16) + sum += Int(q * r) + XCTAssertEqual(sum, 0) + + let x = _Int64(Int64.max / 4) + let y = _Int32(Int32.max) + let xx = _Int1024(x) + let yy = _Int512(y) + let (q3, r3) = yy.dividingFullWidth((xx.high, xx.low)) + sum -= Int(q3 - r3) + XCTAssertEqual(sum, -1) + } + + func testLiterals() { + let w: _UInt16 = 100 + XCTAssertTrue(w == 100 as Int) + + let x: _UInt16 = 1000 + XCTAssertTrue(x == 1000 as Int) + + let y: _Int16 = 1000 + XCTAssertTrue(y == 1000 as Int) + + let z: _Int16 = -1000 + XCTAssertTrue(z == -1000 as Int) + } + + func testLiterals_Underflow() { + // TODO: expectCrashLater() + // _ = -1 as _UInt16 + } + +#if false // TODO: _ExpressibleByBuiltinIntegerLiteral + + func testLiterals_LargeSigned() { + let a: _Int256 = + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + let b: _Int256 = + -0x8000000000000000000000000000000000000000000000000000000000000000 + XCTAssertEqual(a, _Int256.max) + XCTAssertEqual(b, _Int256.min) + } + + func testLiterals_LargeSigned_Underflow() { + // TODO: expectCrashLater() + // _ = -0x8000000000000000000000000000000000000000000000000000000000000001 + // as _Int256 + } + + func testLiterals_LargeSigned_Overflow() { + // TODO: expectCrashLater() + // _ = 0x8000000000000000000000000000000000000000000000000000000000000000 + // as _Int256 + } + + func testLiterals_LargeUnsigned() { + let a: _UInt256 = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + let b: _UInt256 = 0 + XCTAssertEqual(a, _UInt256.max) + XCTAssertEqual(b, _UInt256.min) + } + + func testLiterals_LargeUnsigned_Underflow() { + // TODO: expectCrashLater() + // _ = -1 as _UInt256 + } + + func testLiterals_LargeUnsigned_Overflow() { + // TODO: expectCrashLater() + // _ = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0 + // as _UInt256 + } + +#endif // TODO: _ExpressibleByBuiltinIntegerLiteral + + func testArithmetic_Unsigned() { + let x: _UInt16 = 1000 + let y: _UInt16 = 1111 + XCTAssertEqual(x + 1, 1001) + XCTAssertEqual(x + x, 2000) + XCTAssertEqual(x - (1 as _UInt16), 999) + XCTAssertEqual(x - x, 0) + XCTAssertEqual(y - x, 111) + + XCTAssertEqual(x * 7, 7000) + XCTAssertEqual(y * 7, 7777) + + XCTAssertEqual(x / 3, 333) + XCTAssertEqual(x / x, 1) + XCTAssertEqual(x / y, 0) + XCTAssertEqual(y / x, 1) + + XCTAssertEqual(x % 3, 1) + XCTAssertEqual(x % y, x) + } + + func testArithmetic_Signed() { + let x: _Int16 = 1000 + let y: _Int16 = -1111 + XCTAssertEqual(x + 1, 1001) + XCTAssertEqual(x + x, 2000) + XCTAssertEqual(x - (1 as _Int16), 999) + XCTAssertEqual(x - x, 0) + XCTAssertEqual(0 - x, -1000) + XCTAssertEqual(x + y, -111) + XCTAssertEqual(x - y, 2111) + + XCTAssertEqual(x * 7, 7000) + XCTAssertEqual(y * 7, -7777) + XCTAssertEqual(x * -7, -7000) + XCTAssertEqual(y * -7, 7777) + + XCTAssertEqual(x / 3, 333) + XCTAssertEqual(x / -3, -333) + XCTAssertEqual(x / x, 1) + XCTAssertEqual(x / y, 0) + XCTAssertEqual(y / x, -1) + XCTAssertEqual(y / y, 1) + + XCTAssertEqual(x % 3, 1) + XCTAssertEqual(x % -3, 1) + XCTAssertEqual(y % 3, -1) + XCTAssertEqual(y % -3, -1) + + XCTAssertEqual(-y, 1111) + XCTAssertEqual(-x, -1000) + } + + func testNested() { + do { + let x = _UInt1024.max + let (y, o) = x.addingReportingOverflow(1) + XCTAssertEqual(y, 0) + XCTAssertTrue(y == (0 as Int)) + XCTAssertTrue(o) + } + + do { + let x = _Int1024.max + let (y, o) = x.addingReportingOverflow(1) + XCTAssertEqual(y, _Int1024.min) + XCTAssertLessThan(y, 0) + XCTAssertTrue(y < (0 as Int)) + XCTAssertTrue(y < (0 as UInt)) + XCTAssertTrue(o) + } + + XCTAssertFalse(_UInt1024.isSigned) + XCTAssertEqual(_UInt1024.bitWidth, 1024) + XCTAssertTrue(_Int1024.isSigned) + XCTAssertEqual(_Int1024.bitWidth, 1024) + + XCTAssertTrue( + _UInt1024.max.words.elementsEqual( + repeatElement(UInt.max, count: 1024 / UInt.bitWidth) + ) + ) + } + + func testInitialization() { + XCTAssertTrue(_UInt16(UInt16.max) == UInt16.max) + XCTAssertNil(_UInt16(exactly: UInt32.max)) + XCTAssertEqual(_UInt16(truncatingIfNeeded: UInt64.max), _UInt16.max) + } + + func testInitialization_Overflow() { + // TODO: expectCrashLater() + // _ = _UInt16(UInt32.max) + } + + func testMagnitude() { + XCTAssertTrue(_UInt16.min.magnitude == UInt16.min.magnitude) + XCTAssertTrue((42 as _UInt16).magnitude == (42 as UInt16).magnitude) + XCTAssertTrue(_UInt16.max.magnitude == UInt16.max.magnitude) + + XCTAssertTrue(_Int16.min.magnitude == Int16.min.magnitude) + XCTAssertTrue((-42 as _Int16).magnitude == (-42 as Int16).magnitude) + XCTAssertTrue(_Int16().magnitude == Int16(0).magnitude) // See SR-6602. + XCTAssertTrue((42 as _Int16).magnitude == (42 as Int16).magnitude) + XCTAssertTrue(_Int16.max.magnitude == Int16.max.magnitude) + } + + func testTwoWords() { + typealias DW = DoubleWidth + + XCTAssertEqual(-1 as DW, DW(truncatingIfNeeded: -1 as Int8)) + + XCTAssertNil(Int(exactly: DW(Int.min) - 1)) + XCTAssertNil(Int(exactly: DW(Int.max) + 1)) + + XCTAssertTrue(DW(Int.min) - 1 < Int.min) + XCTAssertTrue(DW(Int.max) + 1 > Int.max) + } + + func testBitwise_LeftAndRightShifts() { + typealias _UInt64 = DoubleWidth>> + typealias _Int64 = DoubleWidth>> + + func f(_ x: T, type: U.Type) { + let y = U(x) + XCTAssertEqual(T.bitWidth, U.bitWidth) + for i in -(T.bitWidth + 1)...(T.bitWidth + 1) { + XCTAssertTrue(x << i == y << i) + XCTAssertTrue(x >> i == y >> i) + + XCTAssertTrue(x &<< i == y &<< i) + XCTAssertTrue(x &>> i == y &>> i) + } + } + + f(1 as UInt64, type: _UInt64.self) + f(~(~0 as UInt64 >> 1), type: _UInt64.self) + f(UInt64.max, type: _UInt64.self) + // 0b01010101_10100101_11110000_10100101_11110000_10100101_11110000_10100101 + f(17340530535757639845 as UInt64, type: _UInt64.self) + + f(1 as Int64, type: _Int64.self) + f(Int64.min, type: _Int64.self) + f(Int64.max, type: _Int64.self) + // 0b01010101_10100101_11110000_10100101_11110000_10100101_11110000_10100101 + f(6171603459878809765 as Int64, type: _Int64.self) + } + + func testRemainder_ByZero() { + func f(_ x: _Int1024, _ y: _Int1024) -> _Int1024 { + return x % y + } + // TODO: expectCrashLater() + // _ = f(42, 0) + } + + func testRemainder_ByMinusOne() { + func f(_ x: _Int256, _ y: _Int256) -> _Int256 { + return x.remainderReportingOverflow(dividingBy: y).partialValue + } + XCTAssertEqual(f(.max, -1), 0) + XCTAssertEqual(f(.min, -1), 0) + } + + func testDivision_ByZero() { + func f(_ x: _Int1024, _ y: _Int1024) -> _Int1024 { + return x / y + } + // TODO: expectCrashLater() + // _ = f(42, 0) + } + + func testDivision_ByMinusOne() { + func f(_ x: _Int1024) -> _Int1024 { + return x / -1 + } + // TODO: expectCrashLater() + // _ = f(_Int1024.min) + } + + func testMultipleOf() { + func isMultipleTest(type: T.Type) { + XCTAssertTrue(T.min.isMultiple(of: 2)) + XCTAssertFalse(T.max.isMultiple(of: 10)) + // Test that these do not crash. + XCTAssertTrue((0 as T).isMultiple(of: 0)) + XCTAssertFalse((1 as T).isMultiple(of: 0)) + XCTAssertTrue(T.min.isMultiple(of: 0 &- 1)) + } + isMultipleTest(type: _Int128.self) + isMultipleTest(type: _UInt128.self) + } + + func testMultiplication_ByMinusOne() { + func f(_ x: _Int1024) -> _Int1024 { + return x * -1 + } + // TODO: expectCrashLater() + // _ = f(_Int1024.min) + } + + func testConversions() { + XCTAssertTrue(_Int16(1 << 15 - 1) == Int(1 << 15 - 1)) + XCTAssertTrue(_Int16(-1 << 15) == Int(-1 << 15)) + XCTAssertTrue(_UInt16(1 << 16 - 1) == Int(1 << 16 - 1)) + XCTAssertTrue(_UInt16(0) == Int(0)) + + XCTAssertTrue(_Int16(Double(1 << 15 - 1)) == Int(1 << 15 - 1)) + XCTAssertTrue(_Int16(Double(-1 << 15)) == Int(-1 << 15)) + XCTAssertTrue(_UInt16(Double(1 << 16 - 1)) == Int(1 << 16 - 1)) + XCTAssertTrue(_UInt16(Double(0)) == Int(0)) + + XCTAssertTrue(_Int16(Double(1 << 15 - 1) + 0.9) == Int(1 << 15 - 1)) + XCTAssertTrue(_Int16(Double(-1 << 15) - 0.9) == Int(-1 << 15)) + XCTAssertTrue(_UInt16(Double(1 << 16 - 1) + 0.9) == Int(1 << 16 - 1)) + XCTAssertTrue(_UInt16(Double(0) - 0.9) == Int(0)) + + XCTAssertEqual(_Int16(0.00001), 0) + XCTAssertEqual(_UInt16(0.00001), 0) + } + + func testConversions_Exact() { + XCTAssertEqual( + _Int16(Double(1 << 15 - 1)), + _Int16(exactly: Double(1 << 15 - 1)) + ) + XCTAssertEqual( + _Int16(Double(-1 << 15)), + _Int16(exactly: Double(-1 << 15)) + ) + XCTAssertEqual( + _UInt16(Double(1 << 16 - 1)), + _UInt16(exactly: Double(1 << 16 - 1)) + ) + XCTAssertEqual( + _UInt16(Double(0)), + _UInt16(exactly: Double(0)) + ) + + XCTAssertNil(_Int16(exactly: Double(1 << 15 - 1) + 0.9)) + XCTAssertNil(_Int16(exactly: Double(-1 << 15) - 0.9)) + XCTAssertNil(_UInt16(exactly: Double(1 << 16 - 1) + 0.9)) + XCTAssertNil(_UInt16(exactly: Double(0) - 0.9)) + + XCTAssertNil(_Int16(exactly: Double(1 << 15))) + XCTAssertNil(_Int16(exactly: Double(-1 << 15) - 1)) + XCTAssertNil(_UInt16(exactly: Double(1 << 16))) + XCTAssertNil(_UInt16(exactly: Double(-1))) + + XCTAssertNil(_Int16(exactly: 0.00001)) + XCTAssertNil(_UInt16(exactly: 0.00001)) + + XCTAssertNil(_UInt16(exactly: Double.nan)) + XCTAssertNil(_UInt16(exactly: Float.nan)) + XCTAssertNil(_UInt16(exactly: Double.infinity)) + XCTAssertNil(_UInt16(exactly: Float.infinity)) + } + + func testConversions_SignedMax() { + // TODO: expectCrashLater() + // _ = _Int16(1 << 15) + } + + func testConversions_SignedMin() { + // TODO: expectCrashLater() + // _ = _Int16(-1 << 15 - 1) + } + + func testConversions_UnsignedMax() { + // TODO: expectCrashLater() + // _ = _UInt16(1 << 16) + } + + func testConversions_UnsignedMin() { + // TODO: expectCrashLater() + // _ = _UInt16(-1) + } + + func testConversions_ToAndFromString( + _ expectedNumber: Number, + _ expectedString: String, + radix: Int, + file: StaticString = #filePath, + line: UInt = #line + ) { + let actualString = String(expectedNumber, radix: radix) + XCTAssertEqual(expectedString, actualString, file: file, line: line) + let actualNumber = Number(expectedString, radix: radix) + XCTAssertEqual(expectedNumber, actualNumber, file: file, line: line) + } + + func testConversions_ToAndFromString_Binary() { + testConversions_ToAndFromString( + _Int128.max, + """ + 111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111 + """, + radix: 2 + ) + testConversions_ToAndFromString( + _Int128.min, + """ + -1000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000 + """, + radix: 2 + ) + } + + func testConversions_ToAndFromString_Decimal() { + testConversions_ToAndFromString( + _Int256.max, + """ + 5789604461865809771178549250434395392663\ + 4992332820282019728792003956564819967 + """, + radix: 10 + ) + testConversions_ToAndFromString( + _Int256.min, + """ + -5789604461865809771178549250434395392663\ + 4992332820282019728792003956564819968 + """, + radix: 10 + ) + } + + func testConversions_ToAndFromString_Hexadecimal() { + testConversions_ToAndFromString( + _Int512.max, + """ + 7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + """, + radix: 16 + ) + testConversions_ToAndFromString( + _Int512.min, + """ + -8000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000 + """, + radix: 16 + ) + } + + func testWords() { + XCTAssertTrue(_Int16(0).words.elementsEqual([0])) + XCTAssertTrue(_Int16(1).words.elementsEqual([1])) + XCTAssertTrue(_Int16(-1).words.elementsEqual([UInt.max])) + XCTAssertTrue(_Int16(256).words.elementsEqual([256])) + XCTAssertTrue(_Int16(-256).words.elementsEqual([UInt.max - 255])) + XCTAssertTrue(_Int16.max.words.elementsEqual([32767])) + XCTAssertTrue(_Int16.min.words.elementsEqual([UInt.max - 32767])) + + XCTAssertTrue( + (0 as _Int1024).words.elementsEqual( + repeatElement(0 as UInt, count: 1024 / UInt.bitWidth) + ) + ) + XCTAssertTrue( + (-1 as _Int1024).words.elementsEqual( + repeatElement(UInt.max, count: 1024 / UInt.bitWidth) + ) + ) + XCTAssertTrue( + (1 as _Int1024).words.elementsEqual( + [1] + Array(repeating: 0, count: 1024 / UInt.bitWidth - 1) + ) + ) + } + + func testConditionalConformance() { + func checkSignedIntegerConformance(_: T) {} + func checkUnsignedIntegerConformance(_: T) {} + + checkSignedIntegerConformance(0 as _Int128) + checkSignedIntegerConformance(0 as _Int1024) + + checkUnsignedIntegerConformance(0 as _UInt128) + checkUnsignedIntegerConformance(0 as _UInt1024) + } +} diff --git a/Tests/WindowsMain.swift b/Tests/WindowsMain.swift index 3d378cac..520722af 100644 --- a/Tests/WindowsMain.swift +++ b/Tests/WindowsMain.swift @@ -2,7 +2,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-2021 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 @@ -114,6 +114,39 @@ extension IntegerUtilitiesShiftTests { ]) } +extension IntegerUtilitiesTests.DoubleWidthTests { + static var all = testCase([ + ("testArithmetic_Signed", IntegerUtilitiesTests.DoubleWidthTests.testArithmetic_Signed), + ("testArithmetic_Unsigned", IntegerUtilitiesTests.DoubleWidthTests.testArithmetic_Unsigned), + ("testBitwise_LeftAndRightShifts", IntegerUtilitiesTests.DoubleWidthTests.testBitwise_LeftAndRightShifts), + ("testCompileTime_SR_6947", IntegerUtilitiesTests.DoubleWidthTests.testCompileTime_SR_6947), + ("testConditionalConformance", IntegerUtilitiesTests.DoubleWidthTests.testConditionalConformance), + ("testConversions", IntegerUtilitiesTests.DoubleWidthTests.testConversions), + ("testConversions_Exact", IntegerUtilitiesTests.DoubleWidthTests.testConversions_Exact), + ("testConversions_SignedMax", IntegerUtilitiesTests.DoubleWidthTests.testConversions_SignedMax), + ("testConversions_SignedMin", IntegerUtilitiesTests.DoubleWidthTests.testConversions_SignedMin), + ("testConversions_ToAndFromString_Binary", IntegerUtilitiesTests.DoubleWidthTests.testConversions_ToAndFromString_Binary), + ("testConversions_ToAndFromString_Decimal", IntegerUtilitiesTests.DoubleWidthTests.testConversions_ToAndFromString_Decimal), + ("testConversions_ToAndFromString_Hexadecimal", IntegerUtilitiesTests.DoubleWidthTests.testConversions_ToAndFromString_Hexadecimal), + ("testConversions_UnsignedMax", IntegerUtilitiesTests.DoubleWidthTests.testConversions_UnsignedMax), + ("testConversions_UnsignedMin", IntegerUtilitiesTests.DoubleWidthTests.testConversions_UnsignedMin), + ("testDivision_ByMinusOne", IntegerUtilitiesTests.DoubleWidthTests.testDivision_ByMinusOne), + ("testDivision_ByZero", IntegerUtilitiesTests.DoubleWidthTests.testDivision_ByZero), + ("testInitialization", IntegerUtilitiesTests.DoubleWidthTests.testInitialization), + ("testInitialization_Overflow", IntegerUtilitiesTests.DoubleWidthTests.testInitialization_Overflow), + ("testLiterals", IntegerUtilitiesTests.DoubleWidthTests.testLiterals), + ("testLiterals_Underflow", IntegerUtilitiesTests.DoubleWidthTests.testLiterals_Underflow), + ("testMagnitude", IntegerUtilitiesTests.DoubleWidthTests.testMagnitude), + ("testMultipleOf", IntegerUtilitiesTests.DoubleWidthTests.testMultipleOf), + ("testMultiplication_ByMinusOne", IntegerUtilitiesTests.DoubleWidthTests.testMultiplication_ByMinusOne), + ("testNested", IntegerUtilitiesTests.DoubleWidthTests.testNested), + ("testRemainder_ByMinusOne", IntegerUtilitiesTests.DoubleWidthTests.testRemainder_ByMinusOne), + ("testRemainder_ByZero", IntegerUtilitiesTests.DoubleWidthTests.testRemainder_ByZero), + ("testTwoWords", IntegerUtilitiesTests.DoubleWidthTests.testTwoWords), + ("testWords", IntegerUtilitiesTests.DoubleWidthTests.testWords), + ]) +} + var testCases = [ ComplexTests.ApproximateEqualityTests.all, RealTests.ApproximateEqualityTests.all, @@ -125,6 +158,7 @@ var testCases = [ IntegerUtilitiesGCDTests.all, IntegerUtilitiesRotateTests.all, IntegerUtilitiesShiftTests.all, + IntegerUtilitiesTests.DoubleWidthTests.all, ] #if swift(>=5.3) && canImport(_Differentiation)