From f721b616e403c30387695f17e358d7f291e6a8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Sat, 2 Dec 2023 12:19:34 +0100 Subject: [PATCH] Added `words(_:)` page size to NBKPrimeSieve. Cleanup (#114). --- Sources/NBKCoreKit/Models/NBKPrimeSieve.swift | 161 ++++++++++-------- .../Models/NBKPrimeSieve.swift | 72 +++++++- 2 files changed, 161 insertions(+), 72 deletions(-) diff --git a/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift b/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift index 28534697..313d2224 100644 --- a/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift +++ b/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift @@ -54,11 +54,11 @@ public final class NBKPrimeSieve: CustomStringConvertible { /// @usableFromInline var cache: Cache - /// A cyclical pattern used to skip small prime multiples. + /// A cyclical pattern used to skip multiples of small primes. @usableFromInline let wheel: Wheel - /// A cyclical pattern used to cull small prime multiples. - @usableFromInline let small: [UInt] + /// A cyclical pattern used to cull multiples of small primes. + @usableFromInline let small: Small //=------------------------------------------------------------------------= // MARK: Initializers @@ -71,7 +71,7 @@ public final class NBKPrimeSieve: CustomStringConvertible { public init(size: Size) { self.cache = Cache(size: size) self.wheel = Wheel(primes: [2, 3, 5, 7]) - self.small = Self.pattern(primes: Array(wheel.primes[1...])) + self.small = Small(primes: Array(wheel.primes[1...])) self.state = Self.makeInitialState(&cache, wheel, small) } @@ -94,11 +94,6 @@ public final class NBKPrimeSieve: CustomStringConvertible { self.cache.count &<< 1 as UInt // OK, see size } - /// The number of bits in one number page. - @inlinable public var size: UInt { - self.cache.count - } - //=------------------------------------------------------------------------= // MARK: Utilities //=------------------------------------------------------------------------= @@ -135,7 +130,7 @@ public final class NBKPrimeSieve: CustomStringConvertible { /// /// It represents at most `Int.max` bits so the odd number stride fits. /// - /// - Requires: `words * UInt.bitWidth <= Int.max` + /// - Requires: `2 * UInt.bitWidth * words <= UInt.max` /// @usableFromInline let words: Int @@ -146,7 +141,7 @@ public final class NBKPrimeSieve: CustomStringConvertible { @usableFromInline init(@NBK.MoreThanZero words: Int) { self.words = words precondition(words <= (Int.max / UInt.bitWidth), - "number of elements per page must fit in UInt") + "the prime sieve's increment must fit in UInt") } //=--------------------------------------------------------------------= @@ -155,7 +150,12 @@ public final class NBKPrimeSieve: CustomStringConvertible { /// The size per page measured in KiB (i.e. 1024 B). @inlinable public static func KiB(_ count: Int) -> Self { - Self(words: 8 * 1024 / UInt.bitWidth * count) + Self(words: count * (8 * 1024 / UInt.bitWidth)) + } + + /// The size per page measured in words. + @inlinable public static func words(_ count: Int) -> Self { + Self(words: count) } } } @@ -182,7 +182,7 @@ extension NBKPrimeSieve { // mark composites not hit by the wheel //=--------------------------------------= precull: do { - var pattern = NBK.CyclicIterator(self.small)! + var pattern = NBK.CyclicIterator(self.small.pattern)! pattern.set(iteration: 1 &+ NBK.PBI.quotient(dividing: NBK.ZeroOrMore(self.limit &>> 1), by: NBK.PowerOf2(bitWidth: UInt.self))) self.cache.sieve(pattern: pattern) } @@ -236,7 +236,7 @@ extension NBKPrimeSieve { } } - @inline(never) @inlinable static func makeInitialState(_ cache: inout Cache, _ wheel: Wheel, _ small: [UInt]) -> State { + @inline(never) @inlinable static func makeInitialState(_ cache: inout Cache, _ wheel: Wheel, _ small: Small) -> State { Swift.assert(cache.base.allSatisfy({ $0.onesComplement().isZero })) //=--------------------------------------= let limit = cache.count * 2 - 1 // OK, see size @@ -247,7 +247,7 @@ extension NBKPrimeSieve { // mark each number in: 1, small //=--------------------------------------= cache.base[cache.base.startIndex] = ~1 - cache.sieve(pattern: NBK.CyclicIterator(small)!) + cache.sieve(pattern: NBK.CyclicIterator(small.pattern)!) //=--------------------------------------= // mark each number in: composites //=--------------------------------------= @@ -311,7 +311,7 @@ extension NBKPrimeSieve { /// ### Development /// - /// The possibility of size overflow is preconditioned by the size model. + /// Size overflow is prevented by the size model's preconditions. /// @frozen @usableFromInline struct Cache { @@ -325,19 +325,8 @@ extension NBKPrimeSieve { // MARK: Initializers //=--------------------------------------------------------------------= - /// Rounds the given `size` down to at least one machine word. - /// - /// ### On a 64-bit machine - /// - /// ``` - /// bits: 000 ..< 064 -> 064 - /// bits: 064 ..< 128 -> 064 - /// bits: 128 ..< 192 -> 128 - /// bits: 192 ..< 256 -> 192 - /// ``` - /// @inlinable init(size: Size) { - self.base = Array(repeating: UInt.max, count: Int(size.words)) + self.base = Array(repeating: UInt.max, count: size.words) } //=--------------------------------------------------------------------= @@ -403,7 +392,7 @@ extension NBKPrimeSieve { // MARK: State //=--------------------------------------------------------------------= - /// The primes used to create this wheel. + /// The primes used to create this instance. @usableFromInline let primes: [UInt] /// Returns the next `value` from the `index`. @@ -484,54 +473,90 @@ extension NBKPrimeSieve { } //=----------------------------------------------------------------------------= -// MARK: + Miscellaneous +// MARK: + Details x Small //=----------------------------------------------------------------------------= extension NBKPrimeSieve { - //=------------------------------------------------------------------------= - // MARK: Utilities - //=------------------------------------------------------------------------= + //*========================================================================* + // MARK: * Small + //*========================================================================* - @usableFromInline static func pattern(primes: [UInt]) -> [UInt] { - var pattern = [UInt](repeating: UInt.max, count: Int(primes.reduce(1, *))) - var next:(prime: UInt, product: UInt); next.prime = primes.first!; next.product = next.prime - var primeIndex = primes.startIndex; while primeIndex < primes.endIndex { - //=----------------------------------= - let current: (prime: UInt, product: UInt) = next - var patternIndex = NBK.PBI.dividing(NBK.ZeroOrMore(current.prime &>> 1), by: NBK.PowerOf2(bitWidth: UInt.self)) - while patternIndex.quotient < current.prime { - var chunk = UInt.zero - - while patternIndex.remainder < UInt.bitWidth { - chunk |= 1 &<< patternIndex.remainder - patternIndex.remainder &+= current.prime - }; chunk.formOnesComplement() - - var destination = patternIndex.quotient; while destination < current.product { - pattern[Int(bitPattern: destination)] &= chunk - destination &+= current.prime + /// A cyclical pattern used to cull multiples of small primes. + /// + /// ### Development + /// + /// Consider multiple patterns formed by chunking: + /// + /// [03, 67], [05, 61], [07, 59], + /// [11, 53], [13, 47], [17, 43], + /// [19, 41], [23, 37], [29, 31], + /// + @frozen @usableFromInline struct Small { + + //=--------------------------------------------------------------------= + // MARK: State + //=--------------------------------------------------------------------= + + /// The primes used to create this instance. + @usableFromInline var primes: [UInt] + + /// A cyclical pattern used to cull multiples of small primes. + @usableFromInline var pattern: [UInt] + + //=--------------------------------------------------------------------= + // MARK: Initializers + //=--------------------------------------------------------------------= + + @inlinable init(primes: [UInt]) { + self.primes = primes + self.pattern = Self.pattern(primes: primes) + } + + //=--------------------------------------------------------------------= + // MARK: Utilities + //=--------------------------------------------------------------------= + + @usableFromInline static func pattern(primes: [UInt]) -> [UInt] { + var pattern = [UInt](repeating: UInt.max, count: Int(primes.reduce(1, *))) + var next:(prime: UInt, product: UInt); next.prime = primes.first!; next.product = next.prime + var primeIndex = primes.startIndex; while primeIndex < primes.endIndex { + //=----------------------------------= + let current: (prime: UInt, product: UInt) = next + var patternIndex = NBK.PBI.dividing(NBK.ZeroOrMore(current.prime &>> 1), by: NBK.PowerOf2(bitWidth: UInt.self)) + while patternIndex.quotient < current.prime { + var chunk = UInt.zero + + while patternIndex.remainder < UInt.bitWidth { + chunk |= 1 &<< patternIndex.remainder + patternIndex.remainder &+= current.prime + }; chunk.formOnesComplement() + + var destination = patternIndex.quotient; while destination < current.product { + pattern[Int(bitPattern: destination)] &= chunk + destination &+= current.prime + } + + patternIndex.quotient &+= NBK.PBI.quotient (dividing: NBK.ZeroOrMore(patternIndex.remainder), by: NBK.PowerOf2(bitWidth: UInt.self)) + patternIndex.remainder = NBK.PBI.remainder(dividing: NBK.ZeroOrMore(patternIndex.remainder), by: NBK.PowerOf2(bitWidth: UInt.self)) + } + //=----------------------------------= + primes.formIndex(after: &primeIndex) + //=----------------------------------= + if primeIndex < primes.endIndex { + next.prime = primes[primeIndex] + next.product &*= next.prime } - patternIndex.quotient &+= NBK.PBI.quotient (dividing: NBK.ZeroOrMore(patternIndex.remainder), by: NBK.PowerOf2(bitWidth: UInt.self)) - patternIndex.remainder = NBK.PBI.remainder(dividing: NBK.ZeroOrMore(patternIndex.remainder), by: NBK.PowerOf2(bitWidth: UInt.self)) - } - //=----------------------------------= - primes.formIndex(after: &primeIndex) - //=----------------------------------= - if primeIndex < primes.endIndex { - next.prime = primes[primeIndex] - next.product &*= next.prime - } - - var destination = current.product; while destination < next.product { - for source in 0 as UInt ..< current.product { - pattern[Int(bitPattern: destination)] &= pattern[Int(bitPattern: source)] - destination += 1 as UInt + var destination = current.product; while destination < next.product { + for source in 0 as UInt ..< current.product { + pattern[Int(bitPattern: destination)] &= pattern[Int(bitPattern: source)] + destination += 1 as UInt + } } } + + return pattern as [UInt] as [UInt] as [UInt] } - - return pattern as [UInt] as [UInt] as [UInt] } } diff --git a/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift b/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift index 057577e0..d7404abd 100644 --- a/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift +++ b/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift @@ -151,8 +151,69 @@ final class NBKPrimeSieveTests: XCTestCase { // MARK: Tests //=------------------------------------------------------------------------= - func test001KiB() { + func testBit064() { + let ((sieve)) = T(size: .words( 0064 / UInt.bitWidth)) + check(sieve, limit: 0127, count: 0031, last: 127) + + sieve.increment() + check(sieve, limit: 0255, count: 0054, last: 251) + + sieve.increment() + check(sieve, limit: 0383, count: 0076, last: 383) + + sieve.increment() + check(sieve, limit: 0511, count: 0097, last: 509) + } + + func testBit128() { + let ((sieve)) = T(size: .words( 0128 / UInt.bitWidth)) + check(sieve, limit: 0255, count: 0054, last: 0251) + + sieve.increment() + check(sieve, limit: 0511, count: 0097, last: 0509) + + sieve.increment() + check(sieve, limit: 0767, count: 0135, last: 0761) + + sieve.increment() + check(sieve, limit: 1023, count: 0172, last: 1021) + } + + func testBit192() { + let ((sieve)) = T(size: .words( 0192 / UInt.bitWidth)) + check(sieve, limit: 0383, count: 0076, last: 0383) + + sieve.increment() + check(sieve, limit: 0767, count: 0135, last: 0761) + + sieve.increment() + check(sieve, limit: 1151, count: 0190, last: 1151) + + sieve.increment() + check(sieve, limit: 1535, count: 0242, last: 1531) + } + + func testBit256() { + let ((sieve)) = T(size: .words( 0256 / UInt.bitWidth)) + check(sieve, limit: 0511, count: 0097, last: 0509) + + sieve.increment() + check(sieve, limit: 1023, count: 0172, last: 1021) + + sieve.increment() + check(sieve, limit: 1535, count: 0242, last: 1531) + + sieve.increment() + check(sieve, limit: 2047, count: 0309, last: 2039) + } + + //=------------------------------------------------------------------------= + // MARK: Tests x KiB + //=------------------------------------------------------------------------= + + func testKiB001() { let ((sieve)) = T(size: .KiB(001)) + XCTAssertEqual(sieve.stride,16384) check(sieve, limit: 016383, count: 01900, last: 016381) sieve.increment() @@ -165,8 +226,9 @@ final class NBKPrimeSieveTests: XCTestCase { check(sieve, limit: 065535, count: 06542, last: 065521) } - func test002KiB() { + func testKiB002() { let ((sieve)) = T(size: .KiB(002)) + XCTAssertEqual(sieve.stride,32768) check(sieve, limit: 032767, count: 03512, last: 032749) sieve.increment() @@ -179,8 +241,9 @@ final class NBKPrimeSieveTests: XCTestCase { check(sieve, limit: 131071, count: 12251, last: 131071) } - func test003KiB() { + func testKiB003() { let ((sieve)) = T(size: .KiB(003)) + XCTAssertEqual(sieve.stride,49152) check(sieve, limit: 049151, count: 05051, last: 049139) sieve.increment() @@ -193,8 +256,9 @@ final class NBKPrimeSieveTests: XCTestCase { check(sieve, limit: 196607, count: 17704, last: 196597) } - func test004KiB() { + func testKiB004() { let ((sieve)) = T(size: .KiB(004)) + XCTAssertEqual(sieve.stride,65536) check(sieve, limit: 065535, count: 06542, last: 065521) sieve.increment()