diff --git a/Complex.xcodeproj/project.pbxproj b/Complex.xcodeproj/project.pbxproj index 96ebc89..6f1050a 100644 --- a/Complex.xcodeproj/project.pbxproj +++ b/Complex.xcodeproj/project.pbxproj @@ -32,6 +32,13 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + DD6F08BE24001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08BD24001B9A00749359 /* ComplexOverflowingArithmetic.swift */; }; + DD6F08BF24001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08BD24001B9A00749359 /* ComplexOverflowingArithmetic.swift */; }; + DD6F08C024001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08BD24001B9A00749359 /* ComplexOverflowingArithmetic.swift */; }; + DD6F08C124001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08BD24001B9A00749359 /* ComplexOverflowingArithmetic.swift */; }; + DD6F08C324001CD400749359 /* ComplexOverflowingArithmeticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08C224001CD400749359 /* ComplexOverflowingArithmeticTests.swift */; }; + DD6F08C424001CD400749359 /* ComplexOverflowingArithmeticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08C224001CD400749359 /* ComplexOverflowingArithmeticTests.swift */; }; + DD6F08C524001CD400749359 /* ComplexOverflowingArithmeticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08C224001CD400749359 /* ComplexOverflowingArithmeticTests.swift */; }; DD6F08CD240077C300749359 /* TestingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08CC240077C300749359 /* TestingBase.swift */; }; DD6F08CE240077C300749359 /* TestingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08CC240077C300749359 /* TestingBase.swift */; }; DD6F08CF240077C300749359 /* TestingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F08CC240077C300749359 /* TestingBase.swift */; }; @@ -149,6 +156,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + DD6F08BD24001B9A00749359 /* ComplexOverflowingArithmetic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplexOverflowingArithmetic.swift; sourceTree = ""; }; + DD6F08C224001CD400749359 /* ComplexOverflowingArithmeticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplexOverflowingArithmeticTests.swift; sourceTree = ""; }; DD6F08CC240077C300749359 /* TestingBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingBase.swift; sourceTree = ""; }; DD6F08D02400808300749359 /* codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = codecov.yml; sourceTree = ""; }; DDB8120623F59B760079FEB5 /* Complex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Complex.swift; sourceTree = ""; }; @@ -310,6 +319,7 @@ DD6F08CC240077C300749359 /* TestingBase.swift */, DDB8121023F5AEB10079FEB5 /* ComplexTests.swift */, DDB8126623F7B7F40079FEB5 /* ComplexArithmeticTests.swift */, + DD6F08C224001CD400749359 /* ComplexOverflowingArithmeticTests.swift */, DDB8122023F656AC0079FEB5 /* FunctionsTests.swift */, DDB8128523FB1A430079FEB5 /* FunctionsTests.swift.gyb */, DDFEEC4323EF13910096015C /* ComplexTests-Info.plist */, @@ -347,8 +357,9 @@ children = ( DDB8120623F59B760079FEB5 /* Complex.swift */, DDB8126123F7B1C90079FEB5 /* ComplexArithmetic.swift */, - DDB8126F23FA2C210079FEB5 /* Functions.swift.gyb */, + DD6F08BD24001B9A00749359 /* ComplexOverflowingArithmetic.swift */, DDB8120B23F5A8E80079FEB5 /* Functions.swift */, + DDB8126F23FA2C210079FEB5 /* Functions.swift.gyb */, DDB8125823F707EE0079FEB5 /* Operators.swift */, ); path = Complex; @@ -698,6 +709,7 @@ DDB8125923F707EE0079FEB5 /* Operators.swift in Sources */, DDB8120723F59B760079FEB5 /* Complex.swift in Sources */, DDB8120C23F5A8E80079FEB5 /* Functions.swift in Sources */, + DD6F08BE24001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -707,6 +719,7 @@ files = ( DDB8126723F7B7F40079FEB5 /* ComplexArithmeticTests.swift in Sources */, DD6F08CD240077C300749359 /* TestingBase.swift in Sources */, + DD6F08C324001CD400749359 /* ComplexOverflowingArithmeticTests.swift in Sources */, DDB8121123F5AEB10079FEB5 /* ComplexTests.swift in Sources */, DDB8122123F656AC0079FEB5 /* FunctionsTests.swift in Sources */, ); @@ -720,6 +733,7 @@ DDB8125A23F707EE0079FEB5 /* Operators.swift in Sources */, DDB8120823F59B760079FEB5 /* Complex.swift in Sources */, DDB8120D23F5A8E80079FEB5 /* Functions.swift in Sources */, + DD6F08BF24001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,6 +743,7 @@ files = ( DDB8126823F7B7F40079FEB5 /* ComplexArithmeticTests.swift in Sources */, DD6F08CE240077C300749359 /* TestingBase.swift in Sources */, + DD6F08C424001CD400749359 /* ComplexOverflowingArithmeticTests.swift in Sources */, DDB8121223F5AEB10079FEB5 /* ComplexTests.swift in Sources */, DDB8122223F656AC0079FEB5 /* FunctionsTests.swift in Sources */, ); @@ -742,6 +757,7 @@ DDB8125B23F707EE0079FEB5 /* Operators.swift in Sources */, DDB8120923F59B760079FEB5 /* Complex.swift in Sources */, DDB8120E23F5A8E80079FEB5 /* Functions.swift in Sources */, + DD6F08C024001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -751,6 +767,7 @@ files = ( DDB8126923F7B7F40079FEB5 /* ComplexArithmeticTests.swift in Sources */, DD6F08CF240077C300749359 /* TestingBase.swift in Sources */, + DD6F08C524001CD400749359 /* ComplexOverflowingArithmeticTests.swift in Sources */, DDB8121323F5AEB10079FEB5 /* ComplexTests.swift in Sources */, DDB8122323F656AC0079FEB5 /* FunctionsTests.swift in Sources */, ); @@ -764,6 +781,7 @@ DDB8125C23F707EE0079FEB5 /* Operators.swift in Sources */, DDB8120A23F59B760079FEB5 /* Complex.swift in Sources */, DDB8120F23F5A8E80079FEB5 /* Functions.swift in Sources */, + DD6F08C124001B9A00749359 /* ComplexOverflowingArithmetic.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/README.md b/README.md index 0dd8f3e..e408139 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Complex [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](https://img.shields.io/cocoapods/p/Complex.svg)](https://cocoapods.org/pods/Complex) [![Build](https://travis-ci.com/SomeRandomiOSDev/Complex.svg?branch=master)](https://travis-ci.com/SomeRandomiOSDev/Complex) +![Swift](https://github.com/SomeRandomiOSDev/Complex/workflows/Swift/badge.svg) [![Code Coverage](https://codecov.io/gh/SomeRandomiOSDev/Complex/branch/master/graph/badge.svg)](https://codecov.io/gh/SomeRandomiOSDev/Complex) [![Codacy](https://api.codacy.com/project/badge/Grade/8ad52c117e4a46d9aa4699d22fc0bf49)](https://app.codacy.com/app/SomeRandomiOSDev/Complex?utm_source=github.com&utm_medium=referral&utm_content=SomeRandomiOSDev/Complex&utm_campaign=Badge_Grade_Dashboard) diff --git a/Sources/Complex/ComplexArithmetic.swift b/Sources/Complex/ComplexArithmetic.swift index fe4766f..9bf6c79 100644 --- a/Sources/Complex/ComplexArithmetic.swift +++ b/Sources/Complex/ComplexArithmetic.swift @@ -184,27 +184,6 @@ extension Complex where Scalar: FixedWidthInteger { } } -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 { @@ -297,25 +276,6 @@ extension Complex where Scalar: FixedWidthInteger { } } -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 { @@ -415,22 +375,3 @@ 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/ComplexOverflowingArithmetic.swift b/Sources/Complex/ComplexOverflowingArithmetic.swift new file mode 100644 index 0000000..da3de8b --- /dev/null +++ b/Sources/Complex/ComplexOverflowingArithmetic.swift @@ -0,0 +1,543 @@ +// +// ComplexOverflowingArithmetic.swift +// Complex +// +// Copyright © 2020 SomeRandomiOSDev. All rights reserved. +// + +import Foundation + +// MARK: Overflowing Addition + +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) + } +} + +// MARK: Overflowing Subtraction + +extension Complex where Scalar: FixedWidthInteger { + + @_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: - Overflowing Multiplication + +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) + } +} + +extension Complex where Scalar: FixedWidthInteger { + + //swiftlint:disable identifier_name + public func multipliedReportingOverflow(by rhs: Complex) -> (partialValue: Complex, overflow: Bool) { + // (a + bi)(c + di) = (ac − bd) + (ad + bc)i + let ac = self.real.multipliedFullWidth(by: rhs.real) + let ad = self.real.multipliedFullWidth(by: rhs.imaginary) + let bc = self.imaginary.multipliedFullWidth(by: rhs.real) + let bd = self.imaginary.multipliedFullWidth(by: rhs.imaginary) + + let real = Complex.subtract(ac, bd) + let imaginary = Complex.add(ad, bc) + + let truncatedReal = Complex.truncateAndReportOverflow(real) + let truncatedImaginary = Complex.truncateAndReportOverflow(imaginary) + + let overflow = truncatedReal.overflow || truncatedImaginary.overflow || (!Scalar.isSigned && Complex.isLess(Complex.signExtend(ac), than: Complex.signExtend(bd))) + let partialValue = Complex(real: truncatedReal.partialValue, imaginary: truncatedImaginary.partialValue) + + return (partialValue: partialValue, overflow: overflow) + } + //swiftlint:enable identifier_name +} + +extension Complex where Scalar: FixedWidthInteger, Scalar: SignedInteger { + + //swiftlint:disable identifier_name + public func multipliedFullWidth(by other: Complex) -> ExtendedComplex { + // (a + bi)(c + di) = (ac − bd) + (ad + bc)i + let ac = self.real.multipliedFullWidth(by: other.real) + let ad = self.real.multipliedFullWidth(by: other.imaginary) + let bc = self.imaginary.multipliedFullWidth(by: other.real) + let bd = self.imaginary.multipliedFullWidth(by: other.imaginary) + + let real = Complex.subtract(ac, bd) + let imaginary = Complex.add(ad, bc) + + let high = (Complex(real: real.high.high, imaginary: imaginary.high.high), Complex(real: real.high.low, imaginary: imaginary.high.low)) + let low = (Complex(real: real.low.high, imaginary: imaginary.low.high), Complex(real: real.low.low, imaginary: imaginary.low.low)) + + return (high: high, low: low) + } + //swiftlint:enable identifier_name +} + +// MARK: - Overflowing Division + +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)) + } +} + +extension Complex where Scalar: FixedWidthInteger { + + //swiftlint:disable identifier_name + public func dividedReportingOverflow(by rhs: Complex) -> (partialValue: Complex, overflow: Bool) { + // (a + bi) / (c + di) -> ((ac + bd) + (bc - ad)i) / (c^2 + d^2) + guard rhs.real != .zero || rhs.imaginary != .zero else { return (rhs, true) } + + let ac = self.real.multipliedFullWidth(by: rhs.real) + let ad = self.real.multipliedFullWidth(by: rhs.imaginary) + let bc = self.imaginary.multipliedFullWidth(by: rhs.real) + let bd = self.imaginary.multipliedFullWidth(by: rhs.imaginary) + let cc = rhs.real.multipliedFullWidth(by: rhs.real) + let dd = rhs.imaginary.multipliedFullWidth(by: rhs.imaginary) + + let real = Complex.add(ac, bd) + let imaginary = Complex.subtract(bc, ad) + let denominator = Complex.add(cc, dd) + + let (realQuotient, _) = Complex.slowpathDivide(real, denominator) + let (imaginaryQuotient, _) = Complex.slowpathDivide(imaginary, denominator) + + let truncatedReal = Complex.truncateAndReportOverflow(realQuotient) + let truncatedImaginary = Complex.truncateAndReportOverflow(imaginaryQuotient) + + let partialValue = Complex(real: truncatedReal.partialValue, imaginary: truncatedImaginary.partialValue) + let overflow = truncatedReal.overflow || truncatedImaginary.overflow || (!Scalar.isSigned && Complex.truncateAndReportOverflow(imaginary).overflow) + + return (partialValue: partialValue, overflow: overflow) + } + //swiftlint:enable identifier_name +} + +extension Complex where Scalar: FixedWidthInteger, Scalar: SignedInteger { + + //swiftlint:disable identifier_name + public func dividingFullWidth(_ dividend: ExtendedComplex) -> (quotient: Complex, remainder: Complex) { + // (a + bi) / (c + di) -> ((ac + bd) + (bc - ad)i) / (c^2 + d^2) + let a = (high: (high: dividend.high.high.real, low: dividend.high.low.real), low: (high: dividend.low.high.real, low: dividend.low.low.real)) + let b = (high: (high: dividend.high.high.imaginary, low: dividend.high.low.imaginary), low: (high: dividend.low.high.imaginary, low: dividend.low.low.imaginary)) + + let ac = Complex.slowpathMultiply(real, a) + let ad = Complex.slowpathMultiply(imaginary, a) + let bc = Complex.slowpathMultiply(real, b) + let bd = Complex.slowpathMultiply(imaginary, b) + let cc = Complex.signExtend(real.multipliedFullWidth(by: real)) + let dd = Complex.signExtend(imaginary.multipliedFullWidth(by: imaginary)) + + let real = Complex.add(ac, bd) + let imaginary = Complex.subtract(bc, ad) + let denominator = Complex.add(cc, dd) + + let (realQuotient, realRemainder) = Complex.slowpathDivide(real, denominator) + let (imaginaryQuotient, imaginaryRemainder) = Complex.slowpathDivide(imaginary, denominator) + + let truncatedRealQuotient = Complex.truncateAndReportOverflow(realQuotient).partialValue + let truncatedImaginaryQuotient = Complex.truncateAndReportOverflow(imaginaryQuotient).partialValue + let truncatedRealRemainder = Complex.truncateAndReportOverflow(realRemainder).partialValue + let truncatedImaginaryRemainder = Complex.truncateAndReportOverflow(imaginaryRemainder).partialValue + + return (quotient: Complex(real: truncatedRealQuotient, imaginary: truncatedImaginaryQuotient), remainder: Complex(real: truncatedRealRemainder, imaginary: truncatedImaginaryRemainder)) + } + //swiftlint:enable identifier_name +} + +// MARK: Overflowing Arthimetic Helper Types & Methods + +extension Complex where Scalar: FixedWidthInteger { + + public typealias ExtendedComplex = (high: (high: Complex, low: Complex), low: (high: Complex, low: Complex)) + + internal typealias ExtendedScalar = (high: (high: Scalar, low: Scalar.Magnitude), low: (high: Scalar.Magnitude, low: Scalar.Magnitude)) + + // + + internal static func signExtend(_ value: Scalar) -> ExtendedScalar { + let highHigh = (Scalar.isSigned && value.leadingZeroBitCount == 0) ? ~Scalar.zero : Scalar.zero + let highLow = Scalar.Magnitude(truncatingIfNeeded: highHigh) + let lowHigh = Scalar.Magnitude(truncatingIfNeeded: highLow) + let lowLow = Scalar.Magnitude(truncatingIfNeeded: value) + + return (high: (high: highHigh, low: highLow), low: (high: lowHigh, low: lowLow)) + } + + internal static func signExtend(_ value: (high: Scalar, low: Scalar.Magnitude)) -> ExtendedScalar { + let highHigh = (Scalar.isSigned && value.high.leadingZeroBitCount == 0) ? ~Scalar.zero : Scalar.zero + let highLow = Scalar.Magnitude(truncatingIfNeeded: highHigh) + let lowHigh = Scalar.Magnitude(truncatingIfNeeded: value.high) + let lowLow = value.low + + return (high: (high: highHigh, low: highLow), low: (high: lowHigh, low: lowLow)) + } + + internal static func twosComplement(of value: (high: Scalar, low: Scalar.Magnitude)) -> (high: Scalar, low: Scalar.Magnitude) { + let (low, overflow) = (~value.low).addingReportingOverflow(1) + let high = (~value.high) &+ (overflow ? 1 : 0) + + return (high: high, low: low) + } + + internal static func twosComplement(of value: ExtendedScalar) -> ExtendedScalar { + let (lowLow, lowLowOverflow) = (~value.low.low).addingReportingOverflow(1) + let (lowHigh, lowHighOverflow) = (~value.low.high).addingReportingOverflow(lowLowOverflow ? 1 : 0) + let (highLow, highLowOverflow) = (~value.high.low).addingReportingOverflow(lowHighOverflow ? 1 : 0) + let highHigh = (~value.high.high) &+ (highLowOverflow ? 1 : 0) + + return (high: (high: highHigh, low: highLow), low: (high: lowHigh, low: lowLow)) + } + + // + + internal static func add(_ lhs: (high: Scalar, low: Scalar.Magnitude), _ rhs: (high: Scalar, low: Scalar.Magnitude)) -> ExtendedScalar { + return add(signExtend(lhs), signExtend(rhs)) + } + + internal static func add(_ lhs: ExtendedScalar, _ rhs: ExtendedScalar) -> ExtendedScalar { + let (lowLow, lowLowOverflow) = lhs.low.low.addingReportingOverflow(rhs.low.low) + var (lowHigh, lowHighOverflow) = lhs.low.high.addingReportingOverflow(rhs.low.high) + var (highLow, highLowOverflow) = lhs.high.low.addingReportingOverflow(rhs.high.low) + var (highHigh, _) = lhs.high.high.addingReportingOverflow(rhs.high.high) + + if lowLowOverflow { + let (value, overflow) = lowHigh.addingReportingOverflow(1) + + lowHigh = value + lowHighOverflow = lowHighOverflow || overflow + } + if lowHighOverflow { + let (value, overflow) = highLow.addingReportingOverflow(1) + + highLow = value + highLowOverflow = highLowOverflow || overflow + } + if highLowOverflow { + highHigh &+= 1 + } + + return (high: (high: highHigh, low: highLow), low: (high: lowHigh, low: lowLow)) + } + + @inline(__always) + internal static func subtract(_ lhs: (high: Scalar, low: Scalar.Magnitude), _ rhs: (high: Scalar, low: Scalar.Magnitude)) -> ExtendedScalar { + return subtract(signExtend(lhs), signExtend(rhs)) + } + + @inline(__always) + internal static func subtract(_ lhs: ExtendedScalar, _ rhs: ExtendedScalar) -> ExtendedScalar { + return add(lhs, twosComplement(of: rhs)) + } + + // + + internal static func slowpathMultiply(_ lhs: ExtendedScalar, _ rhs: ExtendedScalar) -> ExtendedScalar { + var result: ExtendedScalar = ((0, 0), (0, 0)) + + let lhsIsNegative = Scalar.isSigned && lhs.high.high < .zero + let left = lhsIsNegative ? twosComplement(of: lhs) : lhs + + let rhsIsNegative = Scalar.isSigned && rhs.high.high < .zero + let right = rhsIsNegative ? twosComplement(of: rhs) : rhs + + let (leftIsPowerOfTwo, lhsShift) = isPowerOfTwo(left) + let (rightIsPowerOfTwo, rhsShift) = isPowerOfTwo(right) + + if leftIsPowerOfTwo { + result = leftShift(right, by: lhsShift) + } else if rightIsPowerOfTwo { + result = leftShift(left, by: rhsShift) + } else { + var shift: Scalar.Magnitude = 0 + var lastShift: Scalar.Magnitude = 0 + var shifted = right + var bits = left + + while bits.low.low != .zero || bits.low.high != .zero || bits.high.low != .zero || bits.high.high != .zero { + if (bits.low.low & 1) == 1 { + shifted = leftShift(shifted, by: shift - lastShift) + result = add(result, shifted) + + lastShift = shift + } + + bits = rightShift(bits, by: 1) + shift += 1 + } + } + + if lhsIsNegative != rhsIsNegative { + result = twosComplement(of: result) + } + + return result + } + + internal static func slowpathMultiply(_ lhs: Scalar, _ rhs: ExtendedScalar) -> ExtendedScalar { + let lhsIsNegative = lhs < .zero + + let extended = signExtend(lhs) + var result = slowpathMultiply(lhsIsNegative ? twosComplement(of: extended) : extended, rhs) + + if lhsIsNegative { + result = twosComplement(of: result) + } + + return result + } + + internal static func slowpathDivide(_ lhs: ExtendedScalar, _ rhs: ExtendedScalar) -> (quotient: ExtendedScalar, remainder: ExtendedScalar) { + let dividendIsNegative = Scalar.isSigned && lhs.high.high < .zero + var dividend = dividendIsNegative ? twosComplement(of: lhs) : lhs + + let divisorIsNegative = Scalar.isSigned && rhs.high.high < .zero + var divisor = divisorIsNegative ? twosComplement(of: rhs) : rhs + + guard isLessThanOrEqual(divisor, to: dividend) else { return (quotient: ((0, 0), (0, 0)), remainder: lhs) } + let (isPowerOfTwo, shift) = self.isPowerOfTwo(divisor) + + var quotient: ExtendedScalar + var remainder: ExtendedScalar + + if isPowerOfTwo { + remainder = ((0, 0), (0, 0)) + + if shift > 0 { + // The remainder when dividing by two is equal to the bits that are going to be + // shifted out when performing the right shfit below. + + var remainderMask: ExtendedScalar = leftShift(((~0, ~0), (~0, ~0)), by: shift) + remainderMask = ((~remainderMask.high.high, ~remainderMask.high.low), (~remainderMask.low.high, ~remainderMask.low.low)) + + remainder = ((dividend.high.high & remainderMask.high.high, dividend.high.low & remainderMask.high.low), (dividend.low.high & remainderMask.low.high, dividend.low.low & remainderMask.low.low)) + } + + quotient = rightShift(dividend, by: shift) + } else { + quotient = ((0, 0), (0, 0)) + + var remainingShifts = leadingZeroBitCount(of: divisor) - leadingZeroBitCount(of: dividend) + if remainingShifts > 0 { + divisor = leftShift(divisor, by: Scalar.Magnitude(remainingShifts)) + } + + // long division + repeat { + if isLessThanOrEqual(divisor, to: dividend) { + dividend = subtract(dividend, divisor) + quotient = leftShift(quotient, by: 1) + quotient.low.low |= 1 // set least significant bit to 1 + } else { + quotient = leftShift(quotient, by: 1) + } + + divisor = rightShift(divisor, by: 1) + remainingShifts -= 1 + } while remainingShifts >= 0 + + remainder = dividend + } + + if dividendIsNegative != divisorIsNegative { + quotient = twosComplement(of: quotient) + } + if dividendIsNegative { + remainder = twosComplement(of: remainder) + } + + return (quotient: quotient, remainder: remainder) + } + + // + + internal static func leftShift(_ value: ExtendedScalar, by shiftAmount: Scalar.Magnitude) -> ExtendedScalar { + guard shiftAmount < Scalar.bitWidth * 4 else { return ((0, 0), (0, 0)) } + + let shiftMask = Scalar.bitWidth - 1 + var result = value + for _ in 0 ..< shiftAmount { + result.high.high = Scalar(truncatingIfNeeded: (Scalar.Magnitude(truncatingIfNeeded: result.high.high) &<< 1) | (result.high.low &>> shiftMask)) + result.high.low = (result.high.low &<< 1) | (result.low.high &>> shiftMask) + result.low.high = (result.low.high &<< 1) | (result.low.low &>> shiftMask) + result.low.low &<<= 1 + } + + return result + } + + internal static func rightShift(_ value: ExtendedScalar, by shiftAmount: Scalar.Magnitude) -> ExtendedScalar { + guard shiftAmount < Scalar.bitWidth * 4 else { return ((0, 0), (0, 0)) } + + let shiftMask = Scalar.bitWidth - 1 + var result = value + for _ in 0 ..< shiftAmount { + result.low.low = (result.low.low &>> 1) | (result.low.high &<< shiftMask) + result.low.high = (result.low.high &>> 1) | (result.high.low &<< shiftMask) + result.high.low = (result.high.low &>> 1) | (Scalar.Magnitude(truncatingIfNeeded: result.high.high) &<< shiftMask) + result.high.high = Scalar(truncatingIfNeeded: Scalar.Magnitude(truncatingIfNeeded: result.high.high) &>> 1) + } + + return result + } + + // + + internal static func isLessThanOrEqual(_ lhs: ExtendedScalar, to rhs: ExtendedScalar) -> Bool { + let isLessThanOrEqual: Bool + + if lhs.high.high == rhs.high.high { + if lhs.high.low == rhs.high.low { + if lhs.low.high == rhs.low.high { + isLessThanOrEqual = (lhs.low.low <= rhs.low.low) + } else { + isLessThanOrEqual = (lhs.low.high <= rhs.low.high) + } + } else { + isLessThanOrEqual = (lhs.high.low <= rhs.high.low) + } + } else { + isLessThanOrEqual = (lhs.high.high <= rhs.high.high) + } + + return isLessThanOrEqual + } + + internal static func isLess(_ lhs: ExtendedScalar, than rhs: ExtendedScalar) -> Bool { + let isLessThanOrEqual: Bool + + if lhs.high.high == rhs.high.high { + if lhs.high.low == rhs.high.low { + if lhs.low.high == rhs.low.high { + isLessThanOrEqual = (lhs.low.low < rhs.low.low) + } else { + isLessThanOrEqual = (lhs.low.high < rhs.low.high) + } + } else { + isLessThanOrEqual = (lhs.high.low < rhs.high.low) + } + } else { + isLessThanOrEqual = (lhs.high.high < rhs.high.high) + } + + return isLessThanOrEqual + } + + internal static func isPowerOfTwo(_ value: ExtendedScalar) -> (isPowerOfTwo: Bool, power: Scalar.Magnitude) { + if (value.high.high.nonzeroBitCount + value.high.low.nonzeroBitCount + value.low.high.nonzeroBitCount + value.low.low.nonzeroBitCount) == 1 { + var bits = value + var shift: Scalar.Magnitude = 0 + + while bits.low.low > 1 || bits.low.high != .zero || bits.high.low != .zero || bits.high.high != .zero { + bits = rightShift(bits, by: 1) + shift += 1 + } + + return (true, shift) + } + + return (false, 0) + } + + internal static func leadingZeroBitCount(of value: ExtendedScalar) -> Int { + let bitWidth = Scalar.bitWidth + var leadingZeroBitCount = value.high.high.leadingZeroBitCount + + if leadingZeroBitCount == bitWidth { + leadingZeroBitCount += value.high.low.leadingZeroBitCount + + if leadingZeroBitCount == (bitWidth * 2) { + leadingZeroBitCount += value.low.high.leadingZeroBitCount + + if leadingZeroBitCount == (bitWidth * 3) { + leadingZeroBitCount += value.low.low.leadingZeroBitCount + } + } + } + + return leadingZeroBitCount + } + + // + + internal static func truncateAndReportOverflow(_ value: ExtendedScalar) -> (partialValue: Scalar, overflow: Bool) { + // Return value doesn't change. + let partialValue = Scalar(truncatingIfNeeded: value.low.low) + + // Check for overflow + var overflow: Bool = false + + if Scalar.isSigned { + // For signed integers we have different logic depending on if the input is + // negative or positive: + // + // Negative: Overflow occurs when any of the bits of any of the three most + // significant words are zero or if the most significant bit of + // the least significant word is 0. + // + // Positive: Overflow occurs when the any of the three most significant words are + // non-zero or if the most significant bit of the least significant word + // is 1. + + if value.high.high < .zero { + if value.high.high.nonzeroBitCount != Scalar.bitWidth || value.high.low.nonzeroBitCount != Scalar.bitWidth || value.low.high.nonzeroBitCount != Scalar.bitWidth { + overflow = true + } else if value.low.low.leadingZeroBitCount > 0 { + overflow = true + } + } else if value.high.high != .zero || value.high.low != .zero || value.low.high != .zero { + overflow = true + } else if value.low.low.leadingZeroBitCount == 0 { + overflow = true + } + } else { + // For unsigned integers overflow occurs when the any of the three most significant + // words are non-zero. + + overflow = (value.high.high > .zero) || (value.high.low > .zero) || (value.low.high > .zero) + } + + return (partialValue, overflow) + } +} diff --git a/Tests/ComplexTests/ComplexArithmeticTests.swift b/Tests/ComplexTests/ComplexArithmeticTests.swift index f6d795e..0d1775e 100644 --- a/Tests/ComplexTests/ComplexArithmeticTests.swift +++ b/Tests/ComplexTests/ComplexArithmeticTests.swift @@ -109,19 +109,6 @@ class ComplexArithmeticTests: XCTestCase { 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)) @@ -167,19 +154,6 @@ class ComplexArithmeticTests: XCTestCase { 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)) @@ -242,19 +216,6 @@ 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)) @@ -304,107 +265,94 @@ 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) { - CTAssertEqual(complex, complex + .zero, file: file, line: line) - CTAssertEqual(complex, complex .+ .zero, file: file, line: line) - CTAssertEqual(complex, complex + Scalar.zero, file: file, line: line) - CTAssertEqual(complex, Scalar.zero + complex, file: file, line: line) + CTAssertEqual(complex, complex + .zero) + CTAssertEqual(complex, complex .+ .zero) + CTAssertEqual(complex, complex + Scalar.zero) + CTAssertEqual(complex, Scalar.zero + complex) var result = complex result += .zero - CTAssertEqual(result, complex, file: file, line: line) + CTAssertEqual(result, complex) result = complex result .+= .zero - CTAssertEqual(result, complex, file: file, line: line) + CTAssertEqual(result, complex) result = complex result += Scalar.zero - CTAssertEqual(result, complex, file: file, line: line) + CTAssertEqual(result, complex) } private func testSubtractionWithZero(_ complex: Complex, file: StaticString = #file, line: UInt = #line) { - CTAssertEqual(complex, complex - .zero, file: file, line: line) - CTAssertEqual(complex, complex .- .zero, file: file, line: line) - CTAssertEqual(complex, complex - Scalar.zero, file: file, line: line) + CTAssertEqual(complex, complex - .zero) + CTAssertEqual(complex, complex .- .zero) + CTAssertEqual(complex, complex - Scalar.zero) var result = complex result -= .zero - CTAssertEqual(result, complex, file: file, line: line) + CTAssertEqual(result, complex) result = complex result .-= .zero - CTAssertEqual(result, complex, file: file, line: line) + CTAssertEqual(result, complex) result = complex result -= Scalar.zero - CTAssertEqual(result, complex, file: file, line: line) + CTAssertEqual(result, complex) } private func testMultiplicationWithZero(_ complex: Complex, file: StaticString = #file, line: UInt = #line) { - CTAssertEqual(.zero, complex * .zero, file: file, line: line) - CTAssertEqual(.zero, complex .* .zero, file: file, line: line) - CTAssertEqual(.zero, complex * Scalar.zero, file: file, line: line) - CTAssertEqual(.zero, Scalar.zero * complex, file: file, line: line) + CTAssertEqual(.zero, complex * .zero) + CTAssertEqual(.zero, complex .* .zero) + CTAssertEqual(.zero, complex * Scalar.zero) + CTAssertEqual(.zero, Scalar.zero * complex) var result = complex result *= .zero - CTAssertEqual(result, .zero, file: file, line: line) + CTAssertEqual(result, .zero) result = complex result .*= .zero - CTAssertEqual(result, .zero, file: file, line: line) + CTAssertEqual(result, .zero) result = complex result *= Scalar.zero - CTAssertEqual(result, .zero, file: file, line: line) + CTAssertEqual(result, .zero) } private func testAddition(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) { - CTAssertEqual(lhs + rhs, result, file: file, line: line) - CTAssertEqual(rhs + lhs, result, file: file, line: line) - CTAssertEqual(lhs .+ rhs, result, file: file, line: line) - CTAssertEqual(rhs .+ lhs, result, file: file, line: line) + CTAssertEqual(lhs + rhs, result) + CTAssertEqual(rhs + lhs, result) + CTAssertEqual(lhs .+ rhs, result) + CTAssertEqual(rhs .+ lhs, result) var complex = lhs complex += rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) complex = rhs complex += lhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) complex = lhs complex .+= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) complex = rhs complex .+= lhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testAddition(_ lhs: Complex, _ rhs: Scalar, _ result: Complex, file: StaticString = #file, line: UInt = #line) { - CTAssertEqual(lhs + rhs, result, file: file, line: line) - CTAssertEqual(rhs + lhs, result, file: file, line: line) + CTAssertEqual(lhs + rhs, result) + CTAssertEqual(rhs + lhs, result) var complex = lhs complex += rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testAdditionIgnoringOverflow(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { @@ -460,109 +408,57 @@ class ComplexArithmeticTests: XCTestCase { } } - 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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, Scalar.max - 1, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, 0, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, Scalar.max - 1, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, -2, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, 0, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, -2, file: file, line: line) - CTAssertEqual(result.partialValue.imaginary, -2, file: file, line: line) - } - private func testSubtraction(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) { - CTAssertEqual(lhs - rhs, result, file: file, line: line) - CTAssertEqual(lhs .- rhs, result, file: file, line: line) + CTAssertEqual(lhs - rhs, result) + CTAssertEqual(lhs .- rhs, result) var complex = lhs complex -= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) complex = lhs complex .-= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testSubtraction(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) where Scalar: SignedNumeric { - CTAssertEqual(lhs - rhs, result, file: file, line: line) - CTAssertEqual(rhs - lhs, -result, file: file, line: line) - CTAssertEqual(lhs .- rhs, result, file: file, line: line) - CTAssertEqual(rhs .- lhs, -result, file: file, line: line) + CTAssertEqual(lhs - rhs, result) + CTAssertEqual(rhs - lhs, -result) + CTAssertEqual(lhs .- rhs, result) + CTAssertEqual(rhs .- lhs, -result) var complex = lhs complex -= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) complex = rhs complex -= lhs - CTAssertEqual(complex, -result, file: file, line: line) + CTAssertEqual(complex, -result) complex = lhs complex .-= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) complex = rhs complex .-= lhs - CTAssertEqual(complex, -result, file: file, line: line) + CTAssertEqual(complex, -result) } private func testSubtraction(_ lhs: Complex, _ rhs: Scalar, _ result: Complex, file: StaticString = #file, line: UInt = #line) { - CTAssertEqual(lhs - rhs, result, file: file, line: line) + CTAssertEqual(lhs - rhs, result) var complex = lhs complex -= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testSubtraction(_ lhs: Complex, _ rhs: Scalar, _ result: Complex, file: StaticString = #file, line: UInt = #line) where Scalar: SignedNumeric { - CTAssertEqual(lhs - rhs, result, file: file, line: line) - CTAssertEqual(rhs - lhs, -result, file: file, line: line) + CTAssertEqual(lhs - rhs, result) + CTAssertEqual(rhs - lhs, -result) var complex = lhs complex -= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testSubtractionIgnoringOverflow(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { @@ -606,43 +502,17 @@ class ComplexArithmeticTests: XCTestCase { } } - 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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, 1, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, 0, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(result.overflow, file: file, line: line) - CTAssertEqual(result.partialValue.real, 1, file: file, line: line) - CTAssertEqual(result.partialValue.imaginary, 1, file: file, line: line) - } - private func testMultiplication(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) { - CTAssertEqual(lhs * rhs, result, file: file, line: line) - CTAssertEqual(rhs * lhs, result, file: file, line: line) + CTAssertEqual(lhs * rhs, result) + CTAssertEqual(rhs * lhs, result) var complex = lhs complex *= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) complex = rhs complex *= lhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testMultiplicationIgnoringOverflow(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { @@ -676,177 +546,86 @@ class ComplexArithmeticTests: XCTestCase { private func testMultiplication(_ lhs: Complex, _ rhs: Scalar, file: StaticString = #file, line: UInt = #line) { let result = Complex(real: lhs.real * rhs, imaginary: lhs.imaginary * rhs) - CTAssertEqual(lhs * rhs, result, file: file, line: line) - CTAssertEqual(rhs * lhs, result, file: file, line: line) + CTAssertEqual(lhs * rhs, result) + CTAssertEqual(rhs * lhs, result) var complex = lhs complex *= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testComponentwiseMultiplication(_ lhs: Complex, _ rhs: Complex, file: StaticString = #file, line: UInt = #line) { var result = lhs .* rhs - CTAssertEqual(result.real, lhs.real * rhs.real, file: file, line: line) - CTAssertEqual(result.imaginary, lhs.imaginary * rhs.imaginary, file: file, line: line) + CTAssertEqual(result.real, lhs.real * rhs.real) + CTAssertEqual(result.imaginary, lhs.imaginary * rhs.imaginary) result = lhs result .*= rhs - CTAssertEqual(result.real, lhs.real * rhs.real, file: file, line: line) - CTAssertEqual(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) - CTAssertEqual(fullWidth.high.real, 1, file: file, line: line) - CTAssertEqual(fullWidth.high.imaginary, 1, file: file, line: line) - CTAssertEqual(fullWidth.low.real, Scalar.Magnitude.max - 1, file: file, line: line) - CTAssertEqual(fullWidth.low.imaginary, Scalar.Magnitude.max - 1, file: file, line: line) - - var overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, Scalar.max - 1, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, Scalar.max - 1, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, 0, file: file, line: line) - CTAssertEqual(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) - CTAssertEqual(fullWidth.high.real, 1, file: file, line: line) - CTAssertEqual(fullWidth.high.imaginary, 1, file: file, line: line) - CTAssertEqual(fullWidth.low.real, Scalar.Magnitude.max - 3, file: file, line: line) - CTAssertEqual(fullWidth.low.imaginary, Scalar.Magnitude.max - 3, file: file, line: line) - - var overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, -4, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, -4, file: file, line: line) - CTAssertEqual(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) - - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, 0, file: file, line: line) - CTAssertEqual(overflow.partialValue.imaginary, -4, file: file, line: line) + CTAssertEqual(result.real, lhs.real * rhs.real) + CTAssertEqual(result.imaginary, lhs.imaginary * rhs.imaginary) } private func testDivision(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) where Scalar: BinaryInteger { - CTAssertEqual(lhs / rhs, result, file: file, line: line) + CTAssertEqual(lhs / rhs, result) var complex = lhs complex /= rhs - CTAssertEqual(complex, result, file: file, line: line) + CTAssertEqual(complex, result) } private func testDivision(_ lhs: Complex, _ rhs: Complex, _ result: Complex, file: StaticString = #file, line: UInt = #line) where Scalar: BinaryFloatingPoint { - CTAssertTrue((lhs / rhs - result).modulus < 0.0001, file: file, line: line) + CTAssertTrue((lhs / rhs - result).modulus < 0.0001) var complex = lhs complex /= rhs - CTAssertTrue((complex - result).modulus < 0.0001, file: file, line: line) + CTAssertTrue((complex - result).modulus < 0.0001) } private func testDivision(_ lhs: Complex, _ rhs: Scalar, file: StaticString = #file, line: UInt = #line) where Scalar: BinaryInteger { - CTAssertEqual(lhs / rhs, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs), file: file, line: line) + CTAssertEqual(lhs / rhs, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs)) var complex = lhs complex /= rhs - CTAssertEqual(complex, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs), file: file, line: line) + CTAssertEqual(complex, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs)) } private func testDivision(_ lhs: Complex, _ rhs: Scalar, file: StaticString = #file, line: UInt = #line) where Scalar: SignedInteger { - CTAssertEqual(lhs / rhs, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs), file: file, line: line) - CTAssertEqual(rhs / lhs, (Complex(real: rhs, imaginary: 0) / lhs), file: file, line: line) + CTAssertEqual(lhs / rhs, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs)) + CTAssertEqual(rhs / lhs, (Complex(real: rhs, imaginary: 0) / lhs)) var complex = lhs complex /= rhs - CTAssertEqual(complex, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs), file: file, line: line) + CTAssertEqual(complex, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs)) } private func testDivision(_ lhs: Complex, _ rhs: Scalar, file: StaticString = #file, line: UInt = #line) where Scalar: BinaryFloatingPoint { - CTAssertEqual(lhs / rhs, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs), file: file, line: line) - CTAssertEqual(rhs / lhs, (Complex(real: rhs, imaginary: 0) / lhs), file: file, line: line) + CTAssertEqual(lhs / rhs, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs)) + CTAssertEqual(rhs / lhs, (Complex(real: rhs, imaginary: 0) / lhs)) var complex = lhs complex /= rhs - CTAssertEqual(complex, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs), file: file, line: line) + CTAssertEqual(complex, Complex(real: lhs.real / rhs, imaginary: lhs.imaginary / rhs)) } private func testComponentwiseDivision(_ lhs: Complex, _ rhs: Complex, file: StaticString = #file, line: UInt = #line) where Scalar: BinaryInteger { var result = lhs ./ rhs - CTAssertEqual(result.real, lhs.real / rhs.real, file: file, line: line) - CTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary, file: file, line: line) + CTAssertEqual(result.real, lhs.real / rhs.real) + CTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary) result = lhs result ./= rhs - CTAssertEqual(result.real, lhs.real / rhs.real, file: file, line: line) - CTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary, file: file, line: line) + CTAssertEqual(result.real, lhs.real / rhs.real) + CTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary) } private func testComponentwiseDivision(_ lhs: Complex, _ rhs: Complex, file: StaticString = #file, line: UInt = #line) where Scalar: FloatingPoint { var result = lhs ./ rhs - CTAssertEqual(result.real, lhs.real / rhs.real, file: file, line: line) - CTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary, file: file, line: line) + CTAssertEqual(result.real, lhs.real / rhs.real) + CTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary) result = lhs result ./= rhs - CTAssertEqual(result.real, lhs.real / rhs.real, file: file, line: line) - CTAssertEqual(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)) - CTAssertEqual(fullWidth.quotient.real, Scalar.isSigned ? 2 : 1, file: file, line: line) - CTAssertEqual(fullWidth.quotient.imaginary, Scalar.isSigned ? 2 : 1, file: file, line: line) - CTAssertEqual(fullWidth.remainder.real, Scalar.isSigned ? 2 : 1, file: file, line: line) - CTAssertEqual(fullWidth.remainder.imaginary, Scalar.isSigned ? 2 : 1, file: file, line: line) - - var overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue, lhs, file: file, line: line) - - rhs = Complex(real: 0, imaginary: Scalar.max) - overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, lhs.real, file: file, line: line) - CTAssertEqual(overflow.partialValue.imaginary, 1, file: file, line: line) - - rhs = Complex(real: Scalar.max, imaginary: 0) - overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) - CTAssertTrue(overflow.overflow, file: file, line: line) - CTAssertEqual(overflow.partialValue.real, 1, file: file, line: line) - CTAssertEqual(overflow.partialValue.imaginary, lhs.imaginary, file: file, line: line) + CTAssertEqual(result.real, lhs.real / rhs.real) + CTAssertEqual(result.imaginary, lhs.imaginary / rhs.imaginary) } } diff --git a/Tests/ComplexTests/ComplexOverflowingArithmeticTests.swift b/Tests/ComplexTests/ComplexOverflowingArithmeticTests.swift new file mode 100644 index 0000000..008e6cb --- /dev/null +++ b/Tests/ComplexTests/ComplexOverflowingArithmeticTests.swift @@ -0,0 +1,1271 @@ +// +// ComplexOverflowingArithmeticTests.swift +// Complex +// +// Copyright © 2020 SomeRandomiOSDev. All rights reserved. +// + +@testable import Complex +import XCTest + +class ComplexOverflowingArithmeticTests: XCTestCase { + + // MARK: Test Methods + + 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 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 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 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) + } + + func testMultiplyReportingOverflow() { + testMultiplyReportingOverflow(forType: Int8.self) + testMultiplyReportingOverflow(forType: Int16.self) + testMultiplyReportingOverflow(forType: Int32.self) + testMultiplyReportingOverflow(forType: Int64.self) + testMultiplyReportingOverflow(forType: Int.self) + testMultiplyReportingOverflow(forType: UInt8.self) + testMultiplyReportingOverflow(forType: UInt16.self) + testMultiplyReportingOverflow(forType: UInt32.self) + testMultiplyReportingOverflow(forType: UInt64.self) + testMultiplyReportingOverflow(forType: UInt.self) + } + + func testMultiplyFullWidth() { + testMultiplyFullWidth(forType: Int8.self) + testMultiplyFullWidth(forType: Int16.self) + testMultiplyFullWidth(forType: Int32.self) + testMultiplyFullWidth(forType: Int64.self) + testMultiplyFullWidth(forType: Int.self) + } + + func testDivideReportingOverflow() { + testDivideReportingOverflow(forType: Int8.self) + testDivideReportingOverflow(forType: Int16.self) + testDivideReportingOverflow(forType: Int32.self) + testDivideReportingOverflow(forType: Int64.self) + testDivideReportingOverflow(forType: Int.self) + testDivideReportingOverflow(forType: UInt8.self) + testDivideReportingOverflow(forType: UInt16.self) + testDivideReportingOverflow(forType: UInt32.self) + testDivideReportingOverflow(forType: UInt64.self) + testDivideReportingOverflow(forType: UInt.self) + } + + func testDivideFullWidth() { + testDivideFullWidth(forType: Int8.self) + testDivideFullWidth(forType: Int16.self) + testDivideFullWidth(forType: Int32.self) + testDivideFullWidth(forType: Int64.self) + testDivideFullWidth(forType: Int.self) + } + + // + + func testSignExtension() { + testSignExtension(forType: Int8.self) + testSignExtension(forType: Int16.self) + testSignExtension(forType: Int32.self) + testSignExtension(forType: Int64.self) + testSignExtension(forType: Int.self) + testSignExtension(forType: UInt8.self) + testSignExtension(forType: UInt16.self) + testSignExtension(forType: UInt32.self) + testSignExtension(forType: UInt64.self) + testSignExtension(forType: UInt.self) + } + + func testTwosComplement() { + testTwosComplement(forType: Int8.self) + testTwosComplement(forType: Int16.self) + testTwosComplement(forType: Int32.self) + testTwosComplement(forType: Int64.self) + testTwosComplement(forType: Int.self) + testTwosComplement(forType: UInt8.self) + testTwosComplement(forType: UInt16.self) + testTwosComplement(forType: UInt32.self) + testTwosComplement(forType: UInt64.self) + testTwosComplement(forType: UInt.self) + } + + func testAddExtendedScalars() { + testAddExtendedScalars(forType: Int8.self) + testAddExtendedScalars(forType: Int16.self) + testAddExtendedScalars(forType: Int32.self) + testAddExtendedScalars(forType: Int64.self) + testAddExtendedScalars(forType: Int.self) + testAddExtendedScalars(forType: UInt8.self) + testAddExtendedScalars(forType: UInt16.self) + testAddExtendedScalars(forType: UInt32.self) + testAddExtendedScalars(forType: UInt64.self) + testAddExtendedScalars(forType: UInt.self) + } + + func testSubtractExtendedScalars() { + testSubtractExtendedScalars(forType: Int8.self) + testSubtractExtendedScalars(forType: Int16.self) + testSubtractExtendedScalars(forType: Int32.self) + testSubtractExtendedScalars(forType: Int64.self) + testSubtractExtendedScalars(forType: Int.self) + testSubtractExtendedScalars(forType: UInt8.self) + testSubtractExtendedScalars(forType: UInt16.self) + testSubtractExtendedScalars(forType: UInt32.self) + testSubtractExtendedScalars(forType: UInt64.self) + testSubtractExtendedScalars(forType: UInt.self) + } + + func testLeftShiftExtendedScalars() { + testLeftShiftExtendedScalars(forType: Int8.self) + testLeftShiftExtendedScalars(forType: Int16.self) + testLeftShiftExtendedScalars(forType: Int32.self) + testLeftShiftExtendedScalars(forType: Int64.self) + testLeftShiftExtendedScalars(forType: Int.self) + testLeftShiftExtendedScalars(forType: UInt8.self) + testLeftShiftExtendedScalars(forType: UInt16.self) + testLeftShiftExtendedScalars(forType: UInt32.self) + testLeftShiftExtendedScalars(forType: UInt64.self) + testLeftShiftExtendedScalars(forType: UInt.self) + } + + func testRightShiftExtendedScalars() { + testRightShiftExtendedScalars(forType: Int8.self) + testRightShiftExtendedScalars(forType: Int16.self) + testRightShiftExtendedScalars(forType: Int32.self) + testRightShiftExtendedScalars(forType: Int64.self) + testRightShiftExtendedScalars(forType: Int.self) + testRightShiftExtendedScalars(forType: UInt8.self) + testRightShiftExtendedScalars(forType: UInt16.self) + testRightShiftExtendedScalars(forType: UInt32.self) + testRightShiftExtendedScalars(forType: UInt64.self) + testRightShiftExtendedScalars(forType: UInt.self) + } + + func testIsLessThanOrEqual() { + testIsLessThanOrEqual(forType: Int8.self) + testIsLessThanOrEqual(forType: Int16.self) + testIsLessThanOrEqual(forType: Int32.self) + testIsLessThanOrEqual(forType: Int64.self) + testIsLessThanOrEqual(forType: Int.self) + testIsLessThanOrEqual(forType: UInt8.self) + testIsLessThanOrEqual(forType: UInt16.self) + testIsLessThanOrEqual(forType: UInt32.self) + testIsLessThanOrEqual(forType: UInt64.self) + testIsLessThanOrEqual(forType: UInt.self) + } + + func testIsLessThan() { + testIsLessThan(forType: Int8.self) + testIsLessThan(forType: Int16.self) + testIsLessThan(forType: Int32.self) + testIsLessThan(forType: Int64.self) + testIsLessThan(forType: Int.self) + testIsLessThan(forType: UInt8.self) + testIsLessThan(forType: UInt16.self) + testIsLessThan(forType: UInt32.self) + testIsLessThan(forType: UInt64.self) + testIsLessThan(forType: UInt.self) + } + + func testIsPowerOfTwo() { + testIsPowerOfTwo(forType: Int8.self) + testIsPowerOfTwo(forType: Int16.self) + testIsPowerOfTwo(forType: Int32.self) + testIsPowerOfTwo(forType: Int64.self) + testIsPowerOfTwo(forType: Int.self) + testIsPowerOfTwo(forType: UInt8.self) + testIsPowerOfTwo(forType: UInt16.self) + testIsPowerOfTwo(forType: UInt32.self) + testIsPowerOfTwo(forType: UInt64.self) + testIsPowerOfTwo(forType: UInt.self) + } + + func testLeadingZeroBitCount() { + testLeadingZeroBitCount(forType: Int8.self) + testLeadingZeroBitCount(forType: Int16.self) + testLeadingZeroBitCount(forType: Int32.self) + testLeadingZeroBitCount(forType: Int64.self) + testLeadingZeroBitCount(forType: Int.self) + testLeadingZeroBitCount(forType: UInt8.self) + testLeadingZeroBitCount(forType: UInt16.self) + testLeadingZeroBitCount(forType: UInt32.self) + testLeadingZeroBitCount(forType: UInt64.self) + testLeadingZeroBitCount(forType: UInt.self) + } + + func testTruncateExtendedScalars() { + testTruncateExtendedScalars(forType: Int8.self) + testTruncateExtendedScalars(forType: Int16.self) + testTruncateExtendedScalars(forType: Int32.self) + testTruncateExtendedScalars(forType: Int64.self) + testTruncateExtendedScalars(forType: Int.self) + testTruncateExtendedScalars(forType: UInt8.self) + testTruncateExtendedScalars(forType: UInt16.self) + testTruncateExtendedScalars(forType: UInt32.self) + testTruncateExtendedScalars(forType: UInt64.self) + testTruncateExtendedScalars(forType: UInt.self) + } + + func testSlowpathMultiply() { + testSlowpathMultiply(forType: Int8.self) + testSlowpathMultiply(forType: Int16.self) + testSlowpathMultiply(forType: Int32.self) + testSlowpathMultiply(forType: Int64.self) + testSlowpathMultiply(forType: Int.self) + testSlowpathMultiply(forType: UInt8.self) + testSlowpathMultiply(forType: UInt16.self) + testSlowpathMultiply(forType: UInt32.self) + testSlowpathMultiply(forType: UInt64.self) + testSlowpathMultiply(forType: UInt.self) + } + + func testSlowpathDivide() { + testSlowpathDivide(forType: Int8.self) + testSlowpathDivide(forType: Int16.self) + testSlowpathDivide(forType: Int32.self) + testSlowpathDivide(forType: Int64.self) + testSlowpathDivide(forType: Int.self) + testSlowpathDivide(forType: UInt8.self) + testSlowpathDivide(forType: UInt16.self) + testSlowpathDivide(forType: UInt32.self) + testSlowpathDivide(forType: UInt64.self) + testSlowpathDivide(forType: UInt.self) + } + + // MARK: Private Methods + + private func testOverflowingAddition(forType: Scalar.Type) 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) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, Scalar.max - 1) + CTAssertEqual(result.partialValue.imaginary, 0) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, 0) + CTAssertEqual(result.partialValue.imaginary, Scalar.max - 1) + + lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, Scalar.max - 1) + CTAssertEqual(result.partialValue.imaginary, Scalar.max - 1) + } + + private func testOverflowingAddition(forType: Scalar.Type) 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) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, -2) + CTAssertEqual(result.partialValue.imaginary, 0) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, 0) + CTAssertEqual(result.partialValue.imaginary, -2) + + lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + result = lhs.addingReportingOverflow(rhs) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, -2) + CTAssertEqual(result.partialValue.imaginary, -2) + } + + private func testOverflowingSubtraction(forType: Scalar.Type) where Scalar: FixedWidthInteger { + var lhs = Complex(real: Scalar.min, imaginary: 0) + var rhs = Complex(real: Scalar.max, imaginary: 0) + var result = lhs.subtractingReportingOverflow(rhs) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, 1) + CTAssertEqual(result.partialValue.imaginary, 0) + + lhs = Complex(real: 0, imaginary: Scalar.min) + rhs = Complex(real: 0, imaginary: Scalar.max) + result = lhs.subtractingReportingOverflow(rhs) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, 0) + CTAssertEqual(result.partialValue.imaginary, 1) + + lhs = Complex(real: Scalar.min, imaginary: Scalar.min) + rhs = Complex(real: Scalar.max, imaginary: Scalar.max) + result = lhs.subtractingReportingOverflow(rhs) + + CTAssertTrue(result.overflow) + CTAssertEqual(result.partialValue.real, 1) + CTAssertEqual(result.partialValue.imaginary, 1) + } + + private func testComponentwiseOverflowingMultiplication(forType: Scalar.Type) 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) + CTAssertEqual(fullWidth.high.real, 1) + CTAssertEqual(fullWidth.high.imaginary, 1) + CTAssertEqual(fullWidth.low.real, Scalar.Magnitude.max - 1) + CTAssertEqual(fullWidth.low.imaginary, Scalar.Magnitude.max - 1) + + var overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, Scalar.max - 1) + CTAssertEqual(overflow.partialValue.imaginary, Scalar.max - 1) + + lhs = Complex(real: Scalar.max, imaginary: 0) + rhs = Complex(real: 2, imaginary: 0) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, Scalar.max - 1) + CTAssertEqual(overflow.partialValue.imaginary, 0) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: 2) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 0) + CTAssertEqual(overflow.partialValue.imaginary, Scalar.max - 1) + } + + private func testComponentwiseOverflowingMultiplication(forType: Scalar.Type) 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) + CTAssertEqual(fullWidth.high.real, 1) + CTAssertEqual(fullWidth.high.imaginary, 1) + CTAssertEqual(fullWidth.low.real, Scalar.Magnitude.max - 3) + CTAssertEqual(fullWidth.low.imaginary, Scalar.Magnitude.max - 3) + + var overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, -4) + CTAssertEqual(overflow.partialValue.imaginary, -4) + + lhs = Complex(real: Scalar.max, imaginary: 0) + rhs = Complex(real: 4, imaginary: 0) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, -4) + CTAssertEqual(overflow.partialValue.imaginary, 0) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: 4) + overflow = lhs.componentwiseMultipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 0) + CTAssertEqual(overflow.partialValue.imaginary, -4) + } + + private func testComponentwiseOverflowingDivision(forType: Scalar.Type) 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)) + CTAssertEqual(fullWidth.quotient.real, Scalar.isSigned ? 2 : 1) + CTAssertEqual(fullWidth.quotient.imaginary, Scalar.isSigned ? 2 : 1) + CTAssertEqual(fullWidth.remainder.real, Scalar.isSigned ? 2 : 1) + CTAssertEqual(fullWidth.remainder.imaginary, Scalar.isSigned ? 2 : 1) + + var overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue, lhs) + + rhs = Complex(real: 0, imaginary: Scalar.max) + overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, lhs.real) + CTAssertEqual(overflow.partialValue.imaginary, 1) + + rhs = Complex(real: Scalar.max, imaginary: 0) + overflow = lhs.componentwiseDividedReportingOverflow(by: rhs) + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 1) + CTAssertEqual(overflow.partialValue.imaginary, lhs.imaginary) + } + + private func testMultiplyReportingOverflow(forType: Scalar.Type) where Scalar: FixedWidthInteger { + if Scalar.isSigned { + var lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + var rhs = Complex(real: 4, imaginary: 4) + + var overflow = lhs.multipliedReportingOverflow(by: rhs) + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 0) + CTAssertEqual(overflow.partialValue.imaginary, -8) + + lhs = Complex(real: Scalar.max, imaginary: 0) + rhs = Complex(real: 4, imaginary: 0) + overflow = lhs.multipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, -4) + CTAssertEqual(overflow.partialValue.imaginary, 0) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: 4) + overflow = lhs.multipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 4) + CTAssertEqual(overflow.partialValue.imaginary, 0) + + lhs = Complex(real: -10, imaginary: 4) + rhs = Complex(real: -10, imaginary: 3) + overflow = lhs.multipliedReportingOverflow(by: rhs) + + CTAssertFalse(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 88) + CTAssertEqual(overflow.partialValue.imaginary, -70) + } else { + var lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + var rhs = Complex(real: 2, imaginary: 2) + + var overflow = lhs.multipliedReportingOverflow(by: rhs) + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 0) + CTAssertEqual(overflow.partialValue.imaginary, Scalar.max &<< 2) + + lhs = Complex(real: Scalar.max, imaginary: 0) + rhs = Complex(real: 2, imaginary: 0) + overflow = lhs.multipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, Scalar.max - 1) + CTAssertEqual(overflow.partialValue.imaginary, 0) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: 2) + overflow = lhs.multipliedReportingOverflow(by: rhs) + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 2) + CTAssertEqual(overflow.partialValue.imaginary, 0) + + lhs = Complex(real: 10, imaginary: 4) + rhs = Complex(real: 10, imaginary: 3) + overflow = lhs.multipliedReportingOverflow(by: rhs) + + CTAssertFalse(overflow.overflow) + CTAssertEqual(overflow.partialValue.real, 88) + CTAssertEqual(overflow.partialValue.imaginary, 70) + } + } + + private func testMultiplyFullWidth(forType: Scalar.Type) where Scalar: FixedWidthInteger, Scalar: SignedInteger { + var lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + var rhs = Complex(real: 8, imaginary: 4) + var fullWidth = lhs.multipliedFullWidth(by: rhs) + var real = ((fullWidth.high.high.real, fullWidth.high.low.real), (fullWidth.low.high.real, fullWidth.low.low.real)) + var imaginary = ((fullWidth.high.high.imaginary, fullWidth.high.low.imaginary), (fullWidth.low.high.imaginary, fullWidth.low.low.imaginary)) + + CTAssertEqual(real, ((0, 0), (1, Scalar.Magnitude.max &<< 2))) + CTAssertEqual(imaginary, ((0, 0), (5, Scalar.max.multipliedFullWidth(by: 12).low))) + + lhs = Complex(real: Scalar.max, imaginary: 0) + rhs = Complex(real: 4, imaginary: 0) + fullWidth = lhs.multipliedFullWidth(by: rhs) + real = ((fullWidth.high.high.real, fullWidth.high.low.real), (fullWidth.low.high.real, fullWidth.low.low.real)) + imaginary = ((fullWidth.high.high.imaginary, fullWidth.high.low.imaginary), (fullWidth.low.high.imaginary, fullWidth.low.low.imaginary)) + + CTAssertEqual(real, ((0, 0), (1, Scalar.Magnitude.max &<< 2))) + CTAssertEqual(imaginary, ((0, 0), (0, 0))) + + lhs = Complex(real: 0, imaginary: Scalar.max) + rhs = Complex(real: 0, imaginary: 4) + fullWidth = lhs.multipliedFullWidth(by: rhs) + real = ((fullWidth.high.high.real, fullWidth.high.low.real), (fullWidth.low.high.real, fullWidth.low.low.real)) + imaginary = ((fullWidth.high.high.imaginary, fullWidth.high.low.imaginary), (fullWidth.low.high.imaginary, fullWidth.low.low.imaginary)) + + CTAssertEqual(real, ((~0, ~0), (Scalar.Magnitude.max - 1, 4))) + CTAssertEqual(imaginary, ((0, 0), (0, 0))) + + lhs = Complex(real: -10, imaginary: 4) + rhs = Complex(real: -10, imaginary: 3) + fullWidth = lhs.multipliedFullWidth(by: rhs) + real = ((fullWidth.high.high.real, fullWidth.high.low.real), (fullWidth.low.high.real, fullWidth.low.low.real)) + imaginary = ((fullWidth.high.high.imaginary, fullWidth.high.low.imaginary), (fullWidth.low.high.imaginary, fullWidth.low.low.imaginary)) + + CTAssertEqual(real, ((0, 0), (0, 88))) + CTAssertEqual(imaginary, ((~0, ~0), (~0, Scalar.Magnitude(truncatingIfNeeded: Scalar(-70))))) + } + + private func testDivideReportingOverflow(forType: Scalar.Type) where Scalar: FixedWidthInteger { + var lhs = Complex(real: Scalar.random(in: Scalar.min ... Scalar.max), imaginary: Scalar.random(in: Scalar.min ... Scalar.max)) + var rhs = Complex(real: 0, imaginary: 0) + var overflow = lhs.dividedReportingOverflow(by: rhs) // divide by 0 + + CTAssertTrue(overflow.overflow) + CTAssertEqual(overflow.partialValue, rhs) + + lhs = Complex(real: 11, imaginary: 9) + rhs = Complex(real: 7, imaginary: 3) + overflow = lhs.dividedReportingOverflow(by: rhs) + + CTAssertFalse(overflow.overflow) + CTAssertEqual(overflow.partialValue, Complex(real: 1, imaginary: 0)) + + lhs = Complex(real: 11, imaginary: 9) + rhs = Complex(real: 3, imaginary: 7) + overflow = lhs.dividedReportingOverflow(by: rhs) + + CTAssertEqual(overflow.overflow, !Scalar.isSigned) + CTAssertEqual(overflow.partialValue.real, 1) + + if Scalar.isSigned { + CTAssertEqual(overflow.partialValue.imaginary, 0) + } + + lhs = Complex(real: Scalar.max, imaginary: Scalar.max) + rhs = Complex(real: 4, imaginary: 4) + overflow = lhs.dividedReportingOverflow(by: rhs) + + CTAssertFalse(overflow.overflow) + CTAssertEqual(overflow.partialValue, Complex(real: Scalar.max &>> 2, imaginary: 0)) + } + + private func testDivideFullWidth(forType: Scalar.Type) where Scalar: FixedWidthInteger, Scalar: SignedInteger { + var lhs = Complex(real: 7, imaginary: 3) + var rhs: Complex.ExtendedComplex = ((.zero, .zero), (.zero, Complex(real: 11, imaginary: 9))) + var fullWidth = lhs.dividingFullWidth(rhs) + + CTAssertEqual(fullWidth.quotient, Complex(real: 1, imaginary: 0)) + CTAssertEqual(fullWidth.remainder, Complex(real: 46, imaginary: 30)) + + lhs = Complex(real: 3, imaginary: 7) + fullWidth = lhs.dividingFullWidth(rhs) + + CTAssertEqual(fullWidth.quotient, Complex(real: 1, imaginary: 0)) + CTAssertEqual(fullWidth.remainder, Complex(real: 38, imaginary: -50)) + + lhs = Complex(real: 4, imaginary: 4) + rhs = ((.zero, .zero), (.zero, Complex(real: Scalar.Magnitude(Scalar.max), imaginary: Scalar.Magnitude(Scalar.max)))) + fullWidth = lhs.dividingFullWidth(rhs) + + CTAssertEqual(fullWidth.quotient, Complex(real: Scalar.max &>> 2, imaginary: 0)) + CTAssertEqual(fullWidth.remainder, Complex(real: 24, imaginary: 0)) + } + + // + + private func testSignExtension(forType: Scalar.Type) where Scalar: FixedWidthInteger { + var value = Scalar.min.multipliedFullWidth(by: 1) + var extended = Complex.signExtend(value) + CTAssertEqual(extended, (high: (high: Scalar.isSigned ? ~0 : 0, low: Scalar.isSigned ? Scalar.Magnitude.max : 0), low: (high: Scalar.Magnitude(truncatingIfNeeded: value.high), low: value.low))) + + extended = Complex.signExtend(Scalar.min) + CTAssertEqual(extended, (high: (high: Scalar.isSigned ? ~0 : 0, low: Scalar.isSigned ? Scalar.Magnitude.max : 0), low: (high: Scalar.Magnitude(truncatingIfNeeded: value.high), low: value.low))) + + value = Scalar.max.multipliedFullWidth(by: 1) + extended = Complex.signExtend(value) + CTAssertEqual(extended, (high: (high: 0, low: 0), low: (high: Scalar.Magnitude(truncatingIfNeeded: value.high), low: value.low))) + + extended = Complex.signExtend(Scalar.max) + CTAssertEqual(extended, (high: (high: 0, low: 0), low: (high: Scalar.Magnitude(truncatingIfNeeded: value.high), low: value.low))) + + extended = Complex.signExtend((0, 0)) + CTAssertEqual(extended, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + + extended = Complex.signExtend(Scalar.zero) + CTAssertEqual(extended, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + + value = (Scalar.max, Scalar.Magnitude.max) + extended = Complex.signExtend(value) + CTAssertEqual(extended, (high: (high: 0, low: 0), low: (high: Scalar.Magnitude(truncatingIfNeeded: value.high), low: value.low))) + } + + private func testTwosComplement(forType: Scalar.Type) where Scalar: FixedWidthInteger { + var value2: (high: Scalar, low: Scalar.Magnitude) = (0, 0) + var complement2 = Complex.twosComplement(of: value2) + CTAssertEqual(complement2.high, value2.high) + CTAssertEqual(complement2.low, value2.low) + + value2 = (~0, 0) + complement2 = Complex.twosComplement(of: value2) + CTAssertEqual(complement2.high, 1) + CTAssertEqual(complement2.low, value2.low) + + value2 = (0, 1) + complement2 = Complex.twosComplement(of: value2) + CTAssertEqual(complement2.high, ~0) + CTAssertEqual(complement2.low, ~0) + + // + + var extendedValue: Complex.ExtendedScalar = ((0, 0), (0, 0)) + var extendedComplement = Complex.twosComplement(of: extendedValue) + CTAssertEqual(extendedComplement, extendedValue) + + extendedValue = ((~0, 0), (0, 0)) + extendedComplement = Complex.twosComplement(of: extendedValue) + CTAssertEqual(extendedComplement, (high: (high: 1, low: 0), low: (high: 0, low: 0))) + + extendedValue = ((0, 0), (0, 1)) + extendedComplement = Complex.twosComplement(of: extendedValue) + CTAssertEqual(extendedComplement, (high: (high: ~0, low: ~0), low: (high: ~0, low: ~0))) + } + + private func testAddExtendedScalars(forType: Scalar.Type) where Scalar: FixedWidthInteger { + // Test: 0 + 0 = 0 + // + var nonextendedLHS: (high: Scalar, low: Scalar.Magnitude) = (high: 0, low: 0) + var nonextendedRHS: (high: Scalar, low: Scalar.Magnitude) = (high: 0, low: 0) + var result = Complex.add(nonextendedLHS, nonextendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + + var extendedLHS = Complex.signExtend(nonextendedLHS) + var extendedRHS = Complex.signExtend(nonextendedRHS) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + + // Test overflow of `low.low` + // + nonextendedLHS = (high: 0, low: 1) + nonextendedRHS = (high: 0, low: Scalar.Magnitude.max) + result = Complex.add(nonextendedLHS, nonextendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 1, low: 0))) + + extendedLHS = Complex.signExtend(nonextendedLHS) + extendedRHS = Complex.signExtend(nonextendedRHS) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 1, low: 0))) + + // Test non-overflow of `low.low` + // + nonextendedLHS = (high: 0, low: 1) + nonextendedRHS = (high: 0, low: Scalar.Magnitude.max - 1) + result = Complex.add(nonextendedLHS, nonextendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: Scalar.Magnitude.max))) + + extendedLHS = Complex.signExtend(nonextendedLHS) + extendedRHS = Complex.signExtend(nonextendedRHS) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: Scalar.Magnitude.max))) + + // Test overflow of `low.high` + // + nonextendedLHS = (high: 1, low: 0) + nonextendedRHS = (high: ~Scalar.zero, low: Scalar.Magnitude.max) + result = Complex.add(nonextendedLHS, nonextendedRHS) + CTAssertEqual(result, (high: (high: 0, low: Scalar.isSigned ? 0 : 1), low: (high: 0, low: Scalar.Magnitude.max))) + + extendedLHS = Complex.signExtend(nonextendedLHS) + extendedRHS = Complex.signExtend(nonextendedRHS) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: Scalar.isSigned ? 0 : 1), low: (high: 0, low: Scalar.Magnitude.max))) + + // Test non-overflow of `low.high` + // + nonextendedLHS = (high: 1, low: 0) + nonextendedRHS = (high: ~Scalar.zero - 1, low: 0) + result = Complex.add(nonextendedLHS, nonextendedRHS) + CTAssertEqual(result, (high: (high: Scalar.isSigned ? ~Scalar.zero : 0, low: Scalar.isSigned ? ~Scalar.Magnitude.zero : 0), low: (high: ~Scalar.Magnitude.zero, low: 0))) + + extendedLHS = Complex.signExtend(nonextendedLHS) + extendedRHS = Complex.signExtend(nonextendedRHS) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: Scalar.isSigned ? ~Scalar.zero : 0, low: Scalar.isSigned ? ~Scalar.Magnitude.zero : 0), low: (high: ~Scalar.Magnitude.zero, low: 0))) + + // Test overflow of `low.high` from overflow of `low.low` + // + nonextendedLHS = (high: 1, low: 1) + nonextendedRHS = (high: ~Scalar.zero - 1, low: Scalar.Magnitude.max) + result = Complex.add(nonextendedLHS, nonextendedRHS) + CTAssertEqual(result, (high: (high: 0, low: Scalar.isSigned ? 0 : 1), low: (high: 0, low: 0))) + + extendedLHS = Complex.signExtend(nonextendedLHS) + extendedRHS = Complex.signExtend(nonextendedRHS) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: Scalar.isSigned ? 0 : 1), low: (high: 0, low: 0))) + + // Test overflow of `high.low` + // + extendedLHS = (high: (high: 0, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: 0, low: Scalar.Magnitude.max), (high: 0, low: 0)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 1, low: 0), low: (high: 1, low: 1))) + + // Test non-overflow of `high.low` + // + extendedLHS = (high: (high: 0, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: 0, low: Scalar.Magnitude.max - 1), (high: 0, low: 0)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: Scalar.Magnitude.max), low: (high: 1, low: 1))) + + // Test overflow of `high.low` from overflow of `low.high` + // + extendedLHS = (high: (high: 0, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: 0, low: Scalar.Magnitude.max - 1), (high: Scalar.Magnitude.max, low: 0)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 1, low: 0), low: (high: 0, low: 1))) + + // Test overflow of `high.low` from overflow of `low.high` from overflow of `low.low` + // + extendedLHS = (high: (high: 0, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: 0, low: Scalar.Magnitude.max - 1), (high: Scalar.Magnitude.max - 1, low: Scalar.Magnitude.max)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 1, low: 0), low: (high: 0, low: 0))) + + // Test overflow of `high.high` + // + extendedLHS = (high: (high: 1, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: ~Scalar.zero, low: 0), (high: 0, low: 0)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 1), low: (high: 1, low: 1))) + + // Test non-overflow of `high.high` + // + extendedLHS = (high: (high: 1, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: ~Scalar.zero - 1, low: 0), (high: 0, low: 0)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: ~Scalar.zero, low: 1), low: (high: 1, low: 1))) + + // Test overflow of `high.high` from overflow of `high.low` + // + extendedLHS = (high: (high: 1, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: ~Scalar.zero - 1, low: Scalar.Magnitude.max), (high: 0, low: 0)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 1, low: 1))) + + // Test overflow of `high.high` from overflow of `high.low` from overflow of `low.high` + // + extendedLHS = (high: (high: 1, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: ~Scalar.zero - 1, low: Scalar.Magnitude.max - 1), (high: Scalar.Magnitude.max, low: 0)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 1))) + + // Test overflow of `high.high` from overflow of `high.low` from overflow of `low.high` from overflow of `low.low` + // + extendedLHS = (high: (high: 1, low: 1), (high: 1, low: 1)) + extendedRHS = (high: (high: ~Scalar.zero - 1, low: Scalar.Magnitude.max - 1), (high: Scalar.Magnitude.max - 1, low: Scalar.Magnitude.max)) + result = Complex.add(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + } + + private func testSubtractExtendedScalars(forType: Scalar.Type) where Scalar: FixedWidthInteger { + // Test x - 0 = x + // + var nonextendedLHS: (high: Scalar, low: Scalar.Magnitude) = (3, Scalar.Magnitude.max) + var nonextendedRHS: (high: Scalar, low: Scalar.Magnitude) = (0, 0) + var result = Complex.subtract(nonextendedLHS, nonextendedRHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 3, low: Scalar.Magnitude.max))) + + var extendedLHS: Complex.ExtendedScalar = ((1, Scalar.Magnitude.max &>> 1), (3, Scalar.Magnitude.max)) + var extendedRHS: Complex.ExtendedScalar = ((0, 0), (0, 0)) + result = Complex.subtract(extendedLHS, extendedRHS) + CTAssertEqual(result, (high: (high: 1, low: Scalar.Magnitude.max &>> 1), low: (high: 3, low: Scalar.Magnitude.max))) + + if Scalar.isSigned { + // Test 0 - x = -x + // + nonextendedLHS = (0, 0) + nonextendedRHS = (3, Scalar.Magnitude.max) + result = Complex.twosComplement(of: Complex.subtract(nonextendedLHS, nonextendedRHS)) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 3, low: Scalar.Magnitude.max))) + + extendedLHS = ((0, 0), (0, 0)) + extendedRHS = ((1, Scalar.Magnitude.max &>> 1), (3, Scalar.Magnitude.max)) + result = Complex.twosComplement(of: Complex.subtract(extendedLHS, extendedRHS)) + CTAssertEqual(result, (high: (high: 1, low: Scalar.Magnitude.max &>> 1), low: (high: 3, low: Scalar.Magnitude.max))) + } + + // Test x - x = 0 + // + nonextendedLHS = (3, Scalar.Magnitude.max) + result = Complex.subtract(nonextendedLHS, nonextendedLHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + + extendedLHS = ((1, Scalar.Magnitude.max >> 1), (3, Scalar.Magnitude.max)) + result = Complex.subtract(extendedLHS, extendedLHS) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + } + + private func testLeftShiftExtendedScalars(forType: Scalar.Type) where Scalar: FixedWidthInteger { + let upperHalf = (~Scalar.Magnitude.zero) &<< (Scalar.Magnitude.bitWidth / 2) + let lowerHalf = (~Scalar.Magnitude.zero) &>> (Scalar.Magnitude.bitWidth / 2) + let shiftedUpperHalf = (upperHalf &<< 1) + 1 + + var value: Complex.ExtendedScalar = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + var result = Complex.leftShift(value, by: 1) + CTAssertEqual(result, (high: (high: ~Scalar.zero, low: Scalar.Magnitude.max), low: (high: Scalar.Magnitude.max, low: Scalar.Magnitude.max - 1))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.leftShift(value, by: Scalar.Magnitude(Scalar.bitWidth)) + CTAssertEqual(result, (high: (high: ~Scalar.zero, low: Scalar.Magnitude.max), low: (high: Scalar.Magnitude.max, low: 0))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.leftShift(value, by: Scalar.Magnitude(Scalar.bitWidth / 2)) + CTAssertEqual(result, (high: (high: ~Scalar.zero, low: Scalar.Magnitude.max), low: (high: Scalar.Magnitude.max, low: upperHalf))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.leftShift(value, by: Scalar.Magnitude(Scalar.bitWidth * 2)) + CTAssertEqual(result, (high: (high: ~Scalar.zero, low: Scalar.Magnitude.max), low: (high: 0, low: 0))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.leftShift(value, by: Scalar.Magnitude(Scalar.bitWidth * 3)) + CTAssertEqual(result, (high: (high: ~Scalar.zero, low: 0), low: (high: 0, low: 0))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.leftShift(value, by: Scalar.Magnitude(Scalar.bitWidth * 4)) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + + value = ((Scalar(truncatingIfNeeded: upperHalf), upperHalf), (upperHalf, upperHalf)) + result = Complex.leftShift(value, by: Scalar.Magnitude(Scalar.bitWidth / 2)) + CTAssertEqual(result, (high: (high: Scalar(truncatingIfNeeded: lowerHalf), low: lowerHalf), low: (high: lowerHalf, low: 0))) + + value = ((Scalar(truncatingIfNeeded: lowerHalf), lowerHalf), (lowerHalf, lowerHalf)) + result = Complex.leftShift(value, by: Scalar.Magnitude(Scalar.bitWidth / 2)) + CTAssertEqual(result, (high: (high: Scalar(truncatingIfNeeded: upperHalf), low: upperHalf), low: (high: upperHalf, low: upperHalf))) + + value = ((Scalar(truncatingIfNeeded: upperHalf), upperHalf), (upperHalf, upperHalf)) + result = Complex.leftShift(value, by: 1) + CTAssertEqual(result, (high: (high: Scalar(truncatingIfNeeded: shiftedUpperHalf), low: shiftedUpperHalf), low: (high: shiftedUpperHalf, low: upperHalf &<< 1))) + } + + private func testRightShiftExtendedScalars(forType: Scalar.Type) where Scalar: FixedWidthInteger { + let upperHalf = (~Scalar.Magnitude.zero) &<< (Scalar.Magnitude.bitWidth / 2) + let lowerHalf = (~Scalar.Magnitude.zero) &>> (Scalar.Magnitude.bitWidth / 2) + let shiftedLowerHalf = (lowerHalf &>> 1) | upperHalf &<< ((Scalar.Magnitude.bitWidth / 2) - 1) + + var value: Complex.ExtendedScalar = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + var result = Complex.rightShift(value, by: 1) + CTAssertEqual(result, (high: (high: Scalar.max &>> (Scalar.isSigned ? 0 : 1), low: Scalar.Magnitude.max), low: (high: Scalar.Magnitude.max, low: Scalar.Magnitude.max))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.rightShift(value, by: Scalar.Magnitude(Scalar.bitWidth)) + CTAssertEqual(result, (high: (high: 0, low: Scalar.Magnitude.max), low: (high: Scalar.Magnitude.max, low: Scalar.Magnitude.max))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.rightShift(value, by: Scalar.Magnitude(Scalar.bitWidth / 2)) + CTAssertEqual(result, (high: (high: Scalar(truncatingIfNeeded: lowerHalf), low: Scalar.Magnitude.max), low: (high: Scalar.Magnitude.max, low: Scalar.Magnitude.max))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.rightShift(value, by: Scalar.Magnitude(Scalar.bitWidth * 2)) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: Scalar.Magnitude.max, low: Scalar.Magnitude.max))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.rightShift(value, by: Scalar.Magnitude(Scalar.bitWidth * 3)) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: Scalar.Magnitude.max))) + + value = ((~Scalar.zero, Scalar.Magnitude.max), (Scalar.Magnitude.max, Scalar.Magnitude.max)) + result = Complex.rightShift(value, by: Scalar.Magnitude(Scalar.bitWidth * 4)) + CTAssertEqual(result, (high: (high: 0, low: 0), low: (high: 0, low: 0))) + + value = ((Scalar(truncatingIfNeeded: upperHalf), upperHalf), (upperHalf, upperHalf)) + result = Complex.rightShift(value, by: Scalar.Magnitude(Scalar.bitWidth / 2)) + CTAssertEqual(result, (high: (high: Scalar(truncatingIfNeeded: lowerHalf), low: lowerHalf), low: (high: lowerHalf, low: lowerHalf))) + + value = ((Scalar(truncatingIfNeeded: lowerHalf), lowerHalf), (lowerHalf, lowerHalf)) + result = Complex.rightShift(value, by: Scalar.Magnitude(Scalar.bitWidth / 2)) + CTAssertEqual(result, (high: (high: 0, low: upperHalf), low: (high: upperHalf, low: upperHalf))) + + value = ((Scalar(truncatingIfNeeded: lowerHalf), lowerHalf), (lowerHalf, lowerHalf)) + result = Complex.rightShift(value, by: 1) + CTAssertEqual(result, (high: (high: Scalar(truncatingIfNeeded: lowerHalf &>> 1), low: shiftedLowerHalf), low: (high: shiftedLowerHalf, low: shiftedLowerHalf))) + } + + private func testIsLessThanOrEqual(forType: Scalar.Type) where Scalar: FixedWidthInteger { + CTAssertTrue(Complex.isLessThanOrEqual(((1, 1), (1, 1)), to: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLessThanOrEqual(((0, 1), (1, 1)), to: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLessThanOrEqual(((1, 0), (1, 1)), to: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLessThanOrEqual(((1, 1), (0, 1)), to: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLessThanOrEqual(((1, 1), (1, 0)), to: ((1, 1), (1, 1)))) + CTAssertFalse(Complex.isLessThanOrEqual(((1, 1), (1, 1)), to: ((0, 1), (1, 1)))) + CTAssertFalse(Complex.isLessThanOrEqual(((1, 1), (1, 1)), to: ((1, 0), (1, 1)))) + CTAssertFalse(Complex.isLessThanOrEqual(((1, 1), (1, 1)), to: ((1, 1), (0, 1)))) + CTAssertFalse(Complex.isLessThanOrEqual(((1, 1), (1, 1)), to: ((1, 1), (1, 0)))) + + if Scalar.isSigned { + CTAssertTrue(Complex.isLessThanOrEqual(((~0, ~0), (~0, ~0)), to: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLessThanOrEqual(((~0 - 1, ~0), (~0, ~0)), to: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLessThanOrEqual(((~0, ~0 - 1), (~0, ~0)), to: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLessThanOrEqual(((~0, ~0), (~0 - 1, ~0)), to: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLessThanOrEqual(((~0, ~0), (~0, ~0 - 1)), to: ((~0, ~0), (~0, ~0)))) + CTAssertFalse(Complex.isLessThanOrEqual(((~0, ~0), (~0, ~0)), to: ((~0 - 1, ~0), (~0, ~0)))) + CTAssertFalse(Complex.isLessThanOrEqual(((~0, ~0), (~0, ~0)), to: ((~0, ~0 - 1), (~0, ~0)))) + CTAssertFalse(Complex.isLessThanOrEqual(((~0, ~0), (~0, ~0)), to: ((~0, ~0), (~0 - 1, ~0)))) + CTAssertFalse(Complex.isLessThanOrEqual(((~0, ~0), (~0, ~0)), to: ((~0, ~0), (~0, ~0 - 1)))) + } + } + + private func testIsLessThan(forType: Scalar.Type) where Scalar: FixedWidthInteger { + CTAssertFalse(Complex.isLess(((1, 1), (1, 1)), than: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLess(((0, 1), (1, 1)), than: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLess(((1, 0), (1, 1)), than: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLess(((1, 1), (0, 1)), than: ((1, 1), (1, 1)))) + CTAssertTrue(Complex.isLess(((1, 1), (1, 0)), than: ((1, 1), (1, 1)))) + CTAssertFalse(Complex.isLess(((1, 1), (1, 1)), than: ((0, 1), (1, 1)))) + CTAssertFalse(Complex.isLess(((1, 1), (1, 1)), than: ((1, 0), (1, 1)))) + CTAssertFalse(Complex.isLess(((1, 1), (1, 1)), than: ((1, 1), (0, 1)))) + CTAssertFalse(Complex.isLess(((1, 1), (1, 1)), than: ((1, 1), (1, 0)))) + + if Scalar.isSigned { + CTAssertFalse(Complex.isLess(((~0, ~0), (~0, ~0)), than: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLess(((~0 - 1, ~0), (~0, ~0)), than: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLess(((~0, ~0 - 1), (~0, ~0)), than: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLess(((~0, ~0), (~0 - 1, ~0)), than: ((~0, ~0), (~0, ~0)))) + CTAssertTrue(Complex.isLess(((~0, ~0), (~0, ~0 - 1)), than: ((~0, ~0), (~0, ~0)))) + CTAssertFalse(Complex.isLess(((~0, ~0), (~0, ~0)), than: ((~0 - 1, ~0), (~0, ~0)))) + CTAssertFalse(Complex.isLess(((~0, ~0), (~0, ~0)), than: ((~0, ~0 - 1), (~0, ~0)))) + CTAssertFalse(Complex.isLess(((~0, ~0), (~0, ~0)), than: ((~0, ~0), (~0 - 1, ~0)))) + CTAssertFalse(Complex.isLess(((~0, ~0), (~0, ~0)), than: ((~0, ~0), (~0, ~0 - 1)))) + } + } + + private func testIsPowerOfTwo(forType: Scalar.Type) where Scalar: FixedWidthInteger { + var value: Complex.ExtendedScalar = ((0, 0), (0, 1)) + let extendedBitWidth = Scalar.bitWidth * 4 + + for i in 0 ..< extendedBitWidth { + let result = Complex.isPowerOfTwo(value) + value = Complex.leftShift(value, by: 1) + + CTAssertTrue(result.isPowerOfTwo) + CTAssertEqual(result.power, Scalar.Magnitude(i)) + } + + value = ((0, 0), (0, 2)) + + for _ in 1 ..< extendedBitWidth { + var testValue = value + testValue.low.low |= 1 + + let result = Complex.isPowerOfTwo(testValue) + value = Complex.leftShift(value, by: 1) + + CTAssertFalse(result.isPowerOfTwo) + CTAssertEqual(result.power, 0) + } + } + + private func testLeadingZeroBitCount(forType: Scalar.Type) where Scalar: FixedWidthInteger { + var value: Complex.ExtendedScalar = ((~0, ~0), (~0, ~0)) + let extendedBitWidth = Scalar.bitWidth * 4 + + for i in 0 ..< extendedBitWidth { + CTAssertEqual(Complex.leadingZeroBitCount(of: value), i) + value = Complex.rightShift(value, by: 1) + } + + value = ((0, 0), (0, 1)) + + for i in 1 ..< extendedBitWidth { + CTAssertEqual(Complex.leadingZeroBitCount(of: value), extendedBitWidth - i) + value = Complex.leftShift(value, by: 1) + } + } + + private func testTruncateExtendedScalars(forType: Scalar.Type) where Scalar: FixedWidthInteger { + let randomValue: () -> Scalar.Magnitude = { .random(in: .min ... .max) } + + var result = Complex.truncateAndReportOverflow(((0, 0), (0, 0))) + CTAssertEqual(result.partialValue, 0) + CTAssertFalse(result.overflow) + + result = Complex.truncateAndReportOverflow(((0, 0), (0, Scalar.Magnitude(truncatingIfNeeded: Scalar.max)))) + CTAssertEqual(result.partialValue, Scalar.max) + CTAssertFalse(result.overflow) + + result = Complex.truncateAndReportOverflow(((0, 0), (0, Scalar.Magnitude(truncatingIfNeeded: Scalar.min)))) + CTAssertEqual(result.partialValue, Scalar.min) + CTAssertEqual(result.overflow, Scalar.isSigned) + + var value: Scalar.Magnitude + for _ in 0 ..< 64 { + value = randomValue() + result = Complex.truncateAndReportOverflow(((0, 0), (1, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + + value = randomValue() + result = Complex.truncateAndReportOverflow(((0, 1), (0, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + + value = randomValue() + result = Complex.truncateAndReportOverflow(((1, 0), (0, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + } + + if Scalar.isSigned { + value = randomValue() | (~Scalar.Magnitude.zero &<< (Scalar.bitWidth - 1)) // random value with the MSB set to 1 + result = Complex.truncateAndReportOverflow(((0, 0), (0, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + + value = Scalar.Magnitude.max &>> 1 + result = Complex.truncateAndReportOverflow(((~0, ~0), (~0, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + + value = randomValue() + result = Complex.truncateAndReportOverflow(((~0, ~0), (~0 - 1, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + + value = randomValue() + result = Complex.truncateAndReportOverflow(((~0, ~0 - 1), (~0, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + + value = randomValue() + result = Complex.truncateAndReportOverflow(((~0 - 1, ~0), (~0, value))) + CTAssertEqual(result.partialValue, Scalar(truncatingIfNeeded: value)) + CTAssertTrue(result.overflow) + + result = Complex.truncateAndReportOverflow(((~0, ~0), (~0, 1 << (Scalar.bitWidth - 1)))) + CTAssertEqual(result.partialValue, Scalar.min) + CTAssertFalse(result.overflow) + } + } + + private func testSlowpathMultiply(forType: Scalar.Type) where Scalar: FixedWidthInteger { + let value: Complex.ExtendedScalar = ((1, 1), (1, 1)) + let two: Complex.ExtendedScalar = ((0, 0), (0, 2)) + + var result = Complex.slowpathMultiply(two, value) + CTAssertEqual(result, Complex.leftShift(value, by: 1)) + + result = Complex.slowpathMultiply(value, two) + CTAssertEqual(result, Complex.leftShift(value, by: 1)) + + result = Complex.slowpathMultiply(Scalar(2), value) + CTAssertEqual(result, Complex.leftShift(value, by: 1)) + + result = Complex.slowpathMultiply(Scalar(0), value) + CTAssertEqual(result, ((0, 0), (0, 0))) + + result = Complex.slowpathMultiply(((0, 0), (0, 0)), value) + CTAssertEqual(result, ((0, 0), (0, 0))) + + result = Complex.slowpathMultiply(Scalar(1), value) + CTAssertEqual(result, value) + + result = Complex.slowpathMultiply(((0, 0), (0, 1)), value) + CTAssertEqual(result, value) + + result = Complex.slowpathMultiply(value, ((0, 0), (0, 1))) + CTAssertEqual(result, value) + + result = Complex.slowpathMultiply(value, ((0, 0), (0, ~0))) + CTAssertEqual(result, ((~0, ~0), (~0, ~0))) + + result = Complex.slowpathMultiply(((0, 0), (0, ~0)), value) + CTAssertEqual(result, ((~0, ~0), (~0, ~0))) + + if Scalar.isSigned { + result = Complex.slowpathMultiply(Scalar(-2), value) + CTAssertEqual(result, Complex.twosComplement(of: Complex.leftShift(value, by: 1))) + + result = Complex.slowpathMultiply(Complex.twosComplement(of: two), value) + CTAssertEqual(result, Complex.twosComplement(of: Complex.leftShift(value, by: 1))) + + result = Complex.slowpathMultiply(value, Complex.twosComplement(of: two)) + CTAssertEqual(result, Complex.twosComplement(of: Complex.leftShift(value, by: 1))) + + result = Complex.slowpathMultiply(Scalar(-1), value) + CTAssertEqual(result, Complex.twosComplement(of: value)) + + result = Complex.slowpathMultiply(((~0, ~0), (~0, ~0)), value) + CTAssertEqual(result, Complex.twosComplement(of: value)) + + result = Complex.slowpathMultiply(value, ((~0, ~0), (~0, ~0))) + CTAssertEqual(result, Complex.twosComplement(of: value)) + + result = Complex.slowpathMultiply(Scalar(-1), Complex.twosComplement(of: value)) + CTAssertEqual(result, value) + + result = Complex.slowpathMultiply(((~0, ~0), (~0, ~0)), Complex.twosComplement(of: value)) + CTAssertEqual(result, value) + + result = Complex.slowpathMultiply(Complex.twosComplement(of: value), ((~0, ~0), (~0, ~0))) + CTAssertEqual(result, value) + } + } + + private func testSlowpathDivide(forType: Scalar.Type) where Scalar: FixedWidthInteger { + var result = Complex.slowpathDivide(((0, 0), (0, 100)), ((0, 0), (0, 10))) // 100 / 10 = 10 r0 + CTAssertEqual(result.quotient, ((0, 0), (0, 10))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + result = Complex.slowpathDivide(((0, 0), (0, 101)), ((0, 0), (0, 10))) // 101 / 10 = 10 r1 + CTAssertEqual(result.quotient, ((0, 0), (0, 10))) + CTAssertEqual(result.remainder, ((0, 0), (0, 1))) + + result = Complex.slowpathDivide(((0, 0), (0, 109)), ((0, 0), (0, 10))) // 109 / 10 = 10 r9 + CTAssertEqual(result.quotient, ((0, 0), (0, 10))) + CTAssertEqual(result.remainder, ((0, 0), (0, 9))) + + result = Complex.slowpathDivide(((0, 0), (0, 100)), ((0, 0), (0, 2))) // 100 / 2 = 50 r0 + CTAssertEqual(result.quotient, ((0, 0), (0, 50))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + result = Complex.slowpathDivide(((0, 0), (0, 101)), ((0, 0), (0, 2))) // 101 / 2 = 50 r1 + CTAssertEqual(result.quotient, ((0, 0), (0, 50))) + CTAssertEqual(result.remainder, ((0, 0), (0, 1))) + + result = Complex.slowpathDivide(((0, 0), (0, 109)), ((0, 0), (0, 2))) // 101 / 2 = 54 r1 + CTAssertEqual(result.quotient, ((0, 0), (0, 54))) + CTAssertEqual(result.remainder, ((0, 0), (0, 1))) + + result = Complex.slowpathDivide(((0, 0), (0, 109)), ((0, 0), (0, 2))) // 101 / 2 = 54 r1 + CTAssertEqual(result.quotient, ((0, 0), (0, 54))) + CTAssertEqual(result.remainder, ((0, 0), (0, 1))) + + result = Complex.slowpathDivide(((0, ~0), (~0, ~0)), ((0, 0), (0, ~0))) + CTAssertEqual(result.quotient, ((0, 1), (1, 1))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + result = Complex.slowpathDivide(((0, 0), (0, ~0)), ((0, 0), (~0, ~0))) + CTAssertEqual(result.quotient, ((0, 0), (0, 0))) + CTAssertEqual(result.remainder, ((0, 0), (0, ~0))) + + if Scalar.isSigned { + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 100))), ((0, 0), (0, 10))) // -100 / 10 = -10 r0 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 10)))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 100))), Complex.twosComplement(of: ((0, 0), (0, 10)))) // -100 / -10 = 10 r0 + CTAssertEqual(result.quotient, ((0, 0), (0, 10))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + result = Complex.slowpathDivide(((0, 0), (0, 100)), Complex.twosComplement(of: ((0, 0), (0, 10)))) // 100 / -10 = -10 r0 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 10)))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + // + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 101))), ((0, 0), (0, 10))) // -101 / 10 = -10 r-1 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 10)))) + CTAssertEqual(result.remainder, ((~0, ~0), (~0, ~0))) + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 101))), Complex.twosComplement(of: ((0, 0), (0, 10)))) // -101 / -10 = 10 r-1 + CTAssertEqual(result.quotient, ((0, 0), (0, 10))) + CTAssertEqual(result.remainder, ((~0, ~0), (~0, ~0))) + + result = Complex.slowpathDivide(((0, 0), (0, 101)), Complex.twosComplement(of: ((0, 0), (0, 10)))) // 101 / -10 = -10 r1 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 10)))) + CTAssertEqual(result.remainder, ((0, 0), (0, 1))) + + // + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 109))), ((0, 0), (0, 10))) // -109 / 10 = -10 r-9 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 10)))) + CTAssertEqual(result.remainder, Complex.twosComplement(of: ((0, 0), (0, 9)))) + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 109))), Complex.twosComplement(of: ((0, 0), (0, 10)))) // -109 / -10 = 10 r-9 + CTAssertEqual(result.quotient, ((0, 0), (0, 10))) + CTAssertEqual(result.remainder, Complex.twosComplement(of: ((0, 0), (0, 9)))) + + result = Complex.slowpathDivide(((0, 0), (0, 109)), Complex.twosComplement(of: ((0, 0), (0, 10)))) // 109 / -10 = -10 r9 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 10)))) + CTAssertEqual(result.remainder, ((0, 0), (0, 9))) + + // + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 100))), ((0, 0), (0, 2))) // -100 / 2 = -50 r0 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 50)))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + result = Complex.slowpathDivide(((0, 0), (0, 100)), ((0, 0), (0, 2))) // -100 / -2 = 50 r0 + CTAssertEqual(result.quotient, ((0, 0), (0, 50))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + result = Complex.slowpathDivide(((0, 0), (0, 100)), Complex.twosComplement(of: ((0, 0), (0, 2)))) // 100 / -2 = -50 r0 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 50)))) + CTAssertEqual(result.remainder, ((0, 0), (0, 0))) + + // + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 101))), ((0, 0), (0, 2))) // -101 / 2 = -50 r-1 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 50)))) + CTAssertEqual(result.remainder, ((~0, ~0), (~0, ~0))) + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, 101))), Complex.twosComplement(of: ((0, 0), (0, 2)))) // -101 / -2 = 50 r-1 + CTAssertEqual(result.quotient, ((0, 0), (0, 50))) + CTAssertEqual(result.remainder, ((~0, ~0), (~0, ~0))) + + result = Complex.slowpathDivide(((0, 0), (0, 101)), Complex.twosComplement(of: ((0, 0), (0, 2)))) // 101 / -2 = -50 r1 + CTAssertEqual(result.quotient, Complex.twosComplement(of: ((0, 0), (0, 50)))) + CTAssertEqual(result.remainder, ((0, 0), (0, 1))) + + // + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, ~0))), ((0, 0), (~0, ~0))) + CTAssertEqual(result.quotient, ((0, 0), (0, 0))) + CTAssertEqual(result.remainder, Complex.twosComplement(of: ((0, 0), (0, ~0)))) + + result = Complex.slowpathDivide(Complex.twosComplement(of: ((0, 0), (0, ~0))), Complex.twosComplement(of: ((0, 0), (~0, ~0)))) + CTAssertEqual(result.quotient, ((0, 0), (0, 0))) + CTAssertEqual(result.remainder, Complex.twosComplement(of: ((0, 0), (0, ~0)))) + + result = Complex.slowpathDivide(((0, 0), (0, ~0)), Complex.twosComplement(of: ((0, 0), (~0, ~0)))) + CTAssertEqual(result.quotient, ((0, 0), (0, 0))) + CTAssertEqual(result.remainder, ((0, 0), (0, ~0))) + } + } +} diff --git a/Tests/ComplexTests/TestingBase.swift b/Tests/ComplexTests/TestingBase.swift index 5c6b5d4..7bc8032 100644 --- a/Tests/ComplexTests/TestingBase.swift +++ b/Tests/ComplexTests/TestingBase.swift @@ -11,7 +11,7 @@ import XCTest // MARK: Internal Methods internal func CTAssertEqual(_ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", function: String = #function, file: StaticString = #file, line: UInt = #line) where T: Equatable { - XCTAssertEqual(try expression1(), try expression2(), "\(testMethodDescription(forType: T.self, function: function))\(message().isEmpty ? "" : ": \(message())")", file: file, line: line) + CTAssertEqual(try expression1(), try expression2(), message(), function: function, type: T.self, file: file, line: line) } internal func CTAssertTrue(_ expression: @autoclosure () throws -> Bool, _ message: @autoclosure () -> String = "", function: String = #function, file: StaticString = #file, line: UInt = #line) { @@ -27,8 +27,24 @@ internal func CTAssertEqual(_ expression1: @autoclosure () throws -> Complex< CTAssertTrue(difference.real <= accuracy && difference.imaginary <= accuracy, message(), function: function, file: file, line: line) } +internal func CTAssertEqual(_ expression1: @autoclosure () -> Complex.ExtendedScalar, _ expression2: @autoclosure () -> Complex.ExtendedScalar, _ message: @autoclosure () -> String = "", function: String = #function, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger { + let lhs = expression1() + let rhs = expression2() + + CTAssertEqual(lhs.high.high, rhs.high.high, "ExtendedScalar<\(String(describing: Scalar.self))>.high.high\(message().isEmpty ? "" : ": \(message())")", function: function, type: Scalar.self, file: file, line: line) + CTAssertEqual(lhs.high.low, rhs.high.low, "ExtendedScalar<\(String(describing: Scalar.self))>.high.low\(message().isEmpty ? "" : ": \(message())")", function: function, type: Scalar.self, file: file, line: line) + CTAssertEqual(lhs.low.high, rhs.low.high, "ExtendedScalar<\(String(describing: Scalar.self))>.low.high\(message().isEmpty ? "" : ": \(message())")", function: function, type: Scalar.self, file: file, line: line) + CTAssertEqual(lhs.low.low, rhs.low.low, "ExtendedScalar<\(String(describing: Scalar.self))>.low.low\(message().isEmpty ? "" : ": \(message())")", function: function, type: Scalar.self, file: file, line: line) +} + // MARK: - Private Methods +//swiftlint:disable function_default_parameter_at_end +private func CTAssertEqual(_ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", function: String = #function, type: Scalar.Type, file: StaticString = #file, line: UInt = #line) where T: Equatable { + XCTAssertEqual(try expression1(), try expression2(), "\(testMethodDescription(forType: Scalar.self, function: function))\(message().isEmpty ? "" : ": \(message())")", file: file, line: line) +} +//swiftlint:enable function_default_parameter_at_end + private func testMethodDescription(forType type: Scalar.Type, function: String = #function) -> String { let description: String if let index = function.firstIndex(of: "(") {