Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/IntegerUtilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 123 additions & 0 deletions Sources/IntegerUtilities/LeastCommonMultiple.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//===--- 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<T: BinaryInteger>(_ a: T, _ b: T) -> T {
guard (a != 0) && (b != 0) else {
return 0
}

return T(a.magnitude / gcd(a.magnitude, b.magnitude) * 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.
///
/// > 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<T: FixedWidthInteger>(_ a: T, _ b: T) throws(LeastCommonMultipleOverflowError<T>) -> T {
guard (a != 0) && (b != 0) else {
return 0
}

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)

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
}

/// 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<T: FixedWidthInteger>(_ 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<Int8>` 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<T: FixedWidthInteger>(_ 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`.
///
/// 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<Int8>` 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<T: FixedWidthInteger>: 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 { }
1 change: 1 addition & 0 deletions Tests/IntegerUtilitiesTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_library(IntegerUtilitiesTests
DivideTests.swift
DoubleWidthTests.swift
GreatestCommonDivisorTests.swift
LeastCommonMultipleTests.swift
RotateTests.swift
SaturatingArithmeticTests.swift
ShiftTests.swift)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import IntegerUtilities
import Testing

struct `Greatest Common Divisor Tests` {
@Test func `gcd<BinaryInteger>`() async throws {
@Test func `gcd()`() async throws {
#expect(gcd(0, 0) == 0)
#expect(gcd(0, 1) == 1)
#expect(gcd(1, 0) == 1)
Expand Down
108 changes: 108 additions & 0 deletions Tests/IntegerUtilitiesTests/LeastCommonMultipleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//===--- 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<T: BinaryInteger>(_ 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)
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 `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<Int>(high: 0, low: Int.min.magnitude)) {
try leastCommonMultiple(Int.min, Int.min)
}
#expect(throws: LeastCommonMultipleOverflowError<Int>(high: 0, low: Int.min.magnitude)) {
try leastCommonMultiple(Int.min, 1)
}
#expect(throws: LeastCommonMultipleOverflowError<Int>(high: 0, low: Int.min.magnitude)) {
try leastCommonMultiple(1, Int.min)
}
#expect(throws: LeastCommonMultipleOverflowError<Int8>(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))
}
}