Skip to content

Commit 485927e

Browse files
author
Joe Newton
committed
Added arithmetic operations that ignore overflow for Complex where the scalar type conforms to FixedWidthInteger
1 parent 9895bd3 commit 485927e

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed

Sources/Complex/ComplexArithmetic.swift

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,87 @@ extension Complex where Scalar: SignedNumeric {
103103
}
104104
}
105105

106+
extension Complex where Scalar: FixedWidthInteger {
107+
108+
@_transparent
109+
public static func &+ (lhs: Complex<Scalar>, rhs: Complex<Scalar>) -> Complex<Scalar> {
110+
return Complex<Scalar>(real: lhs.real &+ rhs.real, imaginary: lhs.imaginary &+ rhs.imaginary)
111+
}
112+
113+
@_transparent
114+
public static func &+= (lhs: inout Complex<Scalar>, rhs: Complex<Scalar>) {
115+
lhs = lhs &+ rhs
116+
}
117+
118+
@_transparent
119+
public static func &- (lhs: Complex<Scalar>, rhs: Complex<Scalar>) -> Complex<Scalar> {
120+
return Complex<Scalar>(real: lhs.real &- rhs.real, imaginary: lhs.imaginary &- rhs.imaginary)
121+
}
122+
123+
@_transparent
124+
public static func &-= (lhs: inout Complex<Scalar>, rhs: Complex<Scalar>) {
125+
lhs = lhs &- rhs
126+
}
127+
128+
//
129+
130+
@_transparent
131+
public static func &+ (lhs: Complex<Scalar>, rhs: Scalar) -> Complex<Scalar> {
132+
return Complex<Scalar>(real: lhs.real &+ rhs, imaginary: lhs.imaginary)
133+
}
134+
135+
@_transparent
136+
public static func &+ (lhs: Scalar, rhs: Complex<Scalar>) -> Complex<Scalar> {
137+
return Complex<Scalar>(real: lhs &+ rhs.real, imaginary: rhs.imaginary)
138+
}
139+
140+
@_transparent
141+
public static func &+= (lhs: inout Complex<Scalar>, rhs: Scalar) {
142+
lhs = lhs &+ rhs
143+
}
144+
145+
//
146+
147+
@inlinable @inline(__always)
148+
public static func .&+ (lhs: Complex<Scalar>, rhs: Complex<Scalar>) -> Complex<Scalar> {
149+
return lhs &+ rhs
150+
}
151+
152+
@inlinable @inline(__always)
153+
public static func .&+= (lhs: inout Complex<Scalar>, rhs: Complex<Scalar>) {
154+
lhs = lhs .&+ rhs
155+
}
156+
157+
//
158+
159+
@_transparent
160+
public static func &- (lhs: Complex<Scalar>, rhs: Scalar) -> Complex<Scalar> {
161+
return Complex<Scalar>(real: lhs.real &- rhs, imaginary: lhs.imaginary)
162+
}
163+
164+
@_transparent
165+
public static func &- (lhs: Scalar, rhs: Complex<Scalar>) -> Complex<Scalar> {
166+
return Complex<Scalar>(real: lhs &- rhs.real, imaginary: .zero &- rhs.imaginary)
167+
}
168+
169+
@_transparent
170+
public static func &-= (lhs: inout Complex<Scalar>, rhs: Scalar) {
171+
lhs = lhs &- rhs
172+
}
173+
174+
//
175+
176+
@inlinable @inline(__always)
177+
public static func .&- (lhs: Complex<Scalar>, rhs: Complex<Scalar>) -> Complex<Scalar> {
178+
return lhs &- rhs
179+
}
180+
181+
@inlinable @inline(__always)
182+
public static func .&-= (lhs: inout Complex<Scalar>, rhs: Complex<Scalar>) {
183+
lhs = lhs .&- rhs
184+
}
185+
}
186+
106187
// MARK: - Multiplication
107188

108189
extension Complex {
@@ -150,6 +231,51 @@ extension Complex {
150231
}
151232
}
152233

