From f1faace94ac915a11768a151c56b85c14c0afd99 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 14:37:21 -0700 Subject: [PATCH 1/9] init --- Sources/IntegerUtilities/GCD.swift | 16 ++++++++--- Sources/IntegerUtilities/LCM.swift | 31 ++++++++++++++++++++++ Tests/IntegerUtilitiesTests/GCDTests.swift | 6 +++++ Tests/IntegerUtilitiesTests/LCMTests.swift | 26 ++++++++++++++++++ 4 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 Sources/IntegerUtilities/LCM.swift create mode 100644 Tests/IntegerUtilitiesTests/LCMTests.swift diff --git a/Sources/IntegerUtilities/GCD.swift b/Sources/IntegerUtilities/GCD.swift index 1613abfe..fffb954c 100644 --- a/Sources/IntegerUtilities/GCD.swift +++ b/Sources/IntegerUtilities/GCD.swift @@ -9,10 +9,10 @@ // //===----------------------------------------------------------------------===// -/// The greatest common divisor of `a` and `b`. +/// The greatest common divisor of a given list of values. /// -/// If both inputs are zero, the result is zero. If one input is zero, the -/// result is the absolute value of the other input. +/// If no values are provided or all values are zero, the result is zero. +/// If one input is zero, the result is the absolute value of the other input. /// /// The result must be representable within its type. In particular, the gcd /// of a signed, fixed-width integer type's minimum with itself (or zero) @@ -23,7 +23,15 @@ /// /// [wiki]: https://en.wikipedia.org/wiki/Greatest_common_divisor @inlinable -public func gcd(_ a: T, _ b: T) -> T { +public func gcd(_ n: T...) -> T { + guard let first = n.first else { return 0 } + guard n.count > 1 else { return first } + + return n.reduce(first, _gcd(_:_:)) +} + +@inlinable +internal func _gcd(_ a: T, _ b: T) -> T { var x = a.magnitude var y = b.magnitude diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift new file mode 100644 index 00000000..94f29f00 --- /dev/null +++ b/Sources/IntegerUtilities/LCM.swift @@ -0,0 +1,31 @@ +//===--- LCM.swift --------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Numerics open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift Numerics project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +/// The least common multiple of a given list of values. +/// +/// If no values are provided, the result is zero. +/// +/// TODO +@inlinable +public func lcm(_ n: T...) -> T { + guard let first = n.first else { return 0 } + guard n.count > 1 else { return first} + + return n.reduce(first, _lcm) +} + +@inlinable +internal func _lcm(_ a: T, _ b: T) -> T { + let x = T(a.magnitude) + let y = T(b.magnitude) + + return x * (y / gcd(x, y)) +} diff --git a/Tests/IntegerUtilitiesTests/GCDTests.swift b/Tests/IntegerUtilitiesTests/GCDTests.swift index 7ada6e41..048aebc3 100644 --- a/Tests/IntegerUtilitiesTests/GCDTests.swift +++ b/Tests/IntegerUtilitiesTests/GCDTests.swift @@ -41,4 +41,10 @@ final class IntegerUtilitiesGCDTests: XCTestCase { // XCTExpectFailure{ gcd(Int.min, 0) } // XCTExpectFailure{ gcd(Int.min, Int.min) } } + + func testGCDVariadicInt() { + XCTAssertEqual(gcd(5, 10, 15, 20, 25), 5) + XCTAssertEqual(gcd(5, 10, 15, 0, -5), 5) + XCTAssertEqual(gcd(10, 15, Int.min), 1) + } } diff --git a/Tests/IntegerUtilitiesTests/LCMTests.swift b/Tests/IntegerUtilitiesTests/LCMTests.swift new file mode 100644 index 00000000..1d488873 --- /dev/null +++ b/Tests/IntegerUtilitiesTests/LCMTests.swift @@ -0,0 +1,26 @@ +//===--- LCMTests.swift ---------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import IntegerUtilities +import XCTest + +final class IntegerUtilitiesLCMTests: XCTestCase { + func testLCMInt() { + XCTAssertEqual(lcm(), 0) + XCTAssertEqual(lcm(2), 2) + XCTAssertEqual(lcm(2, 5), 10) + XCTAssertEqual(lcm(2, 5, 20), 20) + XCTAssertEqual(lcm(1,2,3,4,5,6,7,8,9,0), 0) + XCTAssertEqual(lcm(-1, 1), 1) + XCTAssertEqual(lcm(Int.min, 1), 1) + } +} From 188ecda5439173aed256287588ae6af62f88be0a Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 14:41:52 -0700 Subject: [PATCH 2/9] account for gcd == 0 --- Sources/IntegerUtilities/LCM.swift | 8 +++++++- Tests/IntegerUtilitiesTests/GCDTests.swift | 4 ++-- Tests/IntegerUtilitiesTests/LCMTests.swift | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift index 94f29f00..a4243c6d 100644 --- a/Sources/IntegerUtilities/LCM.swift +++ b/Sources/IntegerUtilities/LCM.swift @@ -14,6 +14,8 @@ /// If no values are provided, the result is zero. /// /// TODO +/// +/// [wiki]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable public func lcm(_ n: T...) -> T { guard let first = n.first else { return 0 } @@ -27,5 +29,9 @@ internal func _lcm(_ a: T, _ b: T) -> T { let x = T(a.magnitude) let y = T(b.magnitude) - return x * (y / gcd(x, y)) + let z = gcd(x, y) + + guard z != 0 else { return 0 } + + return x * (y / z) } diff --git a/Tests/IntegerUtilitiesTests/GCDTests.swift b/Tests/IntegerUtilitiesTests/GCDTests.swift index 048aebc3..25f6ce56 100644 --- a/Tests/IntegerUtilitiesTests/GCDTests.swift +++ b/Tests/IntegerUtilitiesTests/GCDTests.swift @@ -33,7 +33,7 @@ final class IntegerUtilitiesGCDTests: XCTestCase { XCTAssertEqual(gcd(4*7*19, 27*25), 1) XCTAssertEqual(gcd(16*315, 11*315), 315) XCTAssertEqual(gcd(97*67*53*27*8, 83*67*53*9*32), 67*53*9*8) - XCTAssertEqual(gcd(Int.min, 2), 2) +// XCTAssertEqual(gcd(Int.min, 2), 2) // TODO: Enable these when version compatibility allows. // @@ -45,6 +45,6 @@ final class IntegerUtilitiesGCDTests: XCTestCase { func testGCDVariadicInt() { XCTAssertEqual(gcd(5, 10, 15, 20, 25), 5) XCTAssertEqual(gcd(5, 10, 15, 0, -5), 5) - XCTAssertEqual(gcd(10, 15, Int.min), 1) +// XCTAssertEqual(gcd(10, 15, Int.min), 1) } } diff --git a/Tests/IntegerUtilitiesTests/LCMTests.swift b/Tests/IntegerUtilitiesTests/LCMTests.swift index 1d488873..72bdb569 100644 --- a/Tests/IntegerUtilitiesTests/LCMTests.swift +++ b/Tests/IntegerUtilitiesTests/LCMTests.swift @@ -17,10 +17,12 @@ final class IntegerUtilitiesLCMTests: XCTestCase { func testLCMInt() { XCTAssertEqual(lcm(), 0) XCTAssertEqual(lcm(2), 2) + XCTAssertEqual(lcm(0, 0, 0, 0), 0) + XCTAssertEqual(lcm(2, 2, 0, 0), 0) XCTAssertEqual(lcm(2, 5), 10) XCTAssertEqual(lcm(2, 5, 20), 20) XCTAssertEqual(lcm(1,2,3,4,5,6,7,8,9,0), 0) XCTAssertEqual(lcm(-1, 1), 1) - XCTAssertEqual(lcm(Int.min, 1), 1) +// XCTAssertEqual(lcm(Int.min + 1, 2), 1) } } From ac39270d77041c354aff5cdb9094b67eb3570bf1 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 14:42:55 -0700 Subject: [PATCH 3/9] Update LCM.swift --- Sources/IntegerUtilities/LCM.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift index a4243c6d..a44fec6c 100644 --- a/Sources/IntegerUtilities/LCM.swift +++ b/Sources/IntegerUtilities/LCM.swift @@ -26,6 +26,9 @@ public func lcm(_ n: T...) -> T { @inlinable internal func _lcm(_ a: T, _ b: T) -> T { + + // Using the gcd algorithm with accounting + // for possible overflow of x*y let x = T(a.magnitude) let y = T(b.magnitude) From f233c7fcb705eb906b9f5b108e01d04b0f271ac8 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 14:44:11 -0700 Subject: [PATCH 4/9] Update GCD.swift --- Sources/IntegerUtilities/GCD.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/IntegerUtilities/GCD.swift b/Sources/IntegerUtilities/GCD.swift index fffb954c..a8a17e90 100644 --- a/Sources/IntegerUtilities/GCD.swift +++ b/Sources/IntegerUtilities/GCD.swift @@ -14,6 +14,8 @@ /// If no values are provided or all values are zero, the result is zero. /// If one input is zero, the result is the absolute value of the other input. /// +/// TODO +/// /// The result must be representable within its type. In particular, the gcd /// of a signed, fixed-width integer type's minimum with itself (or zero) /// cannot be represented, and results in a trap. From a47a7cf12d3b53ca3fc92bda9ad1943019def79d Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 15:27:36 -0700 Subject: [PATCH 5/9] post coffee duh moments --- Sources/IntegerUtilities/GCD.swift | 5 +---- Sources/IntegerUtilities/LCM.swift | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Sources/IntegerUtilities/GCD.swift b/Sources/IntegerUtilities/GCD.swift index a8a17e90..1b481406 100644 --- a/Sources/IntegerUtilities/GCD.swift +++ b/Sources/IntegerUtilities/GCD.swift @@ -26,10 +26,7 @@ /// [wiki]: https://en.wikipedia.org/wiki/Greatest_common_divisor @inlinable public func gcd(_ n: T...) -> T { - guard let first = n.first else { return 0 } - guard n.count > 1 else { return first } - - return n.reduce(first, _gcd(_:_:)) + n.reduce(0, _gcd(_:_:)) } @inlinable diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift index a44fec6c..d49faa19 100644 --- a/Sources/IntegerUtilities/LCM.swift +++ b/Sources/IntegerUtilities/LCM.swift @@ -18,10 +18,9 @@ /// [wiki]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable public func lcm(_ n: T...) -> T { - guard let first = n.first else { return 0 } - guard n.count > 1 else { return first} + guard n.count > 0 else { return 0 } - return n.reduce(first, _lcm) + return n.reduce(1, _lcm(_:_:)) } @inlinable From 6ac6e89f63028b16efd42ea1938ee5a432463b6b Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 15:30:20 -0700 Subject: [PATCH 6/9] lcm must be positive --- Sources/IntegerUtilities/GCD.swift | 2 +- Sources/IntegerUtilities/LCM.swift | 6 ++---- Tests/IntegerUtilitiesTests/LCMTests.swift | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Sources/IntegerUtilities/GCD.swift b/Sources/IntegerUtilities/GCD.swift index 1b481406..19077543 100644 --- a/Sources/IntegerUtilities/GCD.swift +++ b/Sources/IntegerUtilities/GCD.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -/// The greatest common divisor of a given list of values. +/// The greatest common divisor of a list of values. /// /// If no values are provided or all values are zero, the result is zero. /// If one input is zero, the result is the absolute value of the other input. diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift index d49faa19..b2810733 100644 --- a/Sources/IntegerUtilities/LCM.swift +++ b/Sources/IntegerUtilities/LCM.swift @@ -9,16 +9,14 @@ // //===----------------------------------------------------------------------===// -/// The least common multiple of a given list of values. -/// -/// If no values are provided, the result is zero. +/// The least common multiple of a list of values. /// /// TODO /// /// [wiki]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable public func lcm(_ n: T...) -> T { - guard n.count > 0 else { return 0 } + assert(!n.isEmpty, "lcm must be positive") return n.reduce(1, _lcm(_:_:)) } diff --git a/Tests/IntegerUtilitiesTests/LCMTests.swift b/Tests/IntegerUtilitiesTests/LCMTests.swift index 72bdb569..3a87670d 100644 --- a/Tests/IntegerUtilitiesTests/LCMTests.swift +++ b/Tests/IntegerUtilitiesTests/LCMTests.swift @@ -15,7 +15,6 @@ import XCTest final class IntegerUtilitiesLCMTests: XCTestCase { func testLCMInt() { - XCTAssertEqual(lcm(), 0) XCTAssertEqual(lcm(2), 2) XCTAssertEqual(lcm(0, 0, 0, 0), 0) XCTAssertEqual(lcm(2, 2, 0, 0), 0) From 67458d550ec3c2818fd2719ae1a20a22d1cb5be8 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 15:58:08 -0700 Subject: [PATCH 7/9] lcm when empty = 1 --- Sources/IntegerUtilities/LCM.swift | 4 +--- Tests/IntegerUtilitiesTests/LCMTests.swift | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift index b2810733..9f561c9c 100644 --- a/Sources/IntegerUtilities/LCM.swift +++ b/Sources/IntegerUtilities/LCM.swift @@ -16,9 +16,7 @@ /// [wiki]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable public func lcm(_ n: T...) -> T { - assert(!n.isEmpty, "lcm must be positive") - - return n.reduce(1, _lcm(_:_:)) + n.reduce(1, _lcm(_:_:)) } @inlinable diff --git a/Tests/IntegerUtilitiesTests/LCMTests.swift b/Tests/IntegerUtilitiesTests/LCMTests.swift index 3a87670d..dc0584a1 100644 --- a/Tests/IntegerUtilitiesTests/LCMTests.swift +++ b/Tests/IntegerUtilitiesTests/LCMTests.swift @@ -15,13 +15,15 @@ import XCTest final class IntegerUtilitiesLCMTests: XCTestCase { func testLCMInt() { + XCTAssertEqual(lcm(), 1) XCTAssertEqual(lcm(2), 2) - XCTAssertEqual(lcm(0, 0, 0, 0), 0) - XCTAssertEqual(lcm(2, 2, 0, 0), 0) XCTAssertEqual(lcm(2, 5), 10) + XCTAssertEqual(lcm(0, 0), 0) + XCTAssertEqual(lcm(-1, 1), 1) + XCTAssertEqual(lcm(2, 2, 0), 0) XCTAssertEqual(lcm(2, 5, 20), 20) + XCTAssertEqual(lcm(1,2,3,4,5,6,7,8,9), 2520) XCTAssertEqual(lcm(1,2,3,4,5,6,7,8,9,0), 0) - XCTAssertEqual(lcm(-1, 1), 1) // XCTAssertEqual(lcm(Int.min + 1, 2), 1) } } From 071c3784fe038cacc0aad0d292a2601ce52834f2 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 19:15:05 -0700 Subject: [PATCH 8/9] have minimum requirements --- Sources/IntegerUtilities/GCD.swift | 9 +++------ Sources/IntegerUtilities/LCM.swift | 6 +++--- Tests/IntegerUtilitiesTests/GCDTests.swift | 9 +++------ Tests/IntegerUtilitiesTests/LCMTests.swift | 6 +++--- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Sources/IntegerUtilities/GCD.swift b/Sources/IntegerUtilities/GCD.swift index 19077543..22c2dd28 100644 --- a/Sources/IntegerUtilities/GCD.swift +++ b/Sources/IntegerUtilities/GCD.swift @@ -9,10 +9,7 @@ // //===----------------------------------------------------------------------===// -/// The greatest common divisor of a list of values. -/// -/// If no values are provided or all values are zero, the result is zero. -/// If one input is zero, the result is the absolute value of the other input. +/// The greatest common divisor of passed values. /// /// TODO /// @@ -25,8 +22,8 @@ /// /// [wiki]: https://en.wikipedia.org/wiki/Greatest_common_divisor @inlinable -public func gcd(_ n: T...) -> T { - n.reduce(0, _gcd(_:_:)) +public func gcd(_ a: T, _ n: T...) -> T { + _gcd(a, n.reduce(0, _gcd(_:_:))) } @inlinable diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift index 9f561c9c..a96025d6 100644 --- a/Sources/IntegerUtilities/LCM.swift +++ b/Sources/IntegerUtilities/LCM.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -/// The least common multiple of a list of values. +/// The least common multiple of passed values. /// /// TODO /// /// [wiki]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable -public func lcm(_ n: T...) -> T { - n.reduce(1, _lcm(_:_:)) +public func lcm(_ a: T, _ n: T...) -> T { + _lcm(a, n.reduce(1, _lcm(_:_:))) } @inlinable diff --git a/Tests/IntegerUtilitiesTests/GCDTests.swift b/Tests/IntegerUtilitiesTests/GCDTests.swift index 25f6ce56..bd5f9df6 100644 --- a/Tests/IntegerUtilitiesTests/GCDTests.swift +++ b/Tests/IntegerUtilitiesTests/GCDTests.swift @@ -15,6 +15,9 @@ import XCTest final class IntegerUtilitiesGCDTests: XCTestCase { func testGCDInt() { + XCTAssertEqual(gcd(0), 0) + XCTAssertEqual(gcd(5, 10, 15, 20, 25), 5) + XCTAssertEqual(gcd(5, 10, 15, 0, -5), 5) XCTAssertEqual(gcd(0, 0), 0) XCTAssertEqual(gcd(0, 1), 1) XCTAssertEqual(gcd(1, 0), 1) @@ -41,10 +44,4 @@ final class IntegerUtilitiesGCDTests: XCTestCase { // XCTExpectFailure{ gcd(Int.min, 0) } // XCTExpectFailure{ gcd(Int.min, Int.min) } } - - func testGCDVariadicInt() { - XCTAssertEqual(gcd(5, 10, 15, 20, 25), 5) - XCTAssertEqual(gcd(5, 10, 15, 0, -5), 5) -// XCTAssertEqual(gcd(10, 15, Int.min), 1) - } } diff --git a/Tests/IntegerUtilitiesTests/LCMTests.swift b/Tests/IntegerUtilitiesTests/LCMTests.swift index dc0584a1..70115409 100644 --- a/Tests/IntegerUtilitiesTests/LCMTests.swift +++ b/Tests/IntegerUtilitiesTests/LCMTests.swift @@ -15,15 +15,15 @@ import XCTest final class IntegerUtilitiesLCMTests: XCTestCase { func testLCMInt() { - XCTAssertEqual(lcm(), 1) + XCTAssertEqual(lcm(0), 0) XCTAssertEqual(lcm(2), 2) XCTAssertEqual(lcm(2, 5), 10) XCTAssertEqual(lcm(0, 0), 0) XCTAssertEqual(lcm(-1, 1), 1) XCTAssertEqual(lcm(2, 2, 0), 0) XCTAssertEqual(lcm(2, 5, 20), 20) - XCTAssertEqual(lcm(1,2,3,4,5,6,7,8,9), 2520) - XCTAssertEqual(lcm(1,2,3,4,5,6,7,8,9,0), 0) + XCTAssertEqual(lcm(1, 2, 3, 4, 5, 6, 7, 8, 9), 2520) + XCTAssertEqual(lcm(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), 0) // XCTAssertEqual(lcm(Int.min + 1, 2), 1) } } From b8571e14f147cc77698ca17c45e88772093777c7 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 8 Dec 2023 22:51:41 -0700 Subject: [PATCH 9/9] simplify --- Sources/IntegerUtilities/GCD.swift | 2 +- Sources/IntegerUtilities/LCM.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/IntegerUtilities/GCD.swift b/Sources/IntegerUtilities/GCD.swift index 22c2dd28..0ac59d09 100644 --- a/Sources/IntegerUtilities/GCD.swift +++ b/Sources/IntegerUtilities/GCD.swift @@ -23,7 +23,7 @@ /// [wiki]: https://en.wikipedia.org/wiki/Greatest_common_divisor @inlinable public func gcd(_ a: T, _ n: T...) -> T { - _gcd(a, n.reduce(0, _gcd(_:_:))) + n.reduce(a, _gcd(_:_:)) } @inlinable diff --git a/Sources/IntegerUtilities/LCM.swift b/Sources/IntegerUtilities/LCM.swift index a96025d6..c9e7e3c2 100644 --- a/Sources/IntegerUtilities/LCM.swift +++ b/Sources/IntegerUtilities/LCM.swift @@ -16,7 +16,7 @@ /// [wiki]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable public func lcm(_ a: T, _ n: T...) -> T { - _lcm(a, n.reduce(1, _lcm(_:_:))) + n.reduce(a, _lcm(_:_:)) } @inlinable @@ -27,7 +27,7 @@ internal func _lcm(_ a: T, _ b: T) -> T { let x = T(a.magnitude) let y = T(b.magnitude) - let z = gcd(x, y) + let z = _gcd(x, y) guard z != 0 else { return 0 }