Skip to content
Closed
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
3 changes: 2 additions & 1 deletion Sources/IntegerUtilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ See https://swift.org/LICENSE.txt for license information

add_library(IntegerUtilities
DivideWithRounding.swift
GCD.swift
GreatestCommonDivisor.swift
LeastCommonMultiple.swift
Rotate.swift
RoundingRule.swift
SaturatingArithmetic.swift
Expand Down
40 changes: 0 additions & 40 deletions Sources/IntegerUtilities/GCD.swift

This file was deleted.

35 changes: 35 additions & 0 deletions Sources/IntegerUtilities/GreatestCommonDivisor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//===--- GreatestCommonDivisor.swift --------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift Numerics open source project
//
// Copyright (c) 2021-2024 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 [greatest common divisor][gcd] of `a` and `b`.
///
/// If both inputs are zero, the result is zero. If one input is zero, the
/// result is the absolute value of the other input.
///
/// [gcd]: https://en.wikipedia.org/wiki/Greatest_common_divisor
@inlinable
public func gcd<T: BinaryInteger>(_ a: T, _ b: T) -> T.Magnitude {
var x = a
var y = b

if x.magnitude < y.magnitude {
swap(&x, &y)
}

// Euclidean algorithm for GCD. It's worth using Lehmer instead for larger
// integer types, but for now this is good and dead-simple and faster than
// the other obvious choice, the binary algorithm.
while y != 0 {
(x, y) = (y, x % y)
}

return x.magnitude
}
79 changes: 79 additions & 0 deletions Sources/IntegerUtilities/LeastCommonMultiple.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//===--- 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
}

let lcm = a.magnitude / gcd(a, b) * b.magnitude

guard let result = T(exactly: lcm) else {
fatalError("LCM \(lcm) is not representable as \(T.self).")
}

return result
}

/// 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<T: FixedWidthInteger>(_ a: T, _ b: T) throws(LeastCommonMultipleOverflowError<T>) -> 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<FixedWidthInteger>`.
///
/// 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 { }
3 changes: 2 additions & 1 deletion Tests/IntegerUtilitiesTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ See https://swift.org/LICENSE.txt for license information
add_library(IntegerUtilitiesTests
DivideTests.swift
DoubleWidthTests.swift
GCDTests.swift
GreatestCommonDivisorTests.swift
LeastCommonMultipleTests.swift
RotateTests.swift
SaturatingArithmeticTests.swift
ShiftTests.swift)
Expand Down
44 changes: 0 additions & 44 deletions Tests/IntegerUtilitiesTests/GCDTests.swift

This file was deleted.

44 changes: 44 additions & 0 deletions Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===--- GreatestCommonDivisorTests.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

@Suite("Greatest Common Divisor Tests")
struct GreatestCommonDivisorTests {
@Test("gcd") func gcdTests() async throws {
#expect(gcd(0, 0) == 0)
#expect(gcd(0, 1) == 1)
#expect(gcd(1, 0) == 1)
#expect(gcd(0, -1) == 1)
#expect(gcd(-1, 0) == 1)
#expect(gcd(1, 1) == 1)
#expect(gcd(1, 2) == 1)
#expect(gcd(2, 2) == 2)
#expect(gcd(4, 2) == 2)
#expect(gcd(6, 8) == 2)
#expect(gcd(77, 91) == 7)
#expect(gcd(24, -36) == 12)
#expect(gcd(-24, -36) == 12)
#expect(gcd(51, 34) == 17)
#expect(gcd(64, 96) == 32)
#expect(gcd(-64, 96) == 32)
#expect(gcd(4*7*19, 27*25) == 1)
#expect(gcd(16*315, 11*315) == 315)
#expect(gcd(97*67*53*27*8, 83*67*53*9*32) == 67*53*9*8)
#expect(gcd(Int.min, 2) == 2)
#expect(gcd(Int.max, Int.max) == Int.max)
#expect(gcd(0, Int.min) == Int.min.magnitude)
#expect(gcd(Int.min, 0) == Int.min.magnitude)
#expect(gcd(Int.min, Int.min) == Int.min.magnitude)
}
}
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_BinaryInteger<T: BinaryInteger>(_ a: T, _ b: T) -> T {
IntegerUtilities.lcm(a,b)
}

@Suite("Least Common Multiple Tests")
struct LeastCommonMultipleTests {
@Test("lcm<BinaryInteger>") func lcm_BinaryIntegerTest() async throws {

#expect(lcm_BinaryInteger(1024, 0) == 0)
#expect(lcm_BinaryInteger(0, 1024) == 0)
#expect(lcm_BinaryInteger(0, 0) == 0)
#expect(lcm_BinaryInteger(1024, 768) == 3072)
#expect(lcm_BinaryInteger(768, 1024) == 3072)
#expect(lcm_BinaryInteger(24, 18) == 72)
#expect(lcm_BinaryInteger(18, 24) == 72)
#expect(lcm_BinaryInteger(6930, 288) == 110880)
#expect(lcm_BinaryInteger(288, 6930) == 110880)
#expect(lcm_BinaryInteger(Int.max, 1) == Int.max)
#expect(lcm_BinaryInteger(1, Int.max) == Int.max)

#if compiler(>=6.2)
try await #expect(
#require(
String(
bytes: #require(processExitsWith: .failure, observing: [\.standardErrorContent]) {
_ = lcm_BinaryInteger(Int.min, Int.min)
}.standardErrorContent,
encoding: .utf8
)
).contains(
"Fatal error: LCM 9223372036854775808 is not representable as Int."
)
)
try await #expect(
#require(
String(
bytes: #require(processExitsWith: .failure, observing: [\.standardErrorContent]) {
_ = lcm_BinaryInteger(Int.min, 1)
}.standardErrorContent,
encoding: .utf8
)
).contains(
"Fatal error: LCM 9223372036854775808 is not representable as Int."
)
)
try await #expect(
#require(
String(
bytes: #require(processExitsWith: .failure, observing: [\.standardErrorContent]) {
_ = lcm_BinaryInteger(1, Int.min)
}.standardErrorContent,
encoding: .utf8
)
).contains(
"Fatal error: LCM 9223372036854775808 is not representable as Int."
)
)
await #expect(processExitsWith: .failure) {
_ = lcm_BinaryInteger(Int8.min, Int8.max)
}
#endif
}

@Test("lcm<FixedWidthInteger>") func lcm_FixedWidthIntegerTests() async throws {
func lcm<T: FixedWidthInteger>(_ a: T, _ b: T) throws -> T {
try IntegerUtilities.lcm(a,b)
}

#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<Int>(high: 0, low: Int.min.magnitude)) {
try lcm(Int.min, Int.min)
}
#expect(throws: LeastCommonMultipleOverflowError<Int>(high: 0, low: Int.min.magnitude)) {
try lcm(Int.min, 1)
}
#expect(throws: LeastCommonMultipleOverflowError<Int>(high: 0, low: Int.min.magnitude)) {
try lcm(1, Int.min)
}
#expect(throws: LeastCommonMultipleOverflowError<Int8>(high: 63, low: 128)) {
try lcm(Int8.min, Int8.max)
}
}
}