234+
extension Complex where Scalar: FixedWidthInteger {
235+
236+
@_transparent
237+
public static func &* (lhs: Complex<Scalar>, rhs: Complex<Scalar>) -> Complex<Scalar> {
238+
let real = (lhs.real &* rhs.real) &- (lhs.imaginary &* rhs.imaginary)
239+
let imaginary = (lhs.real &* rhs.imaginary) &+ (lhs.imaginary &* rhs.real)
240+
241+
return Complex<Scalar>(real: real, imaginary: imaginary)
242+
}
243+
244+
@_transparent
245+
public static func &*= (lhs: inout Complex<Scalar>, rhs: Complex<Scalar>) {
246+
lhs = lhs &* rhs
247+
}
248+
249+
//
250+
251+
@_transparent
252+
public static func &* (lhs: Complex<Scalar>, rhs: Scalar) -> Complex<Scalar> {
253+
return Complex<Scalar>(real: lhs.real &* rhs, imaginary: lhs.imaginary &* rhs)
254+
}
255+
256+
@_transparent
257+
public static func &* (lhs: Scalar, rhs: Complex<Scalar>) -> Complex<Scalar> {
258+
return Complex<Scalar>(real: lhs &* rhs.real, imaginary: lhs &* rhs.imaginary)
259+
}
260+
261+
@_transparent
262+
public static func &*= (lhs: inout Complex<Scalar>, rhs: Scalar) {
263+
lhs = lhs &* rhs
264+
}
265+
266+
//
267+
268+
@_transparent
269+
public static func .&* (lhs: Complex<Scalar>, rhs: Complex<Scalar>) -> Complex<Scalar> {
270+
return Complex<Scalar>(real: lhs.real &* rhs.real, imaginary: lhs.imaginary &* rhs.imaginary)
271+
}
272+
273+
@_transparent
274+
public static func .&*= (lhs: inout Complex<Scalar>, rhs: Complex<Scalar>) {
275+
lhs = lhs .&* rhs
276+
}
277+
}
278+
153279
// MARK: - Division (BinaryInteger)
154280

