Skip to content

Commit a9a6280

Browse files
committed
Fraction: Added + and - math operators between instances
1 parent 1f7d017 commit a9a6280

File tree

2 files changed

+119
-45
lines changed

2 files changed

+119
-45
lines changed

Sources/TimecodeKit/Utilities/Fraction.swift

Lines changed: 105 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public struct Fraction {
4747

4848
/// Initialize by reducing and normalizing the fraction.
4949
public init(reducing numerator: Int, _ denominator: Int) {
50-
let reduced = Self.reduce(n: numerator, d: denominator)
50+
let reduced = reduce(n: numerator, d: denominator)
5151
self.numerator = reduced.n
5252
self.denominator = reduced.d
5353
_isReduced = true
@@ -137,59 +137,18 @@ extension Fraction {
137137
return isNegative ? norm.negated() : norm
138138
}
139139

140-
/// Internal:
141-
/// Reduce a fraction to its simplest form.
142-
/// This also normalizes signs.
143-
static func reduce(n: Int, d: Int) -> (n: Int, d: Int) {
144-
let (absN, signN) = n < 0 ? (-n, -1) : (n, 1)
145-
let (absD, signD) = d < 0 ? (-d, -1) : (d, 1)
146-
var v = n
147-
var u = d
148-
149-
// Euclid's solution to finding the Greatest Common Denominator
150-
while (v != 0) {
151-
(v, u) = (u % v, v)
152-
}
153-
154-
var outN = absN / u * signN
155-
var outD = absD / u * signD
156-
157-
// final check to normalize if necessary
158-
if outN >= 0, outD < 0 {
159-
outN = -outN
160-
outD = -outD
161-
}
162-
163-
return (outN, outD)
164-
}
165-
166140
/// Returns a new instance reduced to its simplest form.
167141
/// This also normalizes signs.
168142
public func reduced() -> Self {
169143
if _isReduced == true { return self }
170144
return Fraction(reducing: numerator, denominator)
171145
}
172146

173-
/// Internal:
174-
/// Normalize a fraction.
175-
/// Fractions with two negative signs are normalized to two positive signs.
176-
/// Fractions with negative denominator are normalized to negative numerator and positive denominator.
177-
static func normalize(n: Int, d: Int) -> (n: Int, d: Int) {
178-
var n = n
179-
var d = d
180-
if n >= 0 && d >= 0 { return (n: n, d: d) }
181-
if (n < 0 && d < 0) || (d < 0) {
182-
n *= -1
183-
d *= -1
184-
}
185-
return (n: n, d: d)
186-
}
187-
188147
/// Returns a new instance normalized.
189148
/// Fractions with two negative signs are normalized to two positive signs.
190149
/// Fractions with negative denominator are normalized to negative numerator and positive denominator.
191150
func normalized() -> Self {
192-
let result = Self.normalize(n: numerator, d: denominator)
151+
let result = normalize(n: numerator, d: denominator)
193152
return Fraction(result.n, result.d)
194153
}
195154

@@ -208,7 +167,33 @@ extension Fraction {
208167
}
209168
}
210169

211-
// MARK: Double
170+
// MARK: - Math
171+
172+
extension Fraction {
173+
public static func + (lhs: Self, rhs: Self) -> Self {
174+
if lhs.denominator == rhs.denominator {
175+
return Fraction(reducing: lhs.numerator + rhs.numerator, lhs.denominator)
176+
}
177+
178+
let lcm = leastCommonMultiple(lhs: lhs.denominator, rhs: rhs.denominator)
179+
180+
let num = (lhs.numerator * lcm.lhsMultiplier) + (rhs.numerator * lcm.rhsMultiplier)
181+
return Fraction(reducing: num, lcm.denominator)
182+
}
183+
184+
public static func - (lhs: Self, rhs: Self) -> Self {
185+
if lhs.denominator == rhs.denominator {
186+
return Fraction(reducing: lhs.numerator - rhs.numerator, lhs.denominator)
187+
}
188+
189+
let lcm = leastCommonMultiple(lhs: lhs.denominator, rhs: rhs.denominator)
190+
191+
let num = (lhs.numerator * lcm.lhsMultiplier) - (rhs.numerator * lcm.rhsMultiplier)
192+
return Fraction(reducing: num, lcm.denominator)
193+
}
194+
}
195+
196+
// MARK: - Double
212197

213198
extension Double {
214199
/// Internal:
@@ -236,7 +221,7 @@ extension Double {
236221
}
237222
}
238223

239-
// MARK: FCPXML Encoding
224+
// MARK: - FCPXML Encoding
240225

241226
extension Fraction {
242227
/// Initializes from an encoded Final Cut Pro FCPXML time value string.
@@ -305,3 +290,78 @@ extension Fraction {
305290
: "\(reduced.numerator)/\(reduced.denominator)s"
306291
}
307292
}
293+
294+
// MARK: - Math Functions
295+
296+
/// Internal:
297+
/// Normalize a fraction.
298+
/// Fractions with two negative signs are normalized to two positive signs.
299+
/// Fractions with negative denominator are normalized to negative numerator and positive denominator.
300+
func normalize(n: Int, d: Int) -> (n: Int, d: Int) {
301+
var n = n
302+
var d = d
303+
if n >= 0 && d >= 0 { return (n: n, d: d) }
304+
if (n < 0 && d < 0) || (d < 0) {
305+
n *= -1
306+
d *= -1
307+
}
308+
return (n: n, d: d)
309+
}
310+
311+
/// Internal:
312+
/// Reduce a fraction to its simplest form.
313+
/// This also normalizes signs.
314+
func reduce(n: Int, d: Int) -> (n: Int, d: Int) {
315+
let (absN, signN) = n < 0 ? (-n, -1) : (n, 1)
316+
let (absD, signD) = d < 0 ? (-d, -1) : (d, 1)
317+
var v = n
318+
var u = d
319+
320+
// Euclid's solution to finding the Greatest Common Denominator
321+
while (v != 0) {
322+
(v, u) = (u % v, v)
323+
}
324+
325+
var outN = absN / u * signN
326+
var outD = absD / u * signD
327+
328+
// final check to normalize if necessary
329+
if outN >= 0, outD < 0 {
330+
outN = -outN
331+
outD = -outD
332+
}
333+
334+
return (outN, outD)
335+
}
336+
337+
/// Internal:
338+
/// Returns greatest common divisor of two numbers.
339+
func greatestCommonDivisor(_ n1: Int, _ n2: Int) -> Int {
340+
var x = 0
341+
var y = max(n1, n2)
342+
var z = min(n1, n2)
343+
344+
while z != 0 {
345+
x = y
346+
y = z
347+
z = x % y
348+
}
349+
350+
return y
351+
}
352+
353+
/// Internal:
354+
/// Returns least common multiple of two numbers and their respective multipliers.
355+
func leastCommonMultiple(
356+
lhs: Int, rhs: Int
357+
) -> (denominator: Int, lhsMultiplier: Int, rhsMultiplier: Int) {
358+
let denominator = lhs * rhs / greatestCommonDivisor(lhs, rhs)
359+
let lhsMultiplier = denominator / lhs
360+
let rhsMultiplier = denominator / rhs
361+
362+
return (
363+
denominator: denominator,
364+
lhsMultiplier: lhsMultiplier,
365+
rhsMultiplier: rhsMultiplier
366+
)
367+
}

Tests/TimecodeKit-Unit-Tests/Unit Tests/Utilities/Fraction Tests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ class Fraction_Tests: XCTestCase {
6464
XCTAssertTrue(Fraction(2, 10) > Fraction(1, 10))
6565
}
6666

67+
func testMathAdd() {
68+
XCTAssertEqual(Fraction(1, 2) + Fraction(1, 4), Fraction(3, 4))
69+
XCTAssertEqual(Fraction(2, 4) + Fraction(1, 4), Fraction(3, 4))
70+
71+
XCTAssertEqual(Fraction(750, 1900) + Fraction(8000, 3800), Fraction(5, 2))
72+
}
73+
74+
func testMathSubtract() {
75+
XCTAssertEqual(Fraction(3, 4) - Fraction(1, 2), Fraction(1, 4))
76+
XCTAssertEqual(Fraction(3, 4) - Fraction(2, 4), Fraction(1, 4))
77+
78+
XCTAssertEqual(Fraction(8000, 3800) - Fraction(100, 1900), Fraction(39, 19))
79+
}
80+
6781
func testFractionInitReducing() {
6882
let frac = Fraction(reducing: 4, 2)
6983
XCTAssertEqual(frac, Fraction(2, 1))

0 commit comments

Comments
 (0)