From 0a4fe6b91d6b0a7a7397a09e4f1d730eba9b16ad Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Sun, 17 Aug 2025 09:00:36 -0400 Subject: [PATCH 1/8] Added lcm and tests. --- Sources/IntegerUtilities/CMakeLists.txt | 1 + .../LeastCommonMultiple.swift | 73 +++++++++++++++++++ Tests/IntegerUtilitiesTests/CMakeLists.txt | 1 + .../LeastCommonMultipleTests.swift | 60 +++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 Sources/IntegerUtilities/LeastCommonMultiple.swift create mode 100644 Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift diff --git a/Sources/IntegerUtilities/CMakeLists.txt b/Sources/IntegerUtilities/CMakeLists.txt index 6dcac644..e0213e21 100644 --- a/Sources/IntegerUtilities/CMakeLists.txt +++ b/Sources/IntegerUtilities/CMakeLists.txt @@ -10,6 +10,7 @@ See https://swift.org/LICENSE.txt for license information add_library(IntegerUtilities DivideWithRounding.swift GreatestCommonDivisor.swift + LeastCommonMultiple.swift Rotate.swift RoundingRule.swift SaturatingArithmetic.swift diff --git a/Sources/IntegerUtilities/LeastCommonMultiple.swift b/Sources/IntegerUtilities/LeastCommonMultiple.swift new file mode 100644 index 00000000..ce7752db --- /dev/null +++ b/Sources/IntegerUtilities/LeastCommonMultiple.swift @@ -0,0 +1,73 @@ +//===--- LeastCommonMultiple.swift ----------------------------*- swift -*-===// +// +// This source file is part of the Swift Numerics open source project +// +// Copyright (c) 2021-2025 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][lcm] of `a` and `b`. +/// +/// If either input is zero, the result is zero. +/// +/// The result must be representable within its type. +/// +/// [lcm]: https://en.wikipedia.org/wiki/Least_common_multiple +@inlinable +public func lcm(_ a: T, _ b: T) -> T { + guard (a != 0) && (b != 0) else { + return 0 + } + + return T(a.magnitude / gcd(a, b) * b.magnitude) +} + +/// The [least common multiple][lcm] of `a` and `b`. +/// +/// If either input is zero, the result is zero. +/// +/// Throws `LeastCommonMultipleOverflowError` containing the full width result if it is not representable within its type. +/// +/// [lcm]: https://en.wikipedia.org/wiki/Least_common_multiple +@inlinable +public func lcm(_ a: T, _ b: T) throws(LeastCommonMultipleOverflowError) -> T { + guard (a != 0) && (b != 0) else { + return 0 + } + + let reduced = a.magnitude / gcd(a, b) + + // We could use the multipliedFullWidth directly here, but we optimize instead for the non-throwing case because multipliedReportingOverflow is much faster. + let (partialValue, overflow) = reduced.multipliedReportingOverflow(by: b.magnitude) + + guard !overflow, let result = T(exactly: partialValue) else { + let fullWidth = reduced.multipliedFullWidth(by: b.magnitude) + + throw LeastCommonMultipleOverflowError(high: fullWidth.high, low: fullWidth.low) + } + + return result +} + + +/// Error thrown by `lcm`. +/// +/// Thrown when the result of the lcm isn't representable within its type. You can combine `high` and `low` into a double width integer to access the result. +/// +/// For example a `LeastCommonMultipleOverflowError` has `UInt8` as its `Magnitude` and contains the result in `high: UInt8` and `low: UInt8`. +/// These can be combined into a `UInt16` result as `UInt16(high) << 8 | UInt16(low)`. +public struct LeastCommonMultipleOverflowError: Error, Equatable { + public let high: T.Magnitude + public let low: T.Magnitude + + @inlinable + public init(high: T.Magnitude, low: T.Magnitude) { + self.high = high + self.low = low + } +} + +extension LeastCommonMultipleOverflowError: Sendable where T.Magnitude: Sendable { } diff --git a/Tests/IntegerUtilitiesTests/CMakeLists.txt b/Tests/IntegerUtilitiesTests/CMakeLists.txt index 9fd0cd7a..0e51d787 100644 --- a/Tests/IntegerUtilitiesTests/CMakeLists.txt +++ b/Tests/IntegerUtilitiesTests/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(IntegerUtilitiesTests DivideTests.swift DoubleWidthTests.swift GreatestCommonDivisorTests.swift + LeastCommonMultipleTests.swift RotateTests.swift SaturatingArithmeticTests.swift ShiftTests.swift) diff --git a/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift b/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift new file mode 100644 index 00000000..efaf4ac7 --- /dev/null +++ b/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift @@ -0,0 +1,60 @@ +//===--- LeastCommonMultipleTests.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 +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import IntegerUtilities +import Testing + +private func lcm_ForceBinaryInteger(_ a: T, _ b: T) -> T { + IntegerUtilities.lcm(a,b) +} + +struct `Least Common Multiple Tests` { + @Test func `lcm`() async throws { + #expect(lcm_ForceBinaryInteger(1024, 0) == 0) + #expect(lcm_ForceBinaryInteger(0, 1024) == 0) + #expect(lcm_ForceBinaryInteger(0, 0) == 0) + #expect(lcm_ForceBinaryInteger(1024, 768) == 3072) + #expect(lcm_ForceBinaryInteger(768, 1024) == 3072) + #expect(lcm_ForceBinaryInteger(24, 18) == 72) + #expect(lcm_ForceBinaryInteger(18, 24) == 72) + #expect(lcm_ForceBinaryInteger(6930, 288) == 110880) + #expect(lcm_ForceBinaryInteger(288, 6930) == 110880) + #expect(lcm_ForceBinaryInteger(Int.max, 1) == Int.max) + #expect(lcm_ForceBinaryInteger(1, Int.max) == Int.max) + } + + @Test func `lcm`() async throws { + #expect(try lcm(1024, 0) == 0) + #expect(try lcm(0, 1024) == 0) + #expect(try lcm(0, 0) == 0) + #expect(try lcm(1024, 768) == 3072) + #expect(try lcm(768, 1024) == 3072) + #expect(try lcm(24, 18) == 72) + #expect(try lcm(18, 24) == 72) + #expect(try lcm(6930, 288) == 110880) + #expect(try lcm(288, 6930) == 110880) + #expect(try lcm(Int.max, 1) == Int.max) + #expect(try lcm(1, Int.max) == Int.max) + #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { + try lcm(Int.min, Int.min) + } + #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { + try lcm(Int.min, 1) + } + #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { + try lcm(1, Int.min) + } + #expect(throws: LeastCommonMultipleOverflowError(high: 63, low: 128)) { + try lcm(Int8.min, Int8.max) + } + } +} From 90584fcd98a6058fe2e60b4c53a240e07309c1b0 Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Mon, 18 Aug 2025 13:48:05 -0400 Subject: [PATCH 2/8] Renamed gcd test --- Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift b/Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift index f22a3d36..92476a28 100644 --- a/Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift +++ b/Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift @@ -14,7 +14,7 @@ import IntegerUtilities import Testing struct `Greatest Common Divisor Tests` { - @Test func `gcd`() async throws { + @Test func `gcd()`() async throws { #expect(gcd(0, 0) == 0) #expect(gcd(0, 1) == 1) #expect(gcd(1, 0) == 1) From cc77eec98ed7c478e20680f3375f9b80051789bb Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Mon, 18 Aug 2025 13:50:06 -0400 Subject: [PATCH 3/8] Updated lcm with latest changes. Renamed lcm to leastCommonMultiple. Updated gcd call to use magnitude instead. Renamed tests. --- .../LeastCommonMultiple.swift | 6 +-- .../LeastCommonMultipleTests.swift | 46 ++++++++++++------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Sources/IntegerUtilities/LeastCommonMultiple.swift b/Sources/IntegerUtilities/LeastCommonMultiple.swift index ce7752db..08335887 100644 --- a/Sources/IntegerUtilities/LeastCommonMultiple.swift +++ b/Sources/IntegerUtilities/LeastCommonMultiple.swift @@ -22,7 +22,7 @@ public func lcm(_ a: T, _ b: T) -> T { return 0 } - return T(a.magnitude / gcd(a, b) * b.magnitude) + return T(a.magnitude / gcd(a.magnitude, b.magnitude) * b.magnitude) } /// The [least common multiple][lcm] of `a` and `b`. @@ -33,12 +33,12 @@ public func lcm(_ a: T, _ b: T) -> T { /// /// [lcm]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable -public func lcm(_ a: T, _ b: T) throws(LeastCommonMultipleOverflowError) -> T { +public func leastCommonMultiple(_ a: T, _ b: T) throws(LeastCommonMultipleOverflowError) -> T { guard (a != 0) && (b != 0) else { return 0 } - let reduced = a.magnitude / gcd(a, b) + let reduced = a.magnitude / gcd(a.magnitude, b.magnitude) // We could use the multipliedFullWidth directly here, but we optimize instead for the non-throwing case because multipliedReportingOverflow is much faster. let (partialValue, overflow) = reduced.multipliedReportingOverflow(by: b.magnitude) diff --git a/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift b/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift index efaf4ac7..dffac4f3 100644 --- a/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift +++ b/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift @@ -18,7 +18,7 @@ private func lcm_ForceBinaryInteger(_ a: T, _ b: T) -> T { } struct `Least Common Multiple Tests` { - @Test func `lcm`() async throws { + @Test func `lcm()`() async throws { #expect(lcm_ForceBinaryInteger(1024, 0) == 0) #expect(lcm_ForceBinaryInteger(0, 1024) == 0) #expect(lcm_ForceBinaryInteger(0, 0) == 0) @@ -30,31 +30,43 @@ struct `Least Common Multiple Tests` { #expect(lcm_ForceBinaryInteger(288, 6930) == 110880) #expect(lcm_ForceBinaryInteger(Int.max, 1) == Int.max) #expect(lcm_ForceBinaryInteger(1, Int.max) == Int.max) + await #expect(processExitsWith: .failure) { + _ = lcm_ForceBinaryInteger(Int.min, Int.min) + } + await #expect(processExitsWith: .failure) { + _ = lcm_ForceBinaryInteger(Int.min, 1) + } + await #expect(processExitsWith: .failure) { + _ = lcm_ForceBinaryInteger(1, Int.min) + } + await #expect(processExitsWith: .failure) { + _ = lcm_ForceBinaryInteger(Int8.min, Int8.max) + } } - @Test func `lcm`() async throws { - #expect(try lcm(1024, 0) == 0) - #expect(try lcm(0, 1024) == 0) - #expect(try lcm(0, 0) == 0) - #expect(try lcm(1024, 768) == 3072) - #expect(try lcm(768, 1024) == 3072) - #expect(try lcm(24, 18) == 72) - #expect(try lcm(18, 24) == 72) - #expect(try lcm(6930, 288) == 110880) - #expect(try lcm(288, 6930) == 110880) - #expect(try lcm(Int.max, 1) == Int.max) - #expect(try lcm(1, Int.max) == Int.max) + @Test func `leastCommonMultiple()`() async throws { + #expect(try leastCommonMultiple(1024, 0) == 0) + #expect(try leastCommonMultiple(0, 1024) == 0) + #expect(try leastCommonMultiple(0, 0) == 0) + #expect(try leastCommonMultiple(1024, 768) == 3072) + #expect(try leastCommonMultiple(768, 1024) == 3072) + #expect(try leastCommonMultiple(24, 18) == 72) + #expect(try leastCommonMultiple(18, 24) == 72) + #expect(try leastCommonMultiple(6930, 288) == 110880) + #expect(try leastCommonMultiple(288, 6930) == 110880) + #expect(try leastCommonMultiple(Int.max, 1) == Int.max) + #expect(try leastCommonMultiple(1, Int.max) == Int.max) #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { - try lcm(Int.min, Int.min) + try leastCommonMultiple(Int.min, Int.min) } #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { - try lcm(Int.min, 1) + try leastCommonMultiple(Int.min, 1) } #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { - try lcm(1, Int.min) + try leastCommonMultiple(1, Int.min) } #expect(throws: LeastCommonMultipleOverflowError(high: 63, low: 128)) { - try lcm(Int8.min, Int8.max) + try leastCommonMultiple(Int8.min, Int8.max) } } } From 643dbf0d3dc1a68ec0d4ba5f711387d6b0319f33 Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Mon, 18 Aug 2025 13:51:57 -0400 Subject: [PATCH 4/8] Fixed documentation --- Sources/IntegerUtilities/LeastCommonMultiple.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/IntegerUtilities/LeastCommonMultiple.swift b/Sources/IntegerUtilities/LeastCommonMultiple.swift index 08335887..6dc55387 100644 --- a/Sources/IntegerUtilities/LeastCommonMultiple.swift +++ b/Sources/IntegerUtilities/LeastCommonMultiple.swift @@ -53,7 +53,7 @@ public func leastCommonMultiple(_ a: T, _ b: T) throws(Lea } -/// Error thrown by `lcm`. +/// Error thrown by `leastCommonMultiple())`. /// /// Thrown when the result of the lcm isn't representable within its type. You can combine `high` and `low` into a double width integer to access the result. /// From eff7d74e549a3ff23cbdd058458f157719c8c173 Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Mon, 18 Aug 2025 14:20:09 -0400 Subject: [PATCH 5/8] Fixed typo --- Sources/IntegerUtilities/LeastCommonMultiple.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/IntegerUtilities/LeastCommonMultiple.swift b/Sources/IntegerUtilities/LeastCommonMultiple.swift index 6dc55387..8018ebc4 100644 --- a/Sources/IntegerUtilities/LeastCommonMultiple.swift +++ b/Sources/IntegerUtilities/LeastCommonMultiple.swift @@ -53,7 +53,7 @@ public func leastCommonMultiple(_ a: T, _ b: T) throws(Lea } -/// Error thrown by `leastCommonMultiple())`. +/// Error thrown by `leastCommonMultiple`. /// /// Thrown when the result of the lcm isn't representable within its type. You can combine `high` and `low` into a double width integer to access the result. /// From df21078a9a87c019fea3e6f5310b569a134ec6cf Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Wed, 20 Aug 2025 11:06:23 -0400 Subject: [PATCH 6/8] Added leastCommonMultipleReportingOverflow and leastCommonMultipleFullWidth along with tests. --- .../LeastCommonMultiple.swift | 47 ++++++++++ .../LeastCommonMultipleTests.swift | 86 +++++++++++++------ 2 files changed, 108 insertions(+), 25 deletions(-) diff --git a/Sources/IntegerUtilities/LeastCommonMultiple.swift b/Sources/IntegerUtilities/LeastCommonMultiple.swift index 8018ebc4..bfca7759 100644 --- a/Sources/IntegerUtilities/LeastCommonMultiple.swift +++ b/Sources/IntegerUtilities/LeastCommonMultiple.swift @@ -52,6 +52,53 @@ public func leastCommonMultiple(_ a: T, _ b: T) throws(Lea return result } +/// Returns the [least common multiple][lcm] of `a` and `b`, along with a Boolean value indicating whether overflow occurred in the operation. +/// +/// If either input is zero, the result is zero. +/// +/// - Returns: A tuple containing the result of the function along with a Boolean value indicating whether overflow occurred. If the overflow component is false, the partialValue component contains the entire result. If the +/// overflow component is true, an overflow occurred and the partialValue component contains the truncated result of the operation. +/// +/// [lcm]: https://en.wikipedia.org/wiki/Least_common_multiple +@inlinable +public func leastCommonMultipleReportingOverflow(_ a: T, _ b: T) -> (partialValue: T, overflow: Bool) { + guard (a != 0) && (b != 0) else { + return (partialValue: 0, overflow: false) + } + + let reduced = a.magnitude / gcd(a.magnitude, b.magnitude) + + let (partialValue, overflow) = reduced.multipliedReportingOverflow(by: b.magnitude) + + guard !overflow, let result = T(exactly: partialValue) else { + return (partialValue: T(truncatingIfNeeded: partialValue), overflow: true) + } + + return (partialValue: result, overflow: false) +} + +/// Returns a tuple containing the high and low parts of the result of the [least common multiple][lcm] of `a` and `b`. +/// +/// If either input is zero, the result is zero. +/// +/// You can combine `high` and `low` into a double width integer to access the result. +/// +/// For example `leastCommonMultipleFullWidth` has `UInt8` as its `Magnitude` and contains the result in `high: UInt8` and `low: UInt8`. +/// These can be combined into a `UInt16` result as `UInt16(high) << 8 | UInt16(low)`. +/// +/// - Returns: A tuple containing the high and low parts of the result. +/// +/// [lcm]: https://en.wikipedia.org/wiki/Least_common_multiple +@inlinable +public func leastCommonMultipleFullWidth(_ a: T, _ b: T) -> (high: T.Magnitude, low: T.Magnitude) { + guard (a != 0) && (b != 0) else { + return (high: 0, low: 0) + } + + let reduced = a.magnitude / gcd(a.magnitude, b.magnitude) + + return reduced.multipliedFullWidth(by: b.magnitude) +} /// Error thrown by `leastCommonMultiple`. /// diff --git a/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift b/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift index dffac4f3..150ef994 100644 --- a/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift +++ b/Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift @@ -44,29 +44,65 @@ struct `Least Common Multiple Tests` { } } - @Test func `leastCommonMultiple()`() async throws { - #expect(try leastCommonMultiple(1024, 0) == 0) - #expect(try leastCommonMultiple(0, 1024) == 0) - #expect(try leastCommonMultiple(0, 0) == 0) - #expect(try leastCommonMultiple(1024, 768) == 3072) - #expect(try leastCommonMultiple(768, 1024) == 3072) - #expect(try leastCommonMultiple(24, 18) == 72) - #expect(try leastCommonMultiple(18, 24) == 72) - #expect(try leastCommonMultiple(6930, 288) == 110880) - #expect(try leastCommonMultiple(288, 6930) == 110880) - #expect(try leastCommonMultiple(Int.max, 1) == Int.max) - #expect(try leastCommonMultiple(1, Int.max) == Int.max) - #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { - try leastCommonMultiple(Int.min, Int.min) - } - #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { - try leastCommonMultiple(Int.min, 1) - } - #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { - try leastCommonMultiple(1, Int.min) - } - #expect(throws: LeastCommonMultipleOverflowError(high: 63, low: 128)) { - try leastCommonMultiple(Int8.min, Int8.max) - } - } + @Test func `leastCommonMultiple()`() async throws { + #expect(try leastCommonMultiple(1024, 0) == 0) + #expect(try leastCommonMultiple(0, 1024) == 0) + #expect(try leastCommonMultiple(0, 0) == 0) + #expect(try leastCommonMultiple(1024, 768) == 3072) + #expect(try leastCommonMultiple(768, 1024) == 3072) + #expect(try leastCommonMultiple(24, 18) == 72) + #expect(try leastCommonMultiple(18, 24) == 72) + #expect(try leastCommonMultiple(6930, 288) == 110880) + #expect(try leastCommonMultiple(288, 6930) == 110880) + #expect(try leastCommonMultiple(Int.max, 1) == Int.max) + #expect(try leastCommonMultiple(1, Int.max) == Int.max) + #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { + try leastCommonMultiple(Int.min, Int.min) + } + #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { + try leastCommonMultiple(Int.min, 1) + } + #expect(throws: LeastCommonMultipleOverflowError(high: 0, low: Int.min.magnitude)) { + try leastCommonMultiple(1, Int.min) + } + #expect(throws: LeastCommonMultipleOverflowError(high: 63, low: 128)) { + try leastCommonMultiple(Int8.min, Int8.max) + } + } + + @Test func `leastCommonMultipleReportingOverflow()`() async throws { + #expect(leastCommonMultipleReportingOverflow(1024, 0) == (partialValue: 0, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(0, 1024) == (partialValue: 0, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(0, 0) == (partialValue: 0, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(1024, 768) == (partialValue: 3072, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(768, 1024) == (partialValue: 3072, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(24, 18) == (partialValue: 72, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(18, 24) == (partialValue: 72, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(6930, 288) == (partialValue: 110880, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(288, 6930) == (partialValue: 110880, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(Int.max, 1) == (partialValue: Int.max, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(1, Int.max) == (partialValue: Int.max, overflow: false)) + #expect(leastCommonMultipleReportingOverflow(Int.min, Int.min) == (partialValue: Int(truncatingIfNeeded: Int.min), overflow: true)) + #expect(leastCommonMultipleReportingOverflow(Int.min, 1) == (partialValue: Int(truncatingIfNeeded: Int.min), overflow: true)) + #expect(leastCommonMultipleReportingOverflow(1, Int.min) == (partialValue: Int(truncatingIfNeeded: Int.min), overflow: true)) + #expect(leastCommonMultipleReportingOverflow(Int8.min, Int8.max) == (partialValue: Int8(truncatingIfNeeded: Int16(Int8.min).magnitude * Int16(Int8.max).magnitude), overflow: true)) + } + + @Test func `leastCommonMultipleFullWidth()`() async throws { + #expect(leastCommonMultipleFullWidth(1024, 0) == (high: 0, low: 0)) + #expect(leastCommonMultipleFullWidth(0, 1024) == (high: 0, low: 0)) + #expect(leastCommonMultipleFullWidth(0, 0) == (high: 0, low: 0)) + #expect(leastCommonMultipleFullWidth(1024, 768) == (high: 0, low: 3072)) + #expect(leastCommonMultipleFullWidth(768, 1024) == (high: 0, low: 3072)) + #expect(leastCommonMultipleFullWidth(24, 18) == (high: 0, low: 72)) + #expect(leastCommonMultipleFullWidth(18, 24) == (high: 0, low: 72)) + #expect(leastCommonMultipleFullWidth(6930, 288) == (high: 0, low: 110880)) + #expect(leastCommonMultipleFullWidth(288, 6930) == (high: 0, low: 110880)) + #expect(leastCommonMultipleFullWidth(Int.max, 1) == (high: 0, low: Int.max.magnitude)) + #expect(leastCommonMultipleFullWidth(1, Int.max) == (high: 0, low: Int.max.magnitude)) + #expect(leastCommonMultipleFullWidth(Int.min, Int.min) == (high: 0, low: Int.min.magnitude)) + #expect(leastCommonMultipleFullWidth(Int.min, 1) == (high: 0, low: Int.min.magnitude)) + #expect(leastCommonMultipleFullWidth(1, Int.min) == (high: 0, low: Int.min.magnitude)) + #expect(leastCommonMultipleFullWidth(Int8.min, Int8.max) == (high: 63, low: 128)) + } } From eccf71163a85500c8fb4cc8f710adc0aaa788203 Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Fri, 29 Aug 2025 12:57:47 +0200 Subject: [PATCH 7/8] Added Note explaining the performance advantage of leastCommonMultiple. --- Sources/IntegerUtilities/LeastCommonMultiple.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/IntegerUtilities/LeastCommonMultiple.swift b/Sources/IntegerUtilities/LeastCommonMultiple.swift index bfca7759..d4d2bfbf 100644 --- a/Sources/IntegerUtilities/LeastCommonMultiple.swift +++ b/Sources/IntegerUtilities/LeastCommonMultiple.swift @@ -31,6 +31,9 @@ public func lcm(_ a: T, _ b: T) -> T { /// /// Throws `LeastCommonMultipleOverflowError` containing the full width result if it is not representable within its type. /// +/// > Note: For retrieving the result as `T` or the fullwidth result on overflow, calling `leastCommonMultiple()` is faster than calling +/// `leastCommonMultipleReportingOverflow()` followed by leastCommonMultipleFullWidth()`. +/// /// [lcm]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable public func leastCommonMultiple(_ a: T, _ b: T) throws(LeastCommonMultipleOverflowError) -> T { From 3a8d5bbf9f5d564418ec6d8648c73950e5ec7822 Mon Sep 17 00:00:00 2001 From: Jason Bobier Date: Fri, 29 Aug 2025 12:59:01 +0200 Subject: [PATCH 8/8] Fixed typo. --- Sources/IntegerUtilities/LeastCommonMultiple.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/IntegerUtilities/LeastCommonMultiple.swift b/Sources/IntegerUtilities/LeastCommonMultiple.swift index d4d2bfbf..627d621c 100644 --- a/Sources/IntegerUtilities/LeastCommonMultiple.swift +++ b/Sources/IntegerUtilities/LeastCommonMultiple.swift @@ -32,7 +32,7 @@ public func lcm(_ a: T, _ b: T) -> T { /// Throws `LeastCommonMultipleOverflowError` containing the full width result if it is not representable within its type. /// /// > Note: For retrieving the result as `T` or the fullwidth result on overflow, calling `leastCommonMultiple()` is faster than calling -/// `leastCommonMultipleReportingOverflow()` followed by leastCommonMultipleFullWidth()`. +/// `leastCommonMultipleReportingOverflow()` followed by ` leastCommonMultipleFullWidth()`. /// /// [lcm]: https://en.wikipedia.org/wiki/Least_common_multiple @inlinable