155281
extension Complex where Scalar: BinaryInteger {

Sources/Complex/Operators.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,26 @@ import Foundation
1111
infix operator .+: AdditionPrecedence
1212
infix operator .+=: AssignmentPrecedence
1313

14+
/// Component-wise addition, ignoring overflow
15+
infix operator .&+: AdditionPrecedence
16+
infix operator .&+=: AssignmentPrecedence
17+
1418
/// Component-wise subtraction
1519
infix operator .-: AdditionPrecedence
1620
infix operator .-=: AssignmentPrecedence
1721

22+
/// Component-wise subtraction, ignoring overflow
23+
infix operator .&-: AdditionPrecedence
24+
infix operator .&-=: AssignmentPrecedence
25+
1826
/// Component-wise multiplication
1927
infix operator .*: MultiplicationPrecedence
2028
infix operator .*=: AssignmentPrecedence
2129

30+
/// Component-wise multiplication, ignoring overflow
31+
infix operator .&*: MultiplicationPrecedence
32+
infix operator .&*=: AssignmentPrecedence
33+
2234
/// Component-wise division
2335
infix operator ./: MultiplicationPrecedence
2436
infix operator ./=: AssignmentPrecedence

Tests/ComplexTests/ComplexArithmeticTests.swift

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,19 @@ class ComplexArithmeticTests: XCTestCase {
9696
testAddition(Complex<Float80>(real: 1.0, imaginary: 2.0), 3.0, Complex<Float80>(real: 4.0, imaginary: 2.0))
9797
}
9898

99+
func testAdditionIgnoringOverflow() {
100+
testAdditionIgnoringOverflow(forType: Int8.self)
101+
testAdditionIgnoringOverflow(forType: Int16.self)
102+
testAdditionIgnoringOverflow(forType: Int32.self)
103+
testAdditionIgnoringOverflow(forType: Int64.self)
104+
testAdditionIgnoringOverflow(forType: Int.self)
105+
testAdditionIgnoringOverflow(forType: UInt8.self)
106+
testAdditionIgnoringOverflow(forType: UInt16.self)
107+
testAdditionIgnoringOverflow(forType: UInt32.self)
108+
testAdditionIgnoringOverflow(forType: UInt64.self)
109+
testAdditionIgnoringOverflow(forType: UInt.self)
110+
}
111+
99112
func testSubtraction() {
100113
testSubtraction(Complex<Int8>(real: 3, imaginary: 4), Complex<Int8>(real: 1, imaginary: 2), Complex<Int8>(real: 2, imaginary: 2))
101114
testSubtraction(Complex<Int16>(real: 3, imaginary: 4), Complex<Int16>(real: 1, imaginary: 2), Complex<Int16>(real: 2, imaginary: 2))
@@ -128,6 +141,19 @@ class ComplexArithmeticTests: XCTestCase {
128141
testSubtraction(Complex<Float80>(real: 3.0, imaginary: 4.0), 1.0, Complex<Float80>(real: 2.0, imaginary: 4.0))
129142
}
130143

144+
func testSubtractionIgnoringOverflow() {
145+
testSubtractionIgnoringOverflow(forType: Int8.self)
146+
testSubtractionIgnoringOverflow(forType: Int16.self)
147+
testSubtractionIgnoringOverflow(forType: Int32.self)
148+
testSubtractionIgnoringOverflow(forType: Int64.self)
149+
testSubtractionIgnoringOverflow(forType: Int.self)
150+
testSubtractionIgnoringOverflow(forType: UInt8.self)
151+
testSubtractionIgnoringOverflow(forType: UInt16.self)
152+
testSubtractionIgnoringOverflow(forType: UInt32.self)
153+
testSubtractionIgnoringOverflow(forType: UInt64.self)
154+
testSubtractionIgnoringOverflow(forType: UInt.self)
155+
}
156+
131157
func testMultiplication() {
132158
testMultiplication(Complex<Int8>(real: 3, imaginary: 4), Complex<Int8>(real: 1, imaginary: 2), Complex<Int8>(real: -5, imaginary: 10))
133159
testMultiplication(Complex<Int16>(real: 3, imaginary: 4), Complex<Int16>(real: 1, imaginary: 2), Complex<Int16>(real: -5, imaginary: 10))
@@ -160,6 +186,19 @@ class ComplexArithmeticTests: XCTestCase {
160186
testMultiplication(Complex<Float80>(real: 3.0, imaginary: 4.0), 2.0)
161187
}
162188

189+
func testMultiplicationIgnoringOverflow() {
190+
testMultiplicationIgnoringOverflow(forType: Int8.self)
191+
testMultiplicationIgnoringOverflow(forType: Int16.self)
192+
testMultiplicationIgnoringOverflow(forType: Int32.self)
193+
testMultiplicationIgnoringOverflow(forType: Int64.self)
194+
testMultiplicationIgnoringOverflow(forType: Int.self)
195+
testMultiplicationIgnoringOverflow(forType: UInt8.self)
196+
testMultiplicationIgnoringOverflow(forType: UInt16.self)
197+
testMultiplicationIgnoringOverflow(forType: UInt32.self)
198+
testMultiplicationIgnoringOverflow(forType: UInt64.self)
199+
testMultiplicationIgnoringOverflow(forType: UInt.self)
200+
}
201+
163202
func testComponentwiseMultiplication() {
164203
testComponentwiseMultiplication(Complex<Int8>(real: 3, imaginary: 4), Complex<Int8>(real: 1, imaginary: 2))
165204
testComponentwiseMultiplication(Complex<Int16>(real: 3, imaginary: 4), Complex<Int16>(real: 1, imaginary: 2))
@@ -316,8 +355,62 @@ class ComplexArithmeticTests: XCTestCase {
316355
XCTAssertEqual(complex, result, file: file, line: line)
317356
}
318357

358+
private func testAdditionIgnoringOverflow<Scalar>(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger {
359+
// The test is defined to "succeed" if it doesn't crash
360+
let lhs = Complex<Scalar>(real: Scalar.max, imaginary: Scalar.max)
361+
let rhs = Complex<Scalar>(real: Scalar.max, imaginary: Scalar.max)
362+
let rhs2 = Scalar.max
363+
364+
_ = lhs &+ rhs
365+
_ = rhs &+ lhs
366+
_ = lhs .&+ rhs
367+
_ = rhs .&+ lhs
368+
_ = lhs &+ rhs2
369+
_ = rhs2 &+ lhs
370+
371+
var complex = lhs
372+
complex &+= rhs
373+
374+
complex = rhs
375+
complex &+= lhs
376+
377+
complex = lhs
378+
complex .&+= rhs
379+
380+
complex = rhs
381+
complex .&+= lhs
382+
383+
if Scalar.isSigned {
384+
// If the scalar is signed each min is the smallest negative number. Adding these
385+
// two together would overflow.
386+
let lhs = Complex<Scalar>(real: Scalar.min, imaginary: Scalar.min)
387+
let rhs = Complex<Scalar>(real: Scalar.min, imaginary: Scalar.min)
388+
389+
_ = lhs &+ rhs
390+
_ = rhs &+ lhs
391+
_ = lhs .&+ rhs
392+
_ = rhs .&+ lhs
393+
394+
var complex = lhs
395+
complex &+= rhs
396+
397+
complex = lhs
398+
complex &+= rhs2
399+
400+
complex = rhs
401+
complex &+= lhs
402+
403+
complex = lhs
404+
complex .&+= rhs
405+
406+
complex = rhs
407+
complex .&+= lhs
408+
}
409+
}
410+
319411
private func testSubtraction<Scalar>(_ lhs: Complex<Scalar>, _ rhs: Complex<Scalar>, _ result: Complex<Scalar>, file: StaticString = #file, line: UInt = #line) {
320412
XCTAssertEqual(lhs - rhs, result, file: file, line: line)
413+
XCTAssertEqual(lhs .- rhs, result, file: file, line: line)
321414

322415
var complex = lhs
323416
complex -= rhs
@@ -368,6 +461,47 @@ class ComplexArithmeticTests: XCTestCase {
368461
XCTAssertEqual(complex, result, file: file, line: line)
369462
}
370463

464+
private func testSubtractionIgnoringOverflow<Scalar>(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger {
465+
// The test is defined to "succeed" if it doesn't crash
466+
let lhs = Complex<Scalar>(real: Scalar.min, imaginary: Scalar.min)
467+
let rhs = Complex<Scalar>(real: Scalar.max, imaginary: Scalar.max)
468+
let rhs2 = Scalar.max
469+
let rhs3 = Scalar.min
470+
471+
_ = lhs &- rhs
472+
_ = lhs .&- rhs
473+
_ = lhs &- rhs2
474+
_ = rhs3 &- rhs
475+
476+
var complex = lhs
477+
complex &-= rhs
478+
479+
complex = lhs
480+
complex &-= rhs2
481+
482+
complex = lhs
483+
complex .&-= rhs
484+
485+
if Scalar.isSigned {
486+
// If the scalar is signed each min is the smallest negative number. Subtracting
487+
// these from the max would overflow.
488+
let lhs = Complex<Scalar>(real: Scalar.max, imaginary: Scalar.max)
489+
let rhs = Complex<Scalar>(real: Scalar.min, imaginary: Scalar.min)
490+
491+
_ = lhs &- rhs
492+
_ = lhs .&- rhs
493+
494+
var complex = lhs
495+
complex &-= rhs
496+
497+
complex = lhs
498+
complex &-= rhs3
499+
500+
complex = lhs
501+
complex .&-= rhs
502+
}
503+
}
504+
371505
private func testMultiplication<Scalar>(_ lhs: Complex<Scalar>, _ rhs: Complex<Scalar>, _ result: Complex<Scalar>, file: StaticString = #file, line: UInt = #line) {
372506
XCTAssertEqual(lhs * rhs, result, file: file, line: line)
373507
XCTAssertEqual(rhs * lhs, result, file: file, line: line)
@@ -381,6 +515,35 @@ class ComplexArithmeticTests: XCTestCase {
381515
XCTAssertEqual(complex, result, file: file, line: line)
382516
}
383517

518+
private func testMultiplicationIgnoringOverflow<Scalar>(forType: Scalar.Type, file: StaticString = #file, line: UInt = #line) where Scalar: FixedWidthInteger {
519+
// The test is defined to "succeed" if it doesn't crash
520+
let lhs = Complex<Scalar>(real: Scalar.max, imaginary: Scalar.max)
521+
let rhs = Complex<Scalar>(real: Scalar.max, imaginary: Scalar.max)
522+
let rhs2 = Scalar.max
523+
524+
_ = lhs &* rhs
525+
_ = rhs &* lhs
526+
_ = lhs .&* rhs
527+
_ = rhs .&* lhs
528+
_ = lhs &* rhs2
529+
_ = rhs2 &* lhs
530+
531+
var complex = lhs
532+
complex &*= rhs
533+
534+
complex = rhs
535+
complex &*= lhs
536+
537+
complex = lhs
538+
complex &*= rhs2
539+
540+
complex = lhs
541+
complex .&*= rhs
542+
543+
complex = rhs
544+
complex .&*= lhs
545+
}
546+
384547
private func testMultiplication<Scalar>(_ lhs: Complex<Scalar>, _ rhs: Scalar, file: StaticString = #file, line: UInt = #line) {
385548
let result = Complex(real: lhs.real * rhs, imaginary: lhs.imaginary * rhs)
386549
XCTAssertEqual(lhs * rhs, result, file: file, line: line)

0 commit comments

Comments
 (0)