diff --git a/Sources/IntegerUtilities/CMakeLists.txt b/Sources/IntegerUtilities/CMakeLists.txt index db167c77..6dcac644 100644 --- a/Sources/IntegerUtilities/CMakeLists.txt +++ b/Sources/IntegerUtilities/CMakeLists.txt @@ -9,7 +9,7 @@ See https://swift.org/LICENSE.txt for license information add_library(IntegerUtilities DivideWithRounding.swift - GCD.swift + GreatestCommonDivisor.swift Rotate.swift RoundingRule.swift SaturatingArithmetic.swift diff --git a/Sources/IntegerUtilities/GCD.swift b/Sources/IntegerUtilities/GCD.swift deleted file mode 100644 index 28e09bdb..00000000 --- a/Sources/IntegerUtilities/GCD.swift +++ /dev/null @@ -1,40 +0,0 @@ -//===--- GCD.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. -/// -/// 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. -/// -/// gcd(Int.min, Int.min) // Overflow error -/// gcd(Int.min, 0) // Overflow error -/// -/// [gcd]: https://en.wikipedia.org/wiki/Greatest_common_divisor -@inlinable -public func gcd(_ a: T, _ b: T) -> T { - var x = a - var y = b - if x.magnitude < y.magnitude { swap(&x, &y) } - // Avoid overflow when x = signed min, y = -1. - if y.magnitude == 1 { return 1 } - // 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) } - // Try to convert result to T. - if let result = T(exactly: x.magnitude) { return result } - // If that fails, produce a diagnostic. - fatalError("GCD (\(x)) is not representable as \(T.self).") -} diff --git a/Sources/IntegerUtilities/GreatestCommonDivisor.swift b/Sources/IntegerUtilities/GreatestCommonDivisor.swift new file mode 100644 index 00000000..3104c7ef --- /dev/null +++ b/Sources/IntegerUtilities/GreatestCommonDivisor.swift @@ -0,0 +1,79 @@ +//===--- GreatestCommonDivisor.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 [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. +/// +/// 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. +/// +/// gcd(Int.min, Int.min) // Overflow error +/// gcd(Int.min, 0) // Overflow error +/// +/// [gcd]: https://en.wikipedia.org/wiki/Greatest_common_divisor +@inlinable +public func gcd(_ a: T, _ b: T) -> T { + let gcd = greatestCommonDivisorFullWidth(a, b) + + guard let result = T(exactly: gcd) else { + fatalError("GCD (\(gcd)) is not representable as \(T.self).") + } + + return result +} + +/// Returns the [greatest common divisor][gcd] of `a` and `b`, along with a Boolean value indicating whether overflow occurred in the operation. +/// +/// If both inputs are zero, the result is zero. If one input is zero, the +/// result is the absolute value of the other input. +/// +/// - 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. +/// +/// [gcd]: https://en.wikipedia.org/wiki/Greatest_common_divisor +@inlinable +public func greatestCommonDivisorReportingOverflow(_ a: T, _ b: T) -> (partialValue: T, overflow: Bool) { + let gcd = greatestCommonDivisorFullWidth(a, b) + + guard let result = T(exactly: gcd) else { + return (partialValue: T(truncatingIfNeeded: gcd), overflow: true) + } + + return (partialValue: result, overflow: false) +} + +/// 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 greatestCommonDivisorFullWidth(_ a: T, _ b: T) -> T.Magnitude { + var x = a.magnitude + var y = b.magnitude + + if x < y { + 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 +} diff --git a/Tests/IntegerUtilitiesTests/CMakeLists.txt b/Tests/IntegerUtilitiesTests/CMakeLists.txt index 15376625..9fd0cd7a 100644 --- a/Tests/IntegerUtilitiesTests/CMakeLists.txt +++ b/Tests/IntegerUtilitiesTests/CMakeLists.txt @@ -10,7 +10,7 @@ See https://swift.org/LICENSE.txt for license information add_library(IntegerUtilitiesTests DivideTests.swift DoubleWidthTests.swift - GCDTests.swift + GreatestCommonDivisorTests.swift RotateTests.swift SaturatingArithmeticTests.swift ShiftTests.swift) diff --git a/Tests/IntegerUtilitiesTests/GCDTests.swift b/Tests/IntegerUtilitiesTests/GCDTests.swift deleted file mode 100644 index 6400732c..00000000 --- a/Tests/IntegerUtilitiesTests/GCDTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -//===--- GCDTests.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 XCTest - -final class IntegerUtilitiesGCDTests: XCTestCase { - func testGCDInt() { - XCTAssertEqual(gcd(0, 0), 0) - XCTAssertEqual(gcd(0, 1), 1) - XCTAssertEqual(gcd(1, 0), 1) - XCTAssertEqual(gcd(0, -1), 1) - XCTAssertEqual(gcd(1, 1), 1) - XCTAssertEqual(gcd(1, 2), 1) - XCTAssertEqual(gcd(2, 2), 2) - XCTAssertEqual(gcd(4, 2), 2) - XCTAssertEqual(gcd(6, 8), 2) - XCTAssertEqual(gcd(77, 91), 7) - XCTAssertEqual(gcd(24, -36), 12) - XCTAssertEqual(gcd(-24, -36), 12) - XCTAssertEqual(gcd(51, 34), 17) - XCTAssertEqual(gcd(64, 96), 32) - XCTAssertEqual(gcd(-64, 96), 32) - 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) - - // TODO: Enable these when version compatibility allows. - // - // XCTExpectFailure{ gcd(0, Int.min) } - // XCTExpectFailure{ gcd(Int.min, 0) } - // XCTExpectFailure{ gcd(Int.min, Int.min) } - } -} diff --git a/Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift b/Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift new file mode 100644 index 00000000..5745e072 --- /dev/null +++ b/Tests/IntegerUtilitiesTests/GreatestCommonDivisorTests.swift @@ -0,0 +1,103 @@ +//===--- GreatestCommonDivisorTests.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 +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import IntegerUtilities +import Testing + +struct `Greatest Common Divisor Tests` { + @Test func `gcd()`() 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.max, Int.max) == Int.max) + #expect(gcd(Int.min, -1) == 1) + await #expect(processExitsWith: .failure) { + _ = gcd(0, Int.min) + } + await #expect(processExitsWith: .failure) { + _ = gcd(Int.min, 0) + } + await #expect(processExitsWith: .failure) { + _ = gcd(Int.min, Int.min) + } + } + + @Test func `greatestCommonDivisorReportingOverflow()`() async throws { + #expect(greatestCommonDivisorReportingOverflow(0, 0) == (partialResult: 0, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(0, 1) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(1, 0) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(0, -1) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(-1, 0) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(1, 1) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(1, 2) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(2, 2) == (partialResult: 2, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(4, 2) == (partialResult: 2, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(6, 8) == (partialResult: 2, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(77, 91) == (partialResult: 7, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(24, -36) == (partialResult: 12, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(-24, -36) == (partialResult: 12, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(51, 34) == (partialResult: 17, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(64, 96) == (partialResult: 32, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(-64, 96) == (partialResult: 32, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(4*7*19, 27*25) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(16*315, 11*315) == (partialResult: 315, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(97*67*53*27*8, 83*67*53*9*32) == (partialResult: 67*53*9*8, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(Int.max, Int.max) == (partialResult: Int.max, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(Int.min, -1) == (partialResult: 1, overflow: false)) + #expect(greatestCommonDivisorReportingOverflow(0, Int.min) == (partialResult: Int(truncatingIfNeeded: Int.min.magnitude), overflow: true)) + #expect(greatestCommonDivisorReportingOverflow(Int.min, 0) == (partialResult: Int(truncatingIfNeeded: Int.min.magnitude), overflow: true)) + #expect(greatestCommonDivisorReportingOverflow(Int.min, Int.min) == (partialResult: Int(truncatingIfNeeded: Int.min.magnitude), overflow: true)) + } + + @Test func `greatestCommonDivisorFullWidth()`() async throws { + #expect(greatestCommonDivisorFullWidth(0, 0) == 0) + #expect(greatestCommonDivisorFullWidth(0, 1) == 1) + #expect(greatestCommonDivisorFullWidth(1, 0) == 1) + #expect(greatestCommonDivisorFullWidth(0, -1) == 1) + #expect(greatestCommonDivisorFullWidth(-1, 0) == 1) + #expect(greatestCommonDivisorFullWidth(1, 1) == 1) + #expect(greatestCommonDivisorFullWidth(1, 2) == 1) + #expect(greatestCommonDivisorFullWidth(2, 2) == 2) + #expect(greatestCommonDivisorFullWidth(4, 2) == 2) + #expect(greatestCommonDivisorFullWidth(6, 8) == 2) + #expect(greatestCommonDivisorFullWidth(77, 91) == 7) + #expect(greatestCommonDivisorFullWidth(24, -36) == 12) + #expect(greatestCommonDivisorFullWidth(-24, -36) == 12) + #expect(greatestCommonDivisorFullWidth(51, 34) == 17) + #expect(greatestCommonDivisorFullWidth(64, 96) == 32) + #expect(greatestCommonDivisorFullWidth(-64, 96) == 32) + #expect(greatestCommonDivisorFullWidth(4*7*19, 27*25) == 1) + #expect(greatestCommonDivisorFullWidth(16*315, 11*315) == 315) + #expect(greatestCommonDivisorFullWidth(97*67*53*27*8, 83*67*53*9*32) == 67*53*9*8) + #expect(greatestCommonDivisorFullWidth(Int.max, Int.max) == Int.max) + #expect(greatestCommonDivisorFullWidth(Int.min, -1) == 1) + #expect(greatestCommonDivisorFullWidth(0, Int.min) == Int.min.magnitude) + #expect(greatestCommonDivisorFullWidth(Int.min, 0) == Int.min.magnitude) + #expect(greatestCommonDivisorFullWidth(Int.min, Int.min) == Int.min.magnitude) + } +}