diff --git a/Sources/Complex/ComplexArithmetic.swift b/Sources/Complex/ComplexArithmetic.swift index 86de047..fe4766f 100644 --- a/Sources/Complex/ComplexArithmetic.swift +++ b/Sources/Complex/ComplexArithmetic.swift @@ -103,6 +103,108 @@ extension Complex where Scalar: SignedNumeric { } } +extension Complex where Scalar: FixedWidthInteger { + + @_transparent + public static func &+ (lhs: Complex, rhs: Complex) -> Complex { + return Complex(real: lhs.real &+ rhs.real, imaginary: lhs.imaginary &+ rhs.imaginary) + } + + @_transparent + public static func &+= (lhs: inout Complex, rhs: Complex) { + lhs = lhs &+ rhs + } + + @_transparent + public static func &- (lhs: Complex, rhs: Complex) -> Complex { + return Complex(real: lhs.real &- rhs.real, imaginary: lhs.imaginary &- rhs.imaginary) + } + + @_transparent + public static func &-= (lhs: inout Complex, rhs: Complex) { + lhs = lhs &- rhs + } + + // + + @_transparent + public static func &+ (lhs: Complex, rhs: Scalar) -> Complex { + return Complex(real: lhs.real &+ rhs, imaginary: lhs.imaginary) + } + + @_transparent + public static func &+ (lhs: Scalar, rhs: Complex) -> Complex { + return Complex(real: lhs &+ rhs.real, imaginary: rhs.imaginary) + } + + @_transparent + public static func &+= (lhs: inout Complex, rhs: Scalar) { + lhs = lhs &+ rhs + } + + // + + @inlinable @inline(__always) + public static func .&+ (lhs: Complex, rhs: Complex) -> Complex { + return lhs &+ rhs + } + + @inlinable @inline(__always) + public static func .&+= (lhs: inout Complex, rhs: Complex) { + lhs = lhs .&+ rhs + } + + // + + @_transparent + public static func &- (lhs: Complex, rhs: Scalar) -> Complex { + return Complex(real: lhs.real &- rhs, imaginary: lhs.imaginary) + } + + @_transparent + public static func &- (lhs: Scalar, rhs: Complex) -> Complex { + return Complex(real: lhs &- rhs.real, imaginary: .zero &- rhs.imaginary) + } + + @_transparent + public static func &-= (lhs: inout Complex, rhs: Scalar) { + lhs = lhs &- rhs + } + + // + + @inlinable @inline(__always) + public static func .&- (lhs: Complex, rhs: Complex) -> Complex { + return lhs &- rhs + } + + @inlinable @inline(__always) + public static func .&-= (lhs: inout Complex, rhs: Complex) { + lhs = lhs .&- rhs + } +} + +extension Complex where Scalar: FixedWidthInteger { + + @_transparent + public func addingReportingOverflow(_ rhs: Complex) -> (partialValue: Complex, overflow: Bool) { + let real = self.real.addingReportingOverflow(rhs.real) + let imaginary = self.imaginary.addingReportingOverflow(rhs.imaginary) + let overflow = real.overflow || imaginary.overflow + + return (partialValue: Complex(real: real.partialValue, imaginary: imaginary.partialValue), overflow: overflow) + } + + @_transparent + public func subtractingReportingOverflow(_ rhs: Complex) -> (partialValue: Complex, overflow: Bool) { + let real = self.real.subtractingReportingOverflow(rhs.real) + let imaginary = self.imaginary.subtractingReportingOverflow(rhs.imaginary) + let overflow = real.overflow || imaginary.overflow + + return (partialValue: Complex(real: real.partialValue, imaginary: imaginary.partialValue), overflow: overflow) + } +} + // MARK: - Multiplication extension Complex { @@ -150,6 +252,70 @@ extension Complex { } } +extension Complex where Scalar: FixedWidthInteger { + + @_transparent + public static func &* (lhs: Complex, rhs: Complex) -> Complex { + let real = (lhs.real &* rhs.real) &- (lhs.imaginary &* rhs.imaginary) + let imaginary = (lhs.real &* rhs.imaginary) &+ (lhs.imaginary &* rhs.real) + + return Complex(real: real, imaginary: imaginary) + } + + @_transparent + public static func &*= (lhs: inout Complex, rhs: Complex) { + lhs = lhs &* rhs + } + + // + + @_transparent + public static func &* (lhs: Complex, rhs: Scalar) -> Complex { + return Complex(real: lhs.real &* rhs, imaginary: lhs.imaginary &* rhs) + } + + @_transparent + public static func &* (lhs: Scalar, rhs: Complex) -> Complex { + return Complex(real: lhs &* rhs.real, imaginary: lhs &* rhs.imaginary) + } + + @_transparent + public static func &*= (lhs: inout Complex, rhs: Scalar) { + lhs = lhs &* rhs + } + + // + + @_transparent + public static func .&* (lhs: Complex, rhs: Complex) -> Complex { + return Complex(real: lhs.real &* rhs.real, imaginary: lhs.imaginary &* rhs.imaginary) + } + + @_transparent + public static func .&*= (lhs: inout Complex, rhs: Complex) { + lhs = lhs .&* rhs + } +} + +extension Complex where Scalar: FixedWidthInteger { + + @_transparent + public func componentwiseMultipliedFullWidth(by rhs: Complex) -> (high: Complex, low: Complex) { + let real = self.real.multipliedFullWidth(by: rhs.real) + let imaginary = self.imaginary.multipliedFullWidth(by: rhs.imaginary) + + return (high: Complex(real: real.high, imaginary: imaginary.high), low: Complex(real: real.low, imaginary: imaginary.low)) + } + + @_transparent + public func componentwiseMultipliedReportingOverflow(by rhs: Complex) -> (partialValue: Complex, overflow: Bool) { + let real = self.real.multipliedReportingOverflow(by: rhs.real) + let imaginary = self.imaginary.multipliedReportingOverflow(by: rhs.imaginary) + + return (partialValue: Complex(real: real.partialValue, imaginary: imaginary.partialValue), overflow: real.overflow || imaginary.overflow) + } +} + // MARK: - Division (BinaryInteger) extension Complex where Scalar: BinaryInteger { @@ -249,3 +415,22 @@ extension Complex where Scalar: FloatingPoint { lhs = lhs ./ rhs } } + +extension Complex where Scalar: FixedWidthInteger { + + @_transparent + public func componentwiseDividedReportingOverflow(by rhs: Complex) -> (partialValue: Complex, overflow: Bool) { + let real = self.real.dividedReportingOverflow(by: rhs.real) + let imaginary = self.imaginary.dividedReportingOverflow(by: rhs.imaginary) + + return (partialValue: Complex(real: real.partialValue, imaginary: imaginary.partialValue), overflow: real.overflow || imaginary.overflow) + } + + @_transparent + public func componentwiseDividingFullWidth(_ dividend: (high: Complex, low: Complex)) -> (quotient: Complex, remainder: Complex) { + let real = self.real.dividingFullWidth((high: dividend.high.real, low: dividend.low.real)) + let imaginary = self.imaginary.dividingFullWidth((high: dividend.high.imaginary, low: dividend.low.imaginary)) + + return (quotient: Complex(real: real.quotient, imaginary: imaginary.quotient), remainder: Complex(real: real.remainder, imaginary: imaginary.remainder)) + } +} diff --git a/Sources/Complex/Operators.swift b/Sources/Complex/Operators.swift index c5c567e..8cb684f 100644 --- a/Sources/Complex/Operators.swift +++ b/Sources/Complex/Operators.swift @@ -11,14 +11,26 @@ import Foundation infix operator .+: AdditionPrecedence infix operator .+=: AssignmentPrecedence +/// Component-wise addition, ignoring overflow +infix operator .&+: AdditionPrecedence +infix operator .&+=: AssignmentPrecedence + /// Component-wise subtraction infix operator .-: AdditionPrecedence infix operator .-=: AssignmentPrecedence +/// Component-wise subtraction, ignoring overflow +infix operator .&-: AdditionPrecedence +infix operator .&-=: AssignmentPrecedence + /// Component-wise multiplication infix operator .*: MultiplicationPrecedence infix operator .*=: AssignmentPrecedence +/// Component-wise multiplication, ignoring overflow +infix operator .&*: MultiplicationPrecedence +infix operator .&*=: AssignmentPrecedence + /// Component-wise division infix operator ./: MultiplicationPrecedence infix operator ./=: AssignmentPrecedence diff --git a/Tests/ComplexTests/ComplexArithmeticTests.swift b/Tests/ComplexTests/ComplexArithmeticTests.swift index 087ce2a..4f5c704 100644 --- a/Tests/ComplexTests/ComplexArithmeticTests.swift +++ b/Tests/ComplexTests/ComplexArithmeticTests.swift @@ -96,6 +96,32 @@ class ComplexArithmeticTests: XCTestCase { testAddition(Complex(real: 1.0, imaginary: 2.0), 3.0, Complex(real: 4.0, imaginary: 2.0)) } + func testAdditionIgnoringOverflow() { + testAdditionIgnoringOverflow(forType: Int8.self) + testAdditionIgnoringOverflow(forType: Int16.self) + testAdditionIgnoringOverflow(forType: Int32.self) + testAdditionIgnoringOverflow(forType: Int64.self) + testAdditionIgnoringOverflow(forType: Int.self) + testAdditionIgnoringOverflow(forType: UInt8.self) + testAdditionIgnoringOverflow(forType: UInt16.self) + testAdditionIgnoringOverflow(forType: UInt32.self) + testAdditionIgnoringOverflow(forType: UInt64.self) + testAdditionIgnoringOverflow(forType: UInt.self) + } + + func testOverflowingAddition() { + testOverflowingAddition(forType: Int8.self) + testOverflowingAddition(forType: Int16.self) + testOverflowingAddition(forType: Int32.self) + testOverflowingAddition(forType: Int64.self) + testOverflowingAddition(forType: Int.self) + testOverflowingAddition(forType: UInt8.self) + testOverflowingAddition(forType: UInt16.self) + testOverflowingAddition(forType: UInt32.self) + testOverflowingAddition(forType: UInt64.self) + testOverflowingAddition(forType: UInt.self) + } + func testSubtraction() { testSubtraction(Complex(real: 3, imaginary: 4), Complex(real: 1, imaginary: 2), Complex(real: 2, imaginary: 2)) testSubtraction(Complex(real: 3, imaginary: 4), Complex(real: 1, imaginary: 2), Complex(real: 2, imaginary: 2)) @@ -128,6 +154,32 @@ class ComplexArithmeticTests: XCTestCase { testSubtraction(Complex(real: 3.0, imaginary: 4.0), 1.0, Complex(real: 2.0, imaginary: 4.0)) } + func testSubtractionIgnoringOverflow() { + testSubtractionIgnoringOverflow(forType: Int8.self) + testSubtractionIgnoringOverflow(forType: Int16.self) + testSubtractionIgnoringOverflow(forType: Int32.self) + testSubtractionIgnoringOverflow(forType: Int64.self) + testSubtractionIgnoringOverflow(forType: Int.self) + testSubtractionIgnoringOverflow(forType: UInt8.self) + testSubtractionIgnoringOverflow(forType: UInt16.self) + testSubtractionIgnoringOverflow(forType: UInt32.self) + testSubtractionIgnoringOverflow(forType: UInt64.self) + testSubtractionIgnoringOverflow(forType: UInt.self) + } + + func testOverflowingSubtraction() { + testOverflowingSubtraction(forType: Int8.self) + testOverflowingSubtraction(forType: Int16.self) + testOverflowingSubtraction(forType: Int32.self) + testOverflowingSubtraction(forType: Int64.self) + testOverflowingSubtraction(forType: Int.self) + testOverflowingSubtraction(forType: UInt8.self) + testOverflowingSubtraction(forType: UInt16.self) + testOverflowingSubtraction(forType: UInt32.self) + testOverflowingSubtraction(forType: UInt64.self) + testOverflowingSubtraction(forType: UInt.self) + } + func testMultiplication() { testMultiplication(Complex(real: 3, imaginary: 4), Complex(real: 1, imaginary: 2), Complex(real: -5, imaginary: 10)) testMultiplication(Complex(real: 3, imaginary: 4), Complex(real: 1, imaginary: 2), Complex(real: -5, imaginary: 10)) @@ -160,6 +212,19 @@ class ComplexArithmeticTests: XCTestCase { testMultiplication(Complex(real: 3.0, imaginary: 4.0), 2.0) } + func testMultiplicationIgnoringOverflow() { + testMultiplicationIgnoringOverflow(forType: Int8.self) + testMultiplicationIgnoringOverflow(forType: Int16.self) + testMultiplicationIgnoringOverflow(forType: Int32.self) + testMultiplicationIgnoringOverflow(forType: Int64.self) + testMultiplicationIgnoringOverflow(forType: Int.self) + testMultiplicationIgnoringOverflow(forType: UInt8.self) + testMultiplicationIgnoringOverflow(forType: UInt16.self) + testMultiplicationIgnoringOverflow(forType: UInt32.self) + testMultiplicationIgnoringOverflow(forType: UInt64.self) + testMultiplicationIgnoringOverflow(forType: UInt.self) + } + func testComponentwiseMultiplication() { testComponentwiseMultiplication(Complex(real: 3, imaginary: 4), Complex(real: 1, imaginary: 2)) testComponentwiseMultiplication(Complex(real: 3, imaginary: 4), Complex(real: 1, imaginary: 2)) @@ -177,6 +242,19 @@ class ComplexArithmeticTests: XCTestCase { testComponentwiseMultiplication(Complex(real: 3.0, imaginary: 4.0), Complex(real: 1.0, imaginary: 2.0)) } + func testComponentwiseOverflowingMultiplication() { + testComponentwiseOverflowingMultiplication(forType: Int8.self) + testComponentwiseOverflowingMultiplication(forType: Int16.self) + testComponentwiseOverflowingMultiplication(forType: Int32.self) + testComponentwiseOverflowingMultiplication(forType: Int64.self) + testComponentwiseOverflowingMultiplication(forType: Int.self) + testComponentwiseOverflowingMultiplication(forType: UInt8.self) + testComponentwiseOverflowingMultiplication(forType: UInt16.self) + testComponentwiseOverflowingMultiplication(forType: UInt32.self) + testComponentwiseOverflowingMultiplication(forType: UInt64.self) + testComponentwiseOverflowingMultiplication(forType: UInt.self) + } + func testDivision() { testDivision(Complex(real: 3, imaginary: 4), Complex(real: 2, imaginary: 1), Complex(real: 2, imaginary: 1)) testDivision(Complex(real: 3, imaginary: 4), Complex(real: 2, imaginary: 1), Complex(real: 2, imaginary: 1)) @@ -226,6 +304,19 @@ class ComplexArithmeticTests: XCTestCase { testComponentwiseDivision(Complex(real: 3.0, imaginary: 4.0), Complex(real: 1.0, imaginary: 2.0)) } + func testComponentwiseOverflowingDivision() { + testComponentwiseOverflowingDivision(forType: Int8.self) + testComponentwiseOverflowingDivision(forType: Int16.self) + testComponentwiseOverflowingDivision(forType: Int32.self) + testComponentwiseOverflowingDivision(forType: Int64.self) + testComponentwiseOverflowingDivision(forType: Int.self) + testComponentwiseOverflowingDivision(forType: UInt8.self) + testComponentwiseOverflowingDivision(forType: UInt16.self) + testComponentwiseOverflowingDivision(forType: UInt32.self) + testComponentwiseOverflowingDivision(forType: UInt64.self) + testComponentwiseOverflowingDivision(forType: UInt.self) + } + // MARK: Private Methods private func testAdditionWithZero(_ complex: Complex, file: StaticString = #file, line: UInt = #line) { @@ -316,8 +407,114 @@ class ComplexArithmeticTests: XCTestCase { XCTAssertEqual(complex, result, file: file, line: line) } + private func testAdditionIgnoringOverflow(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { + // The test is defined to "succeed" if it doesn't crash + let lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + let rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + let rhs2 = Scalar.max + + _ = lhs &+ rhs + _ = rhs &+ lhs + _ = lhs .&+ rhs + _ = rhs .&+ lhs + _ = lhs &+ rhs2 + _ = rhs2 &+ lhs + + var complex = lhs + complex &+= rhs + + complex = rhs + complex &+= lhs + + complex = lhs + complex .&+= rhs + + complex = rhs + complex .&+= lhs + + if Scalar.isSigned { + // If the scalar is signed each min is the smallest negative number. Adding these + // two together would overflow. + let lhs = Complex(real: Scalar.min, imaginary: Scalar.min) + let rhs = Complex(real: Scalar.min, imaginary: Scalar.min) + + _ = lhs &+ rhs + _ = rhs &+ lhs + _ = lhs .&+ rhs + _ = rhs .&+ lhs + + var complex = lhs + complex &+= rhs + + complex = lhs + complex &+= rhs2 + + complex = rhs + complex &+= lhs + + complex = lhs + complex .&+= rhs + + complex = rhs + complex .&+= lhs + } + } + + private func testOverflowingAddition(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger, Scalar: UnsignedInteger { + var lhs = Complex(real: Scalar.max, imaginary: 0) + var rhs = Complex(real: Scalar.max, imaginary: 0) + var result = lhs.addingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, Scalar.max - 1, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, 0, file: file, line: line) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, 0, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, Scalar.max - 1, file: file, line: line) + + lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, Scalar.max - 1, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, Scalar.max - 1, file: file, line: line) + } + + private func testOverflowingAddition(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger, Scalar: SignedInteger { + var lhs = Complex(real: Scalar.max, imaginary: 0) + var rhs = Complex(real: Scalar.max, imaginary: 0) + var result = lhs.addingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, -2, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, 0, file: file, line: line) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, 0, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, -2, file: file, line: line) + + lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, -2, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, -2, file: file, line: line) + } + private func testSubtraction(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(lhs - rhs, result, file: file, line: line) + XCTAssertEqual(lhs .- rhs, result, file: file, line: line) var complex = lhs complex -= rhs @@ -368,6 +565,73 @@ class ComplexArithmeticTests: XCTestCase { XCTAssertEqual(complex, result, file: file, line: line) } + private func testSubtractionIgnoringOverflow(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { + // The test is defined to "succeed" if it doesn't crash + let lhs = Complex(real: Scalar.min, imaginary: Scalar.min) + let rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + let rhs2 = Scalar.max + let rhs3 = Scalar.min + + _ = lhs &- rhs + _ = lhs .&- rhs + _ = lhs &- rhs2 + _ = rhs3 &- rhs + + var complex = lhs + complex &-= rhs + + complex = lhs + complex &-= rhs2 + + complex = lhs + complex .&-= rhs + + if Scalar.isSigned { + // If the scalar is signed each min is the smallest negative number. Subtracting + // these from the max would overflow. + let lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + let rhs = Complex(real: Scalar.min, imaginary: Scalar.min) + + _ = lhs &- rhs + _ = lhs .&- rhs + + var complex = lhs + complex &-= rhs + + complex = lhs + complex &-= rhs3 + + complex = lhs + complex .&-= rhs + } + } + + private func testOverflowingSubtraction(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { + var lhs = Complex(real: Scalar.min, imaginary: 0) + var rhs = Complex(real: Scalar.max, imaginary: 0) + var result = lhs.subtractingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, 1, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, 0, file: file, line: line) + + lhs = Complex(real: 0, imaginary: Scalar.min) + rhs = Complex(real: 0, imaginary: Scalar.max) + result = lhs.subtractingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, 0, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, 1, file: file, line: line) + + lhs = Complex(real: Scalar.min, imaginary: Scalar.min) + rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + result = lhs.subtractingReportingOverflow(rhs) + + XCTAssertTrue(result.overflow, file: file, line: line) + XCTAssertEqual(result.partialValue.real, 1, file: file, line: line) + XCTAssertEqual(result.partialValue.imaginary, 1, file: file, line: line) + } + private func testMultiplication(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(lhs * rhs, result, file: file, line: line) XCTAssertEqual(rhs * lhs, result, file: file, line: line) @@ -381,6 +645,35 @@ class ComplexArithmeticTests: XCTestCase { XCTAssertEqual(complex, result, file: file, line: line) } + private func testMultiplicationIgnoringOverflow(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { + // The test is defined to "succeed" if it doesn't crash + let lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + let rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + let rhs2 = Scalar.max + + _ = lhs &* rhs + _ = rhs &* lhs + _ = lhs .&* rhs + _ = rhs .&* lhs + _ = lhs &* rhs2 + _ = rhs2 &* lhs + + var complex = lhs + complex &*= rhs + + complex = rhs + complex &*= lhs + + complex = lhs + complex &*= rhs2 + + complex = lhs + complex .&*= rhs + + complex = rhs + complex .&*= lhs + } + private func testMultiplication(_ lhs: Complex, _ rhs: Scalar, file: StaticString = #file, line: UInt = #line) { let result = Complex(real: lhs.real * rhs, imaginary: lhs.imaginary * rhs) XCTAssertEqual(lhs * rhs, result, file: file, line: line) @@ -402,6 +695,70 @@ class ComplexArithmeticTests: XCTestCase { XCTAssertEqual(result.imaginary, lhs.imaginary * rhs.imaginary, file: file, line: line) } + private func testComponentwiseOverflowingMultiplication(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger, Scalar: UnsignedInteger { + var lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + var rhs = Complex(real: 2, imaginary: 2) + + let fullWidth = lhs.componentwiseMultipliedFullWidth(by: rhs) + XCTAssertEqual(fullWidth.high.real, 1, file: file, line: line) + XCTAssertEqual(fullWidth.high.imaginary, 1, file: file, line: line) + XCTAssertEqual(fullWidth.low.real, Scalar.Magnitude.max - 1, file: file, line: line) + XCTAssertEqual(fullWidth.low.imaginary, Scalar.Magnitude.max - 1, file: file, line: line) + + var overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, Scalar.max - 1, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, Scalar.max - 1, file: file, line: line) + + lhs = Complex(real: Scalar.max, imaginary: 0) + rhs = Complex(real: 2, imaginary: 0) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, Scalar.max - 1, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, 0, file: file, line: line) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: 2) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, 0, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, Scalar.max - 1, file: file, line: line) + } + + private func testComponentwiseOverflowingMultiplication(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger, Scalar: SignedInteger { + var lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + var rhs = Complex(real: 4, imaginary: 4) + + let fullWidth = lhs.componentwiseMultipliedFullWidth(by: rhs) + XCTAssertEqual(fullWidth.high.real, 1, file: file, line: line) + XCTAssertEqual(fullWidth.high.imaginary, 1, file: file, line: line) + XCTAssertEqual(fullWidth.low.real, Scalar.Magnitude.max - 3, file: file, line: line) + XCTAssertEqual(fullWidth.low.imaginary, Scalar.Magnitude.max - 3, file: file, line: line) + + var overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, -4, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, -4, file: file, line: line) + + lhs = Complex(real: Scalar.max, imaginary: 0) + rhs = Complex(real: 4, imaginary: 0) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, -4, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, 0, file: file, line: line) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: 4) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, 0, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, -4, file: file, line: line) + } + private func testDivision(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) where Scalar: BinaryInteger { XCTAssertEqual(lhs / rhs, result, file: file, line: line) @@ -465,4 +822,31 @@ class ComplexArithmeticTests: XCTestCase { XCTAssertEqual(result.real, lhs.real / rhs.real, file: file, line: line) XCTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary, file: file, line: line) } + + private func testComponentwiseOverflowingDivision(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { + let lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + var rhs = Complex(real: 0, imaginary: 0) + + let fullWidth = lhs.componentwiseDividingFullWidth((high: Complex(real: 1, imaginary: 1), low: Complex.zero)) + XCTAssertEqual(fullWidth.quotient.real, Scalar.isSigned ? 2 : 1, file: file, line: line) + XCTAssertEqual(fullWidth.quotient.imaginary, Scalar.isSigned ? 2 : 1, file: file, line: line) + XCTAssertEqual(fullWidth.remainder.real, Scalar.isSigned ? 2 : 1, file: file, line: line) + XCTAssertEqual(fullWidth.remainder.imaginary, Scalar.isSigned ? 2 : 1, file: file, line: line) + + var overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue, lhs, file: file, line: line) + + rhs = Complex(real: 0, imaginary: Scalar.max) + overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, lhs.real, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, 1, file: file, line: line) + + rhs = Complex(real: Scalar.max, imaginary: 0) + overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) + XCTAssertTrue(overflow.overflow, file: file, line: line) + XCTAssertEqual(overflow.partialValue.real, 1, file: file, line: line) + XCTAssertEqual(overflow.partialValue.imaginary, lhs.imaginary, file: file, line: line) + } }