Skip to content

Commit e485150

Browse files
authored
Merge pull request #70 from RougeWare/feature/TwoDimensional-operators
TwoDimensional operators: +-*/
2 parents 08de7c7 + abdfed8 commit e485150

File tree

5 files changed

+267
-1
lines changed

5 files changed

+267
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This package includes:
99
- centering and scaling
1010
- generic protocols to unify math across all Cartesian types
1111
- conveniences for measuring and placing rectangles/points/sizes
12+
- mathematical operators
1213
- and much more...
1314

1415
Who knew there was so much to be done with rectangles?

Sources/RectangleTools/Synthesized Conveniences/TwoDimensional Extensions.swift

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,142 @@ public extension TwoDimensional where Length: SignedNumeric {
279279
measurementY: -lhs.measurementY)
280280
}
281281
}
282+
283+
284+
285+
// MARK: - Operators
286+
287+
public extension TwoDimensional where Length: AdditiveArithmetic {
288+
/// Add the given scalar value to both dimensions of the 2D value
289+
///
290+
/// ```
291+
/// 11 + CGPoint(x: 7, y: 12) == CGPoint(x: 18, y: 23)
292+
/// ```
293+
///
294+
/// - Parameters:
295+
/// - augend: This will be added to both dimensions of `addend`
296+
/// - addend: The dimensions here will have `augend` added to each
297+
static func + (augend: Length, addend: Self) -> Self {
298+
.init(
299+
measurementX: augend + addend.measurementX,
300+
measurementY: augend + addend.measurementY
301+
)
302+
}
303+
304+
305+
/// Add the given scalar value to both dimensions of the 2D value
306+
///
307+
/// ```
308+
/// CGPoint(x: 7, y: 12) + 11 == CGPoint(x: 18, y: 23)
309+
/// ```
310+
///
311+
/// - Parameters:
312+
/// - augend: The dimensions here will have `augend` added to each
313+
/// - addend: This will be added to both dimensions of `addend`
314+
static func + (augend: Self, addend: Length) -> Self {
315+
addend + augend
316+
}
317+
318+
319+
/// Subtracts both dimensions of the 2D value from the given scalar value and returns the result as that same 2D type
320+
///
321+
/// ```
322+
/// 11 - CGPoint(x: 7, y: 12) == CGPoint(x: 4, y: -1)
323+
/// ```
324+
///
325+
/// - Parameters:
326+
/// - minuend: This will have each dimension of `subtrahend` subtracted from it
327+
/// - subtrahend: Each dimension of this will be subtracted from `minuend`
328+
static func - (minuend: Length, subtrahend: Self) -> Self {
329+
.init(
330+
measurementX: minuend - subtrahend.measurementX,
331+
measurementY: minuend - subtrahend.measurementY
332+
)
333+
}
334+
335+
336+
/// Subtracts the given scalar value from both dimensions of the 2D value and returns the result as that same 2D type
337+
///
338+
/// ```
339+
/// CGPoint(x: 7, y: 12) - 11 == CGPoint(x: -4, y: 1)
340+
/// ```
341+
///
342+
/// - Parameters:
343+
/// - minuend: Each dimension of this will be subtracted from `minuend`
344+
/// - subtrahend: This will have `minuend` subtracted from each dimension of it
345+
static func - (minuend: Self, subtrahend: Length) -> Self {
346+
.init(
347+
measurementX: minuend.measurementX - subtrahend,
348+
measurementY: minuend.measurementY - subtrahend
349+
)
350+
}
351+
}
352+
353+
354+
355+
public extension TwoDimensional where Length: MultiplicativeArithmetic {
356+
357+
/// Multiplies the given scalar value by both dimensions of the 2D value
358+
///
359+
/// ```
360+
/// 2 * CGPoint(x: 7, y: 12) == CGPoint(x: 14, y: 24)
361+
/// ```
362+
///
363+
/// - Parameters:
364+
/// - multiplier: This will be multiplied by both dimensions of `multiplicand`
365+
/// - multiplicand: The dimensions here will have `multiplier` multiplied by each
366+
static func * (multiplier: Length, multiplicand: Self) -> Self {
367+
.init(
368+
measurementX: multiplier * multiplicand.measurementX,
369+
measurementY: multiplier * multiplicand.measurementY
370+
)
371+
}
372+
373+
/// Multiplies both dimensions of the given 2D value by the scalar value
374+
///
375+
/// ```
376+
/// CGPoint(x: 7, y: 12) * 2 == CGPoint(x: 14, y: 24)
377+
/// ```
378+
///
379+
/// - Parameters:
380+
/// - multiplier: The dimensions here will have `multiplier` multiplied by each
381+
/// - multiplicand: This will be multiplied by both dimensions of `multiplicand`
382+
static func * (multiplier: Self, multiplicand: Length) -> Self {
383+
multiplicand * multiplier
384+
}
385+
386+
387+
/// Divides the given scalar value by both dimensions of the 2D value and returns the result as that same 2D type
388+
///
389+
/// ```
390+
/// 2 / CGPoint(x: 7, y: 12) == CGPoint(x: 2/7, y: 0.125)
391+
/// ```
392+
///
393+
/// - Parameters:
394+
/// - numerator: This be divided by each dimension of `denominator`
395+
/// - denominator: Each dimension of this will be divided into `numerator`
396+
// CGPoint(x: 7, y: 12) / 2 == CGPoint(x: 3.5 y: 6)
397+
static func / (numerator: Length, denominator: Self) -> Self {
398+
.init(
399+
measurementX: numerator / denominator.measurementX,
400+
measurementY: numerator / denominator.measurementY
401+
)
402+
}
403+
404+
405+
/// Divides both dimensions of the given 2D value by the scalar value and returns the result as that same 2D type
406+
///
407+
/// ```
408+
/// CGPoint(x: 7, y: 12) / 2 == CGPoint(x: 3.5 y: 6)
409+
/// ```
410+
///
411+
/// - Parameters:
412+
/// - numerator: Each dimension of this be divided by `denominator`
413+
/// - denominator: This will be divided into each dimension of `numerator`
414+
static func / (numerator: Self, denominator: Length) -> Self {
415+
.init(
416+
measurementX: numerator.measurementX / denominator,
417+
measurementY: numerator.measurementY / denominator
418+
)
419+
}
420+
}

