From 753c0d7d85277c2f52d4e52b073a6d26dfa96fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Wed, 22 Nov 2023 09:26:31 +0100 Subject: [PATCH] [NBKCoreKit] Square root by Newton's method. --- .../NBKProperBinaryInteger+Roots.swift | 73 ++++++++++++++++++ .../NBKProperBinaryInteger+Roots.swift | 68 ++++++++++++++++ .../NBKProperBinaryInteger+Roots.swift | 77 +++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 Sources/NBKCoreKit/Private/NBKProperBinaryInteger+Roots.swift create mode 100644 Tests/NBKCoreKitBenchmarks/Private/NBKProperBinaryInteger+Roots.swift create mode 100644 Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+Roots.swift diff --git a/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+Roots.swift b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+Roots.swift new file mode 100644 index 00000000..1efec085 --- /dev/null +++ b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+Roots.swift @@ -0,0 +1,73 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Numberick open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Roots x Unsigned +//*============================================================================* + +extension NBK.ProperBinaryInteger { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + /// Returns the integer `square root` of `power` by using [this algorithm][algorithm]. + /// + /// [algorithm]: https://en.wikipedia.org/wiki/newton%27s_method + /// + /// - Parameter power: A value in `Integer.zero ... Integer.max`. + /// + @inlinable public static func squareRootByNewtonsMethod(of power: Integer) -> Integer { + precondition(!power.isLessThanZero, NBK.callsiteOutOfBoundsInfo()) + return Integer(magnitude: Magnitude.squareRootByNewtonsMethod(of: power.magnitude))! + } +} + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Roots x Unsigned +//*============================================================================* + +extension NBK.ProperBinaryInteger where Integer: NBKUnsignedInteger { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + /// Returns the integer `square root` of `power` by using [this algorithm][algorithm]. + /// + /// [algorithm]: https://en.wikipedia.org/wiki/newton%27s_method + /// + /// - Parameter power: A value in `Integer.zero ... Integer.max`. + /// + /// ### Development + /// + /// - TODO: Consider `update(_:)` when NBKBinaryInteger gets it. + /// - TODO: Consider `bitShift[...](by:)` when NBKBinaryInteger gets it. + /// + @inlinable public static func squareRootByNewtonsMethod(of power: Integer) -> Integer { + //=--------------------------------------= + if power.isZero { + return power + } + //=--------------------------------------= + var guess: (Integer,Integer) + guess.0 = Integer(digit: 1) << ((power.bitWidth &- power.leadingZeroBitCount) &>> 1 &+ 1) + //=--------------------------------------= + repeat { + + guess.1 = guess.0 + guess.0 = power + guess.0 /= guess.1 + guess.0 += guess.1 + guess.0 >>= Int.one + + } while guess.0 < guess.1 + return (guess.1) + } +} diff --git a/Tests/NBKCoreKitBenchmarks/Private/NBKProperBinaryInteger+Roots.swift b/Tests/NBKCoreKitBenchmarks/Private/NBKProperBinaryInteger+Roots.swift new file mode 100644 index 00000000..64d9ace8 --- /dev/null +++ b/Tests/NBKCoreKitBenchmarks/Private/NBKProperBinaryInteger+Roots.swift @@ -0,0 +1,68 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Numberick open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +#if !DEBUG + +import NBKCoreKit +import XCTest + +private typealias X = [UInt] +private typealias X64 = [UInt64] +private typealias X32 = [UInt32] + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Roots +//*============================================================================* + +final class NBKProperBinaryIntegerBenchmarksOnRoots: XCTestCase { + + typealias T = NBK.PBI + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + func testSquareRootAsInt32() { + var power = NBK.blackHoleIdentity(Int32.max) + + for _ in 0 ... 5_000_000 { + NBK.blackHole(T.squareRootByNewtonsMethod(of: power)) + NBK.blackHoleInoutIdentity(&power) + } + } + + func testSquareRootAsInt64() { + var power = NBK.blackHoleIdentity(Int64.max) + + for _ in 0 ... 5_000_000 { + NBK.blackHole(T.squareRootByNewtonsMethod(of: power)) + NBK.blackHoleInoutIdentity(&power) + } + } + + func testSquareRootAsUInt32() { + var power = NBK.blackHoleIdentity(UInt32.max) + + for _ in 0 ... 5_000_000 { + NBK.blackHole(T.squareRootByNewtonsMethod(of: power)) + NBK.blackHoleInoutIdentity(&power) + } + } + + func testSquareRootAsUInt64() { + var power = NBK.blackHoleIdentity(UInt64.max) + + for _ in 0 ... 5_000_000 { + NBK.blackHole(T.squareRootByNewtonsMethod(of: power)) + NBK.blackHoleInoutIdentity(&power) + } + } +} + +#endif diff --git a/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+Roots.swift b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+Roots.swift new file mode 100644 index 00000000..16ed8825 --- /dev/null +++ b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+Roots.swift @@ -0,0 +1,77 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Numberick open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +import NBKCoreKit +import XCTest + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Roots +//*============================================================================* + +final class NBKProperBinaryIntegerTestsOnRoots: XCTestCase { + + typealias T = NBK.PBI + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + func testSquareRoot() { + NBKAssertSquareRoot( Int8 .max, 0000000011) + NBKAssertSquareRoot( Int16.max, 0000000181) + NBKAssertSquareRoot( Int32.max, 0000046340) + NBKAssertSquareRoot( Int64.max, 3037000499) + + NBKAssertSquareRoot(UInt8 .max, UInt8 .max >> 4) + NBKAssertSquareRoot(UInt16.max, UInt16.max >> 8) + NBKAssertSquareRoot(UInt32.max, UInt32.max >> 16) + NBKAssertSquareRoot(UInt64.max, UInt64.max >> 32) + + for base in (0 as Int64) ... (100 as Int64) { + for power in (base * base) ..< (base + 1) * (base + 1) { + NBKAssertSquareRoot(power, base) + } + } + } +} + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Roots x Assertions +//*============================================================================* + +private func NBKAssertSquareRoot( +_ power: T, _ expectation: T, +file: StaticString = #file, line: UInt = #line) { + //=------------------------------------------= + NBKAssertSquareRootAsUnsigned(power.magnitude, expectation.magnitude, file: file, line: line) + //=------------------------------------------= + let root = NBK.PBI.squareRootByNewtonsMethod(of: power) + XCTAssertGreaterThanOrEqual(power, T.zero, file: file, line: line) + XCTAssertEqual(root.magnitude, expectation.magnitude, file: file, line: line) +} + +private func NBKAssertSquareRootAsUnsigned( +_ power: T, _ expectation: T, +file: StaticString = #file, line: UInt = #line) { + //=------------------------------------------= + let root = NBK.PBI.squareRootByNewtonsMethod(of: power) + let product0 = (root + 0).multipliedReportingOverflow(by: root + 0) + let product1 = (root + 1).multipliedReportingOverflow(by: root + 1) + //=------------------------------------------= + XCTAssertEqual(root, expectation, file: file, line: line) + XCTAssertFalse(product0.overflow) + + if !product0.overflow { + XCTAssert(power >= product0.partialValue, file: file, line: line) + } + + if !product1.overflow { + XCTAssert(power < product1.partialValue, file: file, line: line) + } +}