Tests/RectangleToolsTests/Inversion Tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Test.swift
2+
// Inversion Tests.swift
33
// RectangleTools
44
//
55
// Created by Ky on 2024-08-05.

Tests/RectangleToolsTests/Test Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import Foundation
1010
import CoreGraphics
11+
1112
import RectangleTools
1213

1314

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//
2+
// TwoDimensional operator tests.swift
3+
// RectangleTools
4+
//
5+
// Created by Ky on 2025-03-22.
6+
//
7+
8+
import Testing
9+
import CoreGraphics
10+
11+
import RectangleTools
12+
13+
14+
15+
struct CGSize_operatorTests {
16+
@Test
17+
func testAdd() {
18+
#expect(CGSize(width: 0, height: 0) == (CGSize.zero + CGFloat.zero))
19+
#expect(CGSize(width: 0, height: 0) == (CGSize.zero - CGFloat.zero))
20+
21+
#expect(CGSize(width: 100, height: 100) == (CGSize.zero + 100))
22+
#expect(CGSize(width: -100, height: -100) == (CGSize.zero - 100))
23+
#expect(CGSize(width: 100, height: 100) == (100 + CGSize.zero))
24+
#expect(CGSize(width: 100, height: 100) == (100 - CGSize.zero))
25+
26+
#expect(CGSize(width: 101, height: 101) == (CGSize.one + 100))
27+
#expect(CGSize(width: -99, height: -99) == (CGSize.one - 100))
28+
#expect(CGSize(width: 101, height: 101) == (100 + CGSize.one))
29+
#expect(CGSize(width: 99, height: 99) == (100 - CGSize.one))
30+
31+
#expect(CGSize(width: 220, height: 316) == (CGSize(width: 173, height: 269) + 47))
32+
#expect(CGSize(width: 126, height: 222) == (CGSize(width: 173, height: 269) - 47))
33+
#expect(CGSize(width: 220, height: 316) == (47 + CGSize(width: 173, height: 269)))
34+
#expect(CGSize(width: -126, height: -222) == (47 - CGSize(width: 173, height: 269)))
35+
}
36+
37+
38+
@Test
39+
func testMultiply() {
40+
#expect(CGSize(width: 0 , height: 0 ) == (CGSize.zero * CGFloat.zero))
41+
#expect(CGSize(width: CGFloat.nan, height: .nan) ~== (CGSize.zero / CGFloat.zero))
42+
43+
#expect(CGSize(width: 0 , height: 0 ) == (CGSize.zero * 100))
44+
#expect(CGSize(width: 0 , height: 0 ) == (CGSize.zero / 100))
45+
#expect(CGSize(width: 0 , height: 0 ) == (100 * CGSize.zero))
46+
#expect(CGSize(width: CGFloat.infinity, height: .infinity) ~== (100 / CGSize.zero))
47+
48+
#expect(CGSize(width: 100 , height: 100 ) == (CGSize.one * 100))
49+
#expect(CGSize(width: 0.01, height: 0.01) ~== (CGSize.one / 100))
50+
#expect(CGSize(width: 100 , height: 100 ) == (100 * CGSize.one))
51+
#expect(CGSize(width: 100 , height: 100 ) == (100 / CGSize.one))
52+
53+
#expect(CGSize(width: 8_131 , height: 12_643 ) == (CGSize(width: 173, height: 269) * 47))
54+
#expect(CGSize(width: 3.68085106, height: 5.72340426) ~== (CGSize(width: 173, height: 269) / 47))
55+
#expect(CGSize(width: 8_131 , height: 12_643 ) == (47 * CGSize(width: 173, height: 269)))
56+
#expect(CGSize(width: 0.27167630, height: 0.17472119) ~== (47 / CGSize(width: 173, height: 269)))
57+
}
58+
}
59+
60+
61+
62+
struct CGPoint_operatorTests {
63+
@Test
64+
func testAdd() {
65+
#expect(CGPoint(x: 0, y: 0) == (CGPoint.zero + CGFloat.zero))
66+
67+
#expect(CGPoint(x: 100, y: 100) == (CGPoint.zero + 100))
68+
#expect(CGPoint(x: -100, y: -100) == (CGPoint.zero - 100))
69+
#expect(CGPoint(x: 100, y: 100) == (100 + CGPoint.zero))
70+
#expect(CGPoint(x: 100, y: 100) == (100 - CGPoint.zero))
71+
72+
#expect(CGPoint(x: 101, y: 101) == (CGPoint.one + 100))
73+
#expect(CGPoint(x: -99, y: -99) == (CGPoint.one - 100))
74+
#expect(CGPoint(x: 101, y: 101) == (100 + CGPoint.one))
75+
#expect(CGPoint(x: 99, y: 99) == (100 - CGPoint.one))
76+
77+
#expect(CGPoint(x: 220, y: 316) == (CGPoint(x: 173, y: 269) + 47))
78+
#expect(CGPoint(x: 126, y: 222) == (CGPoint(x: 173, y: 269) - 47))
79+
#expect(CGPoint(x: 220, y: 316) == (47 + CGPoint(x: 173, y: 269)))
80+
#expect(CGPoint(x: -126, y: -222) == (47 - CGPoint(x: 173, y: 269)))
81+
}
82+
83+
84+
@Test
85+
func testMultiply() {
86+
#expect(CGPoint(x: 0 , y: 0 ) == (CGPoint.zero * CGFloat.zero))
87+
#expect(CGPoint(x: CGFloat.nan, y: .nan) ~== (CGPoint.zero / CGFloat.zero))
88+
89+
#expect(CGPoint(x: 0 , y: 0 ) == (CGPoint.zero * 100))
90+
#expect(CGPoint(x: 0 , y: 0 ) == (CGPoint.zero / 100))
91+
#expect(CGPoint(x: 0 , y: 0 ) == (100 * CGPoint.zero))
92+
#expect(CGPoint(x: CGFloat.infinity, y: .infinity) ~== (100 / CGPoint.zero))
93+
94+
#expect(CGPoint(x: 100 , y: 100 ) == (CGPoint.one * 100))
95+
#expect(CGPoint(x: 0.01, y: 0.01) ~== (CGPoint.one / 100))
96+
#expect(CGPoint(x: 100 , y: 100 ) == (100 * CGPoint.one))
97+
#expect(CGPoint(x: 100 , y: 100 ) == (100 / CGPoint.one))
98+
99+
#expect(CGPoint(x: 8_131 , y: 12_643 ) == (CGPoint(x: 173, y: 269) * 47))
100+
#expect(CGPoint(x: 3.68085106, y: 5.72340426) ~== (CGPoint(x: 173, y: 269) / 47))
101+
#expect(CGPoint(x: 8_131 , y: 12_643 ) == (47 * CGPoint(x: 173, y: 269)))
102+
#expect(CGPoint(x: 0.27167630, y: 0.17472119) ~== (47 / CGPoint(x: 173, y: 269)))
103+
}
104+
}
105+
106+
107+
108+
infix operator ~== : ComparisonPrecedence
109+
110+
private func ~== <T> (lhs: T, rhs: T) -> Bool
111+
where T: TwoDimensional,
112+
T.Length: BinaryFloatingPoint
113+
{
114+
(lhs.measurementX ~== rhs.measurementX)
115+
&& (lhs.measurementY ~== rhs.measurementY)
116+
}
117+
118+
119+
private func ~== <T> (lhs: T, rhs: T) -> Bool
120+
where T: BinaryFloatingPoint
121+
{
122+
(lhs.isNaN && rhs.isNaN)
123+
|| (lhs.isInfinite && rhs.isInfinite)
124+
|| abs(lhs - rhs) < 0.0001
125+
}

0 commit comments

Comments
 (0)