From 51c51a78e55585b1572c406d2918024b46c8671e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Tue, 12 Nov 2024 05:59:11 +0100 Subject: [PATCH 1/5] Overhaul of Fibonacci (#8) (#126). This overhaul extends the sequence to negative indices and introduces more powerful error handling. It also adds various functions and two free-form sequences: Indexacci and Tupleacci. --- Sources/FibonacciKit/Fibonacci+Fast.swift | 87 +++++ Sources/FibonacciKit/Fibonacci+Stride.swift | 190 +++++++++++ Sources/FibonacciKit/Fibonacci+Text.swift | 25 ++ Sources/FibonacciKit/Fibonacci+Toggle.swift | 69 ++++ Sources/FibonacciKit/Fibonacci.swift | 261 ++++++--------- Sources/FibonacciKit/Indexacci+Stride.swift | 47 +++ Sources/FibonacciKit/Indexacci+Text.swift | 25 ++ Sources/FibonacciKit/Indexacci.swift | 53 +++ Sources/FibonacciKit/Tupleacci+Stride.swift | 49 +++ Sources/FibonacciKit/Tupleacci+Text.swift | 25 ++ Sources/FibonacciKit/Tupleacci.swift | 53 +++ Tests/Benchmarks/BinaryInteger+Geometry.swift | 2 +- Tests/Benchmarks/Fibonacci.swift | 46 +-- .../Fibonacci+Invariants.swift | 26 +- Tests/FibonacciKitTests/Fibonacci+Small.swift | 309 ++++++++++++++++++ .../FibonacciKitTests/Fibonacci+Stride.swift | 160 ++++----- Tests/FibonacciKitTests/Fibonacci.swift | 82 +++-- .../BinaryInteger+Factorial.swift | 6 +- .../BinaryInteger+Fibonacci.swift | 151 +++++++++ 19 files changed, 1350 insertions(+), 316 deletions(-) create mode 100644 Sources/FibonacciKit/Fibonacci+Fast.swift create mode 100644 Sources/FibonacciKit/Fibonacci+Stride.swift create mode 100644 Sources/FibonacciKit/Fibonacci+Text.swift create mode 100644 Sources/FibonacciKit/Fibonacci+Toggle.swift create mode 100644 Sources/FibonacciKit/Indexacci+Stride.swift create mode 100644 Sources/FibonacciKit/Indexacci+Text.swift create mode 100644 Sources/FibonacciKit/Indexacci.swift create mode 100644 Sources/FibonacciKit/Tupleacci+Stride.swift create mode 100644 Sources/FibonacciKit/Tupleacci+Text.swift create mode 100644 Sources/FibonacciKit/Tupleacci.swift create mode 100644 Tests/FibonacciKitTests/Fibonacci+Small.swift create mode 100644 Tests/UltimathnumTests/BinaryInteger+Fibonacci.swift diff --git a/Sources/FibonacciKit/Fibonacci+Fast.swift b/Sources/FibonacciKit/Fibonacci+Fast.swift new file mode 100644 index 00000000..d8d2ad54 --- /dev/null +++ b/Sources/FibonacciKit/Fibonacci+Fast.swift @@ -0,0 +1,87 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Fibonacci x Fast +//*============================================================================* + +extension Fibonacci { + + //=------------------------------------------------------------------------= + // MARK: Initializers + //=------------------------------------------------------------------------= + + /// Returns the sequence pair at `index`, or `nil`. + /// + /// ### Fibonacci + /// + /// - Note: It produces `nil` of the operation is `lossy`. + /// + @inlinable public init?(_ index: consuming Element) { + let base = Self.exactly(index, as: Indexacci.self) + guard let base = base?.optional() else { return nil } + self.init(unsafe: base) + } +} + +//=----------------------------------------------------------------------------= +// MARK: + Algorithms +//=----------------------------------------------------------------------------= + +extension Fibonacci { + + //=------------------------------------------------------------------------= + // MARK: Initializers + //=------------------------------------------------------------------------=s + + @inlinable package static func exactly( + _ index: consuming Element, + as type: Indexacci.Type = Indexacci.self + ) -> Optional>> { + + guard let tuple: Fallible = Self.exactly(index) else { + return nil + } + + return Indexacci(tuple: tuple.value, index: index).veto(tuple.error) + } + + @inlinable package static func exactly( + _ index: borrowing Element, + as type: Tupleacci.Type = Tupleacci.self + ) -> Optional>> { + + if index.isInfinite { + return nil + } + + var value = Tupleacci.fibonacci() + var error = false + let limit = UX(raw: index.nondescending(index.appendix)) + let minus = U8(Bit( index.isNegative)) + + index.withUnsafeBinaryIntegerBody(as: U8.self) { + for count in (0 ..< limit).reversed() { + value = Self.doubled(value).sink(&error) + + if $0[unchecked: IX(raw: count) &>> 3] &>> U8(load: count) & 1 != minus { + value = value.incremented().sink(&error) + } + } + } + + if !minus.isZero { + value = Self.toggled(value, index: index.lsb.toggled()).sink(&error) + } + + return value.veto(error) + } +} diff --git a/Sources/FibonacciKit/Fibonacci+Stride.swift b/Sources/FibonacciKit/Fibonacci+Stride.swift new file mode 100644 index 00000000..cb27958e --- /dev/null +++ b/Sources/FibonacciKit/Fibonacci+Stride.swift @@ -0,0 +1,190 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Fibonacci x Stride +//*============================================================================* + +extension Fibonacci { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + /// Returns the sequence pair at `index + 1`, or `nil`. + /// + /// ### Fibonacci + /// + /// - Note: It produces `nil` of the operation is `lossy`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func incremented() -> Optional { + self.base.incremented().map(Self.init(unsafe:)).optional() + } + + /// Returns the sequence pair at `index - 1`, or `nil`.. + /// + /// ### Fibonacci + /// + /// - Note: It produces `nil` of the operation is `lossy`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func decremented() -> Optional { + self.base.decremented().map(Self.init(unsafe:)).optional() + } + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + /// Returns the sequence pair at `index * 2`, or `nil`. + /// + /// ### Fibonacci + /// + /// - Note: It produces `nil` of the operation is `lossy`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func doubled() -> Optional { + Self.doubled(self.base).map(Self.init(unsafe:)).optional() + } + + /// Returns the sequence pair at `index + other.index`, or `nil`. + /// + /// ### Fibonacci + /// + /// - Note: It produces `nil` of the operation is `lossy`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func incremented(by other: borrowing Self) -> Optional { + Self.incremented(self.base, by: other.base).map(Self.init(unsafe:)).optional() + } + + /// Returns the sequence pair at `index - other.index`, or `nil`. + /// + /// ### Fibonacci + /// + /// - Note: It produces `nil` of the operation is `lossy`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func decremented(by other: borrowing Self) -> Optional { + Self.decremented(self.base, by: other.base).map(Self.init(unsafe:)).optional() + } +} + +//=----------------------------------------------------------------------------= +// MARK: + Algorithms +//=----------------------------------------------------------------------------= + +extension Fibonacci { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + @inlinable package static func doubled( + _ value: consuming Indexacci + ) -> Fallible> { + + var (error) = false + value.tuple = Self.doubled(value.tuple).sink(&error) + value.index = ((value)).index.doubled().sink(&error) + return value.veto(error) + } + + @inlinable package static func doubled( + _ value: consuming Tupleacci + ) -> Fallible> { + + var (error) = false + let (extra) = value.major.doubled().sink(&error).minus(value.minor).sink(&error).times(value.minor).sink(&error) + value.major = value.major.squared().sink(&error).plus((value.minor).squared().sink(&((((error)))))).sink(&error) + value.minor = extra + return value.veto(error) + } + + @inlinable package static func incremented( + _ value: consuming Indexacci, + by other: borrowing Indexacci + ) -> Fallible> { + + var (error) = false + let (fails) = value.index.isNegative == other.index.isNegative + value.index = value.index.plus(other.index).sink(&error) + value.tuple = incremented(value.tuple, by: other.tuple).sink(&error) + return value.veto(fails && error) + + func incremented( + _ value: consuming Tupleacci, + by other: borrowing Tupleacci + ) -> Fallible> { + + var (error) = false + let (extra) = value.major.times(other.major).sink(&error).plus(value.minor.times(other.minor).sink(&error)).sink(&error) + value.major = value.major.minus(value.minor).sink(&error) + value.minor = value.minor.times(other.major).sink(&error).plus(value.major.times(other.minor).sink(&error)).sink(&error) + value.major = extra + return value.veto(error) + } + } + + @inlinable package static func decremented( + _ value: consuming Indexacci, + by other: borrowing Indexacci + ) -> Fallible> { + + var (error) = false + var (fails) = value.index.isNegative != other.index.isNegative + value.index = value.index.minus(other.index).sink(&error) + + if !Element.isSigned { + (fails) = error + } + + value.tuple = decremented(value.tuple, by: other.tuple, index: other.index.lsb).sink(&error) + return value.veto(fails && error) + + func decremented( + _ value: consuming Tupleacci, + by other: borrowing Tupleacci, index: Bit + ) -> Fallible> { + + var (error) = false + var (((a))) = value.minor.times(other.major).sink(&error) + var (((b))) = value.major.times(other.minor).sink(&error) + var (((c))) = value.major.times(other.major).sink(&error) + var (((d))) = value.major.plus (value.minor).sink(&error).times(other.minor).sink(&error) + + if Bool(index) { + Swift.swap(&a, &b) + Swift.swap(&c, &d) + } + + value.minor = a.minus(b).sink(&error) + value.major = c.minus(d).sink(&error) + return value.veto(error) + } + } +} diff --git a/Sources/FibonacciKit/Fibonacci+Text.swift b/Sources/FibonacciKit/Fibonacci+Text.swift new file mode 100644 index 00000000..537db4ec --- /dev/null +++ b/Sources/FibonacciKit/Fibonacci+Text.swift @@ -0,0 +1,25 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Fibonacci x Text +//*============================================================================* + +extension Fibonacci { + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + @inlinable public var description: String { + self.base.description + } +} diff --git a/Sources/FibonacciKit/Fibonacci+Toggle.swift b/Sources/FibonacciKit/Fibonacci+Toggle.swift new file mode 100644 index 00000000..9f83f0db --- /dev/null +++ b/Sources/FibonacciKit/Fibonacci+Toggle.swift @@ -0,0 +1,69 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Fibonacci x Toggle +//*============================================================================* + +extension Fibonacci { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + /// Returns the sequence pair reflected about `-1/2`, or `nil`. + /// + /// ### Fibonacci + /// + /// - Note: It produces `nil` of the operation is `lossy`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func toggled() -> Optional { + Self.toggled(self.base).map(Self.init(unsafe:)).optional() + } +} + +//=----------------------------------------------------------------------------= +// MARK: + Algorithms +//=----------------------------------------------------------------------------= + +extension Fibonacci { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + @inlinable package static func toggled( + _ value: consuming Indexacci + ) -> Fallible> { + + var (error) = false + value.tuple = Self.toggled(value.tuple, index: value.index.lsb).sink(&error) + value.index = ((value)).index.toggled() + return value.veto(error) + } + + @inlinable package static func toggled( + _ value: consuming Tupleacci, index: Bit + ) -> Fallible> { + + if Bool(index) { + value.major.negate().discard() + } else { + value.minor.negate().discard() + } + + return value.swapped().veto(!Element.isSigned) + } +} diff --git a/Sources/FibonacciKit/Fibonacci.swift b/Sources/FibonacciKit/Fibonacci.swift index aacfbe79..927de3d4 100644 --- a/Sources/FibonacciKit/Fibonacci.swift +++ b/Sources/FibonacciKit/Fibonacci.swift @@ -13,210 +13,149 @@ import CoreKit // MARK: * Fibonacci //*============================================================================* -/// The Fibonacci [sequence](https://en.wikipedia.org/wiki/fibonacci_sequence)\. +/// The [Fibonacci][info] sequence. /// -/// It is represented by an index and two consecutive elements. +/// F(x) == F(x-1) + F(x-2) where F(0) == 0 and F(1) == 1 /// -/// ```swift -/// Fibonacci(0) // (index: 0, element: 0, next: 1) -/// Fibonacci(1) // (index: 1, element: 1, next: 1) -/// Fibonacci(2) // (index: 2, element: 1, next: 2) -/// Fibonacci(3) // (index: 3, element: 2, next: 3) -/// Fibonacci(4) // (index: 4, element: 3, next: 5) -/// Fibonacci(5) // (index: 5, element: 5, next: 8) -/// ``` -/// -/// ### Fast double-and-add algorithm -/// -/// The fast double-and-add algorithm is powered by this observation: +/// It is represented by two consecutive elements and an index. /// /// ```swift -/// f(x + 1 + 0) == f(x) * 0000 + f(x + 1) * 00000001 -/// f(x + 1 + 1) == f(x) * 0001 + f(x + 1) * 00000001 -/// f(x + 1 + 2) == f(x) * 0001 + f(x + 1) * 00000002 -/// f(x + 1 + 3) == f(x) * 0002 + f(x + 1) * 00000003 -/// f(x + 1 + 4) == f(x) * 0003 + f(x + 1) * 00000005 -/// f(x + 1 + 5) == f(x) * 0005 + f(x + 1) * 00000008 -/// ------------------------------------------------- -/// f(x + 1 + y) == f(x) * f(y) + f(x + 1) * f(y + 1) -/// ``` -/// -/// Going the other direction is a bit more complicated, but not much: +/// Fibonacci(-12) // (nil) +/// Fibonacci(-11) // (minor: 89, major: -55, index: -11) +/// Fibonacci(-10) // (minor: -55, major: 34, index: -10) +/// Fibonacci( -9) // (minor: 34, major: -21, index: -9) +/// Fibonacci( -8) // (minor: -21, major: 13, index: -8) +/// Fibonacci( -7) // (minor: 13, major: -8, index: -7) +/// Fibonacci( -6) // (minor: -8, major: 5, index: -6) +/// Fibonacci( -5) // (minor: 5, major: -3, index: -5) +/// Fibonacci( -4) // (minor: -3, major: 2, index: -4) +/// Fibonacci( -3) // (minor: 2, major: -1, index: -3) +/// Fibonacci( -2) // (minor: -1, major: 1, index: -2) +/// Fibonacci( -1) // (minor: 1, major: 0, index: -1) +/// Fibonacci( 0) // (minor: 0, major: 1, index: 0) +/// Fibonacci( 1) // (minor: 1, major: 1, index: 1) +/// Fibonacci( 2) // (minor: 1, major: 2, index: 2) +/// Fibonacci( 3) // (minor: 2, major: 3, index: 3) +/// Fibonacci( 4) // (minor: 3, major: 5, index: 4) +/// Fibonacci( 5) // (minor: 5, major: 8, index: 5) +/// Fibonacci( 6) // (minor: 8, major: 13, index: 6) +/// Fibonacci( 7) // (minor: 13, major: 21, index: 7) +/// Fibonacci( 8) // (minor: 21, major: 34, index: 8) +/// Fibonacci( 9) // (minor: 34, major: 55, index: 9) +/// Fibonacci( 10) // (minor: 55, major: 89, index: 10) +/// Fibonacci( 11) // (nil) /// -/// ```swift -/// f(x - 0) == + f(x) * 00000001 - f(x + 1) * 0000 -/// f(x - 1) == - f(x) * 00000001 + f(x + 1) * 0001 -/// f(x - 2) == + f(x) * 00000002 - f(x + 1) * 0001 -/// f(x - 3) == - f(x) * 00000003 + f(x + 1) * 0002 -/// f(x - 4) == + f(x) * 00000005 - f(x + 1) * 0003 -/// f(x - 5) == - f(x) * 00000008 + f(x + 1) * 0005 -/// ----------------------------------------------- -/// f(x - y) == ± f(x) * f(y + 1) ± f(x + 1) * f(y) +/// Fibonacci( 11) // (minor: 89, major: 144, index: 11) +/// Fibonacci( 12) // (minor: 144, major: 233, index: 12) +/// Fibonacci( 13) // (nil) /// ``` /// -/// ### Un/signed vs Magnitude +/// [info]: https://en.wikipedia.org/wiki/fibonacci_sequence /// -/// It permits both signed and unsigned values for testing purposes. -/// -@frozen public struct Fibonacci where Value: BinaryInteger { +@frozen public struct Fibonacci: CustomStringConvertible, Equatable where Element: BinaryInteger { //=------------------------------------------------------------------------= // MARK: State //=------------------------------------------------------------------------= - @usableFromInline var i: Value - @usableFromInline var a: Value - @usableFromInline var b: Value + @usableFromInline var base: Indexacci //=------------------------------------------------------------------------= // MARK: Initializers //=------------------------------------------------------------------------= - /// Creates the first sequence pair. @inlinable public init() { - self.i = 0 - self.a = 0 - self.b = 1 + self.base = Indexacci.fibonacci() } - /// Creates the sequence pair at the given `index`. - @inlinable public init(_ index: Value) throws { - if Bool(index.appendix) { - throw Value.isSigned ? Error.indexOutOfBounds : Error.overflow - } - - self.init() - - try index.withUnsafeBinaryIntegerBody(as: U8.self) { - for i in (0 ..< IX(raw: $0.nondescending(Bit.zero))).reversed() { - try self.double() - - if $0[unchecked: i &>> 3] &>> U8(load: i) & 1 != 0 { - try self.increment() - } - } - } + @inlinable public init(unsafe base: Indexacci) { + self.base = base } //=------------------------------------------------------------------------= // MARK: Utilities //=------------------------------------------------------------------------= - /// The sequence `index`. - @inlinable public var index: Value { - self.i + /// The sequence element at `index`. + @inlinable public var minor: Element { + self.base.tuple.minor } - /// The sequence `element` at `index`. - @inlinable public var element: Value { - self.a + /// The sequence element at `index + 1`. + @inlinable public var major: Element { + self.base.tuple.major } - /// The sequence `element` at `index + 1`. - @inlinable public var next: Value { - self.b - } - - @inlinable public consuming func components() -> (index: Value, element: Value, next: Value) { - (index: self.i, element: self.a, next: self.b) - } - - //=------------------------------------------------------------------------= - // MARK: Transformations - //=------------------------------------------------------------------------= - - /// Forms the sequence pair at `index + 1`. - @inlinable public mutating func increment() throws { - let ix = try i.plus(1).prune(Error.overflow) - let bx = try a.plus(b).prune(Error.overflow) - - self.i = consume ix - self.a = b - self.b = consume bx + /// The sequence `index`. + @inlinable public var index: Element { + self.base.index } - /// Forms the sequence pair at `index - 1`. - @inlinable public mutating func decrement() throws { - let ix = try i.minus(1).veto({$0.isNegative}).prune(Error.indexOutOfBounds) - let ax = try b.minus(a).prune(Error.overflow) - - self.i = consume ix - self.b = a - self.a = consume ax + @inlinable public consuming func components() -> Indexacci { + self.base } +} + +//*============================================================================* +// MARK: * Fibonacci x Binary Integer +//*============================================================================* + +extension BinaryInteger { //=------------------------------------------------------------------------= - // MARK: Transformations + // MARK: Initializers //=------------------------------------------------------------------------= - /// Forms the sequence pair at `index * 2`. - @inlinable public mutating func double() throws { - let ex = Self.Error.overflow - let ix = try i.doubled().prune(ex) - let ax = try b.doubled().prune(ex).minus(a).prune(ex).times(a).prune(ex) - let bx = try b.squared().prune(ex).plus(a.squared().prune(ex)).prune(ex) - - self.i = consume ix - self.a = consume ax - self.b = consume bx - } - - /// Forms the sequence pair at `index + x.index`. - @inlinable public mutating func increment(by x: borrowing Self) throws { - let ex = Self.Error.overflow - let ix = try i.plus (x.i).prune(ex) - let ax = try a.times(x.b).prune(ex).plus(b.minus(a).prune(ex).times(x.a).prune(ex)).prune(ex) - let bx = try b.times(x.b).prune(ex).plus(((((((((a )))))))).times(x.a).prune(ex)).prune(ex) - - self.i = consume ix - self.a = consume ax - self.b = consume bx - } - - /// Forms the sequence pair at `index - x.index`. - @inlinable public mutating func decrement(by x: borrowing Self) throws { - let ix = try i.minus(x.i).veto({ $0.isNegative }).prune(Error.indexOutOfBounds) + /// Returns the `Fibonacci` sequence element at `index` and an `error` indicator, or `nil`. + /// + /// - Note: It ignores `major` element `error` indicators. + /// + /// ### Fibonacci + /// + /// - Note: The `error` is set if the operation is `lossy`. + /// + /// - Note: It produces `nil` if the `index` is `infinite`. + /// + @inlinable public static func fibonacci(_ index: consuming Self) -> Optional> { + let major = index.isPositive + if major { + index = index.decremented().value + } - var a0 = b.times(x.a).value - var a1 = a.times(x.b).value - var b0 = b.plus(a).value.times(x.a).value - var b1 = b.times(x.b).value + guard let result = Fibonacci.exactly(index, as: Tupleacci.self) else { return nil } - if Bool(x.i.lsb) { - Swift.swap(&a0, &a1) - Swift.swap(&b0, &b1) + if major { + return result.map({ $0.major }) + } else { + return result.map({ $0.minor }) } - - self.i = consume ix - self.a = a1.minus(a0).value - self.b = b1.minus(b0).value } - //*========================================================================* - // MARK: * Error - //*========================================================================* - - public enum Error: Swift.Error { - - /// Tried to form a sequence pair that cannot be represented. - case overflow - - /// Tried to form a sequence pair at an index less than zero. - case indexOutOfBounds + /// Returns the `Fibonacci` sequence element at `index` and an `error` indicator. + /// + /// - Note: It ignores `major` element `error` indicators. + /// + /// ### Fibonacci + /// + /// - Note: The `error` is set if the operation is `lossy`. + /// + /// - Note: It produces `nil` if the `index` is `infinite`. + /// + @inlinable public static func fibonacci(_ index: consuming Self) -> Fallible where Self: FiniteInteger { + (Self.fibonacci(index) as Optional).unchecked() } -} - -//=----------------------------------------------------------------------------= -// MARK: + Text -//=----------------------------------------------------------------------------= - -extension Fibonacci: CustomStringConvertible { - - //=------------------------------------------------------------------------= - // MARK: Utilities - //=------------------------------------------------------------------------= - @inlinable public var description: String { - String(describing: self.element) - } + /// Returns the `Fibonacci` sequence element at `index`. + /// + /// - Note: It ignores `major` element `error` indicators. + /// + /// ### Fibonacci + /// + /// - Note: The `error` is set if the operation is `lossy`. + /// + /// - Note: It produces `nil` if the `index` is `infinite`. + /// + @inlinable public static func fibonacci(_ index: consuming Self) -> Self where Self: ArbitraryInteger & SignedInteger { + (Self.fibonacci(index) as Fallible).unchecked() + } } diff --git a/Sources/FibonacciKit/Indexacci+Stride.swift b/Sources/FibonacciKit/Indexacci+Stride.swift new file mode 100644 index 00000000..33b719fc --- /dev/null +++ b/Sources/FibonacciKit/Indexacci+Stride.swift @@ -0,0 +1,47 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Indexacci x Stride +//*============================================================================* + +extension Indexacci { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + /// Returns the sequence pair at `index + 1`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func incremented() -> Fallible { + var error = false + self.tuple = self.tuple.incremented().sink(&error) + self.index = self.index.incremented().sink(&error) + return self.veto(error) + } + + /// Returns the sequence pair at `index - 1`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func decremented() -> Fallible { + var error = false + self.tuple = self.tuple.decremented().sink(&error) + self.index = self.index.decremented().sink(&error) + return self.veto(error) + } +} diff --git a/Sources/FibonacciKit/Indexacci+Text.swift b/Sources/FibonacciKit/Indexacci+Text.swift new file mode 100644 index 00000000..c8dd82d0 --- /dev/null +++ b/Sources/FibonacciKit/Indexacci+Text.swift @@ -0,0 +1,25 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Indexacci x Text +//*============================================================================* + +extension Indexacci { + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + @inlinable public var description: String { + "[\(self.index): \(self.tuple)]" + } +} diff --git a/Sources/FibonacciKit/Indexacci.swift b/Sources/FibonacciKit/Indexacci.swift new file mode 100644 index 00000000..c3f32df4 --- /dev/null +++ b/Sources/FibonacciKit/Indexacci.swift @@ -0,0 +1,53 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Indexacci +//*============================================================================* + +@frozen public struct Indexacci: CustomStringConvertible, Equatable, Recoverable where Element: BinaryInteger { + + //=------------------------------------------------------------------------= + // MARK: Metadata + //=------------------------------------------------------------------------= + + @inlinable public static func fibonacci() -> Self { + Self(tuple: Tupleacci.fibonacci(), index: Element.zero) + } + + //=------------------------------------------------------------------------= + // MARK: State + //=------------------------------------------------------------------------= + + public var tuple: Tupleacci + public var index: Element + + //=------------------------------------------------------------------------= + // MARK: Initializers + //=------------------------------------------------------------------------= + + @inlinable public init(tuple: consuming Tupleacci, index: consuming Element) { + self.tuple = tuple + self.index = index + } + + @inlinable public init(minor: consuming Element, major: consuming Element, index: consuming Element) { + self.init(tuple: Tupleacci(minor: minor, major: major), index: index) + } + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + @inlinable public consuming func components() -> (tuple: Tupleacci, index: Element) { + (tuple: self.tuple, index: self.index) + } +} diff --git a/Sources/FibonacciKit/Tupleacci+Stride.swift b/Sources/FibonacciKit/Tupleacci+Stride.swift new file mode 100644 index 00000000..52431779 --- /dev/null +++ b/Sources/FibonacciKit/Tupleacci+Stride.swift @@ -0,0 +1,49 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Tupleacci x Stride +//*============================================================================* + +extension Tupleacci { + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + /// Returns the sequence pair at `index + 1`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func incremented() -> Fallible { + var error = false + let major = major + self.major = minor.plus(major).sink(&error) + self.minor = major + return self.veto(error) + } + + /// Returns the sequence pair at `index - 1`. + /// + /// ### Development + /// + /// - Todo: Measure versus `components()` approach. + /// + @inlinable public consuming func decremented() -> Fallible { + var error = false + let minor = minor + self.minor = major.minus(minor).sink(&error) + self.major = minor + return self.veto(error) + } +} diff --git a/Sources/FibonacciKit/Tupleacci+Text.swift b/Sources/FibonacciKit/Tupleacci+Text.swift new file mode 100644 index 00000000..2c14e1e8 --- /dev/null +++ b/Sources/FibonacciKit/Tupleacci+Text.swift @@ -0,0 +1,25 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Tupleacci x Text +//*============================================================================* + +extension Tupleacci { + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + @inlinable public var description: String { + "(\(self.minor), \(self.major))" + } +} diff --git a/Sources/FibonacciKit/Tupleacci.swift b/Sources/FibonacciKit/Tupleacci.swift new file mode 100644 index 00000000..02b86c77 --- /dev/null +++ b/Sources/FibonacciKit/Tupleacci.swift @@ -0,0 +1,53 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit + +//*============================================================================* +// MARK: * Tupleacci +//*============================================================================* + +@frozen public struct Tupleacci: CustomStringConvertible, Equatable, Recoverable where Element: BinaryInteger { + + //=------------------------------------------------------------------------= + // MARK: Metadata + //=------------------------------------------------------------------------= + + @inlinable public static func fibonacci() -> Self { + Self(minor: 0, major: 1) + } + + //=------------------------------------------------------------------------= + // MARK: State + //=------------------------------------------------------------------------= + + public var minor: Element + public var major: Element + + //=------------------------------------------------------------------------= + // MARK: Initializers + //=------------------------------------------------------------------------= + + @inlinable public init(minor: Element, major: Element) { + self.minor = minor + self.major = major + } + + //=------------------------------------------------------------------------= + // MARK: Transformations + //=------------------------------------------------------------------------= + + @inlinable public consuming func swapped() -> Self { + Self(minor: self.major, major: self.minor) + } + + @inlinable public consuming func components() -> (minor: Element, major: Element) { + (minor: self.minor, major: self.major) + } +} diff --git a/Tests/Benchmarks/BinaryInteger+Geometry.swift b/Tests/Benchmarks/BinaryInteger+Geometry.swift index 3324c78a..a7344f89 100644 --- a/Tests/Benchmarks/BinaryInteger+Geometry.swift +++ b/Tests/Benchmarks/BinaryInteger+Geometry.swift @@ -25,7 +25,7 @@ final class BinaryIntegerBenchmarksOnGeometry: XCTestCase { func testSquareRootsAsU64() { typealias T = U64 - + for power: UX in 0 ..< 1_000_000 { blackHole(T(load: power).isqrt()) } diff --git a/Tests/Benchmarks/Fibonacci.swift b/Tests/Benchmarks/Fibonacci.swift index 278c8545..6fb62b8a 100644 --- a/Tests/Benchmarks/Fibonacci.swift +++ b/Tests/Benchmarks/Fibonacci.swift @@ -24,9 +24,9 @@ final class FibonacciBenchmarks: XCTestCase { // MARK: Metadata //=------------------------------------------------------------------------= - static let fib1e6 = try! Fibonacci(1_000_000) - static let fib1e6r10 = fib1e6.element.description(as: .decimal) - static let fib1e6r16 = fib1e6.element.description(as: .hexadecimal) + static let fib1e6 = IXL.fibonacci(1_000_000) + static let fib1e6r10 = fib1e6.description(as: .decimal) + static let fib1e6r16 = fib1e6.description(as: .hexadecimal) //=------------------------------------------------------------------------= // MARK: Initialization @@ -39,12 +39,12 @@ final class FibonacciBenchmarks: XCTestCase { } //=------------------------------------------------------------------------= - // MARK: Tests x UXL + // MARK: Tests x IXL //=------------------------------------------------------------------------= - func testFibonacciUXL1e5() throws { - let index: UXL = blackHoleIdentity(100_000) - let element: UXL = try Fibonacci(index).element + func testFibonacciIXL1e5() throws { + let index: IXL = blackHoleIdentity(100_000) + let element: IXL = IXL.fibonacci(index) XCTAssertEqual(element.entropy(), Count(69_425)) } @@ -53,9 +53,9 @@ final class FibonacciBenchmarks: XCTestCase { /// 0.04 seconds /// 0.02 seconds after (#84) /// - func testFibonacciUXL1e6() throws { - let index: UXL = blackHoleIdentity(1_000_000) - let element: UXL = try Fibonacci(index).element + func testFibonacciIXL1e6() throws { + let index: IXL = blackHoleIdentity(1_000_000) + let element: IXL = IXL.fibonacci(index) XCTAssertEqual(element.entropy(), Count(694_242)) } @@ -64,9 +64,9 @@ final class FibonacciBenchmarks: XCTestCase { /// 1.65 seconds /// 0.50 seconds after (#84) /// - func testFibonacciUXL1e7() throws { - let index: UXL = blackHoleIdentity(10_000_000) - let element: UXL = try Fibonacci(index).element + func testFibonacciIXL1e7() throws { + let index: IXL = blackHoleIdentity(10_000_000) + let element: IXL = IXL.fibonacci(index) XCTAssertEqual(element.entropy(), Count(6_942_419)) } @@ -75,31 +75,31 @@ final class FibonacciBenchmarks: XCTestCase { /// - `0.99 seconds` /// - `0.30 seconds` with `Divider21` /// - func testFibonacciUXL1e6ToTextAsDecimal() throws { - let data = blackHoleIdentity(Self.fib1e6.element) + func testFibonacciIXL1e6ToTextAsDecimal() throws { + let data = blackHoleIdentity(Self.fib1e6) let format = blackHoleIdentity(TextInt.decimal) let text = data.description(as: format) XCTAssertEqual(text.utf8.count, 208988) } - func testFibonacciUXL1e6ToTextAsHexadecimal() throws { - let data = blackHoleIdentity(Self.fib1e6.element) + func testFibonacciIXL1e6ToTextAsHexadecimal() throws { + let data = blackHoleIdentity(Self.fib1e6) let format = blackHoleIdentity(TextInt.hexadecimal) let text = data.description(as: format) XCTAssertEqual(text.utf8.count, 173561) } - func testFibonacciUXL1e6FromTextAsDecimal() throws { + func testFibonacciIXL1e6FromTextAsDecimal() throws { let text = blackHoleIdentity(Self.fib1e6r10) let format = blackHoleIdentity(TextInt.decimal) - let data = try UXL.init(text, as: format) - XCTAssertEqual(data, Self.fib1e6.element) + let data = try IXL.init(text, as: format) + XCTAssertEqual(data, Self.fib1e6) } - func testFibonacciUXL1e6FromTextAsHexadecimal() throws { + func testFibonacciIXL1e6FromTextAsHexadecimal() throws { let text = blackHoleIdentity(Self.fib1e6r16) let format = blackHoleIdentity(TextInt.hexadecimal) - let data = try UXL.init(text, as: format) - XCTAssertEqual(data, Self.fib1e6.element) + let data = try IXL.init(text, as: format) + XCTAssertEqual(data, Self.fib1e6) } } diff --git a/Tests/FibonacciKitTests/Fibonacci+Invariants.swift b/Tests/FibonacciKitTests/Fibonacci+Invariants.swift index 91f4fc39..42cd374f 100644 --- a/Tests/FibonacciKitTests/Fibonacci+Invariants.swift +++ b/Tests/FibonacciKitTests/Fibonacci+Invariants.swift @@ -39,14 +39,14 @@ import TestKit for _ in 0 ..< conditional(debug: 8, release: 32) { let index = T.random(in: low...high, using: &randomness) let fibonacci = try #require(try Fibonacci(index)) - try #require(fibonacci.element.euclidean(fibonacci.next) == 1) + try #require(fibonacci.minor.euclidean(fibonacci.major) == 1) } } } /// Generates random values to check the following invariant: /// - /// f(a) * f(b) == (f(a+b+1) / f(a+1) - f(b+1)) * f(a+1) + f(a+b+1) % f(a+1) + /// f(a) * f(b) == (f(a+b+1) / f(a+1) - f(b+1)) * f(a+1) + f(a+b+1) % f(a+1) where (a != -1) /// /// ### Calls: Fibonacci /// @@ -75,9 +75,7 @@ import TestKit let arbitrary: IX = conditional(debug: 144, release: 369) let low = T(metadata.low ?? -arbitrary) let high = T(metadata.high ?? arbitrary) - //=----------------------------------= - let x = Bad.message("arithmetic") - //=----------------------------------= + for _ in 0 ..< conditional(debug: 32, release: 128) { let i = next() @@ -85,17 +83,19 @@ import TestKit try #require((low...high).contains(i.1)) try #require((low...high).contains(i.2)) - var a = try Fibonacci(i.0) - let b = try Fibonacci(i.1) - let c = try #require(a.next.division(b.next)).prune(x) + guard i.1 != IX(-1) else { continue } - try a.decrement(by: b) + let a = try #require(Fibonacci(i.0)) + let b = try #require(Fibonacci(i.1)) + let c = try #require(a.major.division(b.major)?.optional()) + let d = try #require(a.decremented(by: b)) + let e = try #require(d.incremented(by: b)) - let d = try a.element.times(b.element).prune(x) - let e = try c.quotient.minus((a.next)).prune(x).times(b.next).prune(x).plus(c.remainder).prune(x) + let x = (d.minor &* b.minor) + let y = (c.quotient &- d.major) &* b.major &+ c.remainder - try a.increment(by: b) - try #require((d) == e) + try #require(a == e) + try #require(x == y) } func next() -> (T, T, T) { diff --git a/Tests/FibonacciKitTests/Fibonacci+Small.swift b/Tests/FibonacciKitTests/Fibonacci+Small.swift new file mode 100644 index 00000000..555ee567 --- /dev/null +++ b/Tests/FibonacciKitTests/Fibonacci+Small.swift @@ -0,0 +1,309 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit +import FibonacciKit +import TestKit + +//*============================================================================* +// MARK: * Fibonacci x Small +//*============================================================================* + +/// A small integer sequence test suite. +/// +/// This suite runs exhaustive tests on: +/// +/// - `Fibonacci` +/// - `Fibonacci` +/// +@Suite struct FibonacciTestsOnSmall { + + struct Source: Metadata { + + let all: [Fibonacci] + + } + + protocol Metadata: CustomTestStringConvertible, Sendable { + + associatedtype Value: SystemsInteger + + var all: [Fibonacci] { get } + } + + //=------------------------------------------------------------------------= + // MARK: Metadata + //=------------------------------------------------------------------------= + + static let metadata: [any Metadata] = [ + i8s, + u8s, + ] + + static let i8s: Source = reinterpret([ + + (index: -11, element: 89, next: -55), // 0 + (index: -10, element: -55, next: 34), // 1 + (index: -9, element: 34, next: -21), // 2 + (index: -8, element: -21, next: 13), // 3 + (index: -7, element: 13, next: -8), // 4 + (index: -6, element: -8, next: 5), // 5 + (index: -5, element: 5, next: -3), // 6 + (index: -4, element: -3, next: 2), // 7 + (index: -3, element: 2, next: -1), // 8 + (index: -2, element: -1, next: 1), // 9 + (index: -1, element: 1, next: 0), // 10 + (index: 0, element: 0, next: 1), // 11 + (index: 1, element: 1, next: 1), // 12 + (index: 2, element: 1, next: 2), // 13 + (index: 3, element: 2, next: 3), // 14 + (index: 4, element: 3, next: 5), // 15 + (index: 5, element: 5, next: 8), // 16 + (index: 6, element: 8, next: 13), // 17 + (index: 7, element: 13, next: 21), // 18 + (index: 8, element: 21, next: 34), // 19 + (index: 9, element: 34, next: 55), // 20 + (index: 10, element: 55, next: 89), // 21 + + ]) + + static let u8s: Source = reinterpret([ + + (index: 0, element: 0, next: 1), // 0 + (index: 1, element: 1, next: 1), // 1 + (index: 2, element: 1, next: 2), // 2 + (index: 3, element: 2, next: 3), // 3 + (index: 4, element: 3, next: 5), // 4 + (index: 5, element: 5, next: 8), // 5 + (index: 6, element: 8, next: 13), // 6 + (index: 7, element: 13, next: 21), // 7 + (index: 8, element: 21, next: 34), // 8 + (index: 9, element: 34, next: 55), // 9 + (index: 10, element: 55, next: 89), // 10 + (index: 11, element: 89, next: 144), // 11 + (index: 12, element: 144, next: 233), // 12 + + ]) + + static func reinterpret(_ x: [(index: T, element: T, next: T)]) -> Source { + Source(all: x.map(reinterpret)) + } + + static func reinterpret(_ x: ((index: T, element: T, next: T))) -> Fibonacci { + Fibonacci(unsafe: Indexacci(minor: x.element, major: x.next, index: x.index)) + } + + //=------------------------------------------------------------------------= + // MARK: Tests x Fast + //=------------------------------------------------------------------------= + + @Test( + "Fibonacci/small: init(_:) and T.fibonacci(_:)", + Tag.List.tags(.exhaustive, .generic), + arguments: metadata + ) func fast(source: any Metadata) throws { + + try whereIs(source) + func whereIs(_ metadata: T) throws where T: Metadata { + for x in metadata.all { + try #require(Fibonacci(x.index)?.components() == x.components()) + try #require(T.Value.fibonacci((((x.index)))) == Fallible(x.minor), "\((T.Value.self, x))") + } + + if let index = metadata.min.index.decremented().optional() { + let lossy = metadata.min.major &- metadata.min.minor + try #require(Fibonacci(index) == nil) + try #require(T.Value .fibonacci(index) == lossy.veto()) + } + + if let index = metadata.max.index.incremented().optional() { + try #require(Fibonacci(index) == nil) + try #require(T.Value .fibonacci(index) == Fallible(metadata.max.major)) + } + + if let index = metadata.max.index.plus(2).optional() { + let lossy = metadata.max.major &+ metadata.max.minor + try #require(Fibonacci(index) == nil) + try #require(T.Value .fibonacci(index) == lossy.veto()) + } + } + } + + //=----------------------------------------------------------------------------= + // MARK: Tests x Stride + //=----------------------------------------------------------------------------= + + @Test( + "Fibonacci/small: incremented()", + Tag.List.tags(.exhaustive, .generic), + arguments: metadata + ) func incremented(source: any Metadata) throws { + + try whereIs(source) + func whereIs(_ metadata: T) throws where T: Metadata { + var a = metadata.min + for b in metadata.all.dropLast() { + try #require(a.components() == b.components()) + a = try #require(a.incremented()) + } + + try #require(a.components() == metadata.max.components()) + try #require(a.incremented() == nil) + } + } + + @Test( + "Fibonacci/small: decremented()", + Tag.List.tags(.exhaustive, .generic), + arguments: metadata + ) func decremented(source: any Metadata) throws { + + try whereIs(source) + func whereIs(_ metadata: T) throws where T: Metadata { + var a = metadata.max + for b in metadata.all.dropFirst().reversed() { + try #require(a.components() == b.components()) + a = try #require(a.decremented()) + } + + try #require(a.components() == metadata.min.components()) + try #require(a.decremented() == nil) + } + } + + @Test( + "Fibonacci/small: doubled()", + Tag.List.tags(.exhaustive, .generic), + arguments: metadata + ) func doubled(source: any Metadata) throws { + + try whereIs(source) + func whereIs(_ metadata: T) throws where T: Metadata { + let (s) = metadata.all.firstIndex(where: \.index.isZero)! + for (i, a) in metadata.all.enumerated() { + + let b = a.doubled() + let j = s + (i-s)*2 + + if metadata.all.indices.contains(j) { + try #require(b?.components() == metadata.all[j].components()) + + } else { + try #require(b?.components() == nil) + } + } + } + } + + @Test( + "Fibonacci/small: incremented(by:)", + Tag.List.tags(.exhaustive, .generic), + arguments: metadata + ) func incrementedBy(source: any Metadata) throws { + + try whereIs(source) + func whereIs(_ metadata: T) throws where T: Metadata { + let (s) = metadata.all.firstIndex(where: \.index.isZero)! + for (i, a) in metadata.all.enumerated() { + for (j, b) in metadata.all.enumerated() { + + let c = a.incremented(by: b) + let k = s + (i-s) + (j-s) + + if metadata.all.indices.contains(k) { + try #require(c?.components() == metadata.all[k].components()) + + } else { + try #require(c?.components() == nil) + } + } + } + } + } + + @Test( + "Fibonacci/small: decremented(by:)", + Tag.List.tags(.exhaustive, .generic), + arguments: metadata + ) func decrementedBy(source: any Metadata) throws { + + try whereIs(source) + func whereIs(_ metadata: T) throws where T: Metadata { + let (s) = metadata.all.firstIndex(where: \.index.isZero)! + for (i, a) in metadata.all.enumerated() { + for (j, b) in metadata.all.enumerated() { + + let c = a.decremented(by: b) + let k = s + (i-s) - (j-s) + + if metadata.all.indices.contains(k) { + try #require(c?.components() == metadata.all[k].components()) + + } else { + try #require(c?.components() == nil) + } + } + } + } + } + + //=------------------------------------------------------------------------= + // MARK: Tests x Toggle + //=------------------------------------------------------------------------= + + @Test( + "Fibonacci/small: toggled()", + Tag.List.tags(.exhaustive, .generic), + arguments: metadata + ) func toggled(source: any Metadata) throws { + + try whereIs(source) + func whereIs(_ metadata: T) throws where T: Metadata { + for x: Fibonacci in metadata.all { + if T.Value.isSigned { + let (mid) = metadata.all.count / 2 + let index = mid + Swift.Int(IX(x.index.toggled())) + try #require(x.toggled()?.components() == metadata.all[index].components()) + + } else { + try #require(x.toggled()?.components() == nil) + } + } + } + } +} + +//=----------------------------------------------------------------------------= +// MARK: + Utilities +//=----------------------------------------------------------------------------= + +extension FibonacciTestsOnSmall.Metadata { + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + var min: (Fibonacci) { + self.all.first! + } + + var max: (Fibonacci) { + self.all.last! + } + + var indices: ClosedRange { + self.min.index...self.max.index + } + + var testDescription: String { + let type = Fibonacci.self + let body = self.all.lazy.map(\.description).joined(separator: ", ") + return "\(type)([\(body)])" + } +} diff --git a/Tests/FibonacciKitTests/Fibonacci+Stride.swift b/Tests/FibonacciKitTests/Fibonacci+Stride.swift index 287bcd3e..fdec999f 100644 --- a/Tests/FibonacciKitTests/Fibonacci+Stride.swift +++ b/Tests/FibonacciKitTests/Fibonacci+Stride.swift @@ -17,11 +17,11 @@ import TestKit //*============================================================================* @Suite struct FibonacciTestsOnStride { - + //=------------------------------------------------------------------------= // MARK: Tests //=------------------------------------------------------------------------= - + @Test( "Fibonacci/stride: for each (index, increment, decrement) in range", Tag.List.tags(.generic, .exhaustive), @@ -29,56 +29,68 @@ import TestKit ) func forEachIndexIncrementDecrementInRange( metadata: FibonacciTests.Metadata ) throws { - + try whereIs(metadata.type) func whereIs(_ type: T.Type) throws where T: BinaryInteger { let arbitrary: IX = conditional(debug: 144, release: 369) let low: IX = metadata.low ?? -arbitrary let high: IX = metadata.high ?? arbitrary var fibonacci = try #require(try Fibonacci(T(low))) - - scope: if let _ = metadata.low { - try #require(throws: Fibonacci.Error.indexOutOfBounds) { - try fibonacci.decrement() - } + + if let _ = metadata.low { + try #require(fibonacci.decremented() == nil) - guard T.isSigned else { break scope } - try #require(throws: Fibonacci.Error.indexOutOfBounds) { - try Fibonacci(T(fibonacci.index - 1)) + if let index = fibonacci.index.decremented().optional() { + try #require(Fibonacci(index) == nil) } } - + while fibonacci.index < high { - let (i, a, b) = fibonacci.components() - try #require(try fibonacci.increment()) - try #require(fibonacci.components() == (i + 1, b, b + a)) + let minor = fibonacci.minor + let major = fibonacci.major + let index = fibonacci.index + fibonacci = try #require(fibonacci.incremented()) + + let expectation = Indexacci( + minor: major, + major: major + minor, + index: index + 1 + ) - let (indexed) = try Fibonacci(fibonacci.index) - try #require(fibonacci.components() == indexed.components()) + try #require(expectation == fibonacci.components()) + try #require(expectation == Fibonacci(expectation.index)?.components()) } - - scope: if let _ = metadata.high { - try #require(throws: Fibonacci.Error.overflow) { - try fibonacci.increment() - } + + if let _ = metadata.high { + try #require(fibonacci.incremented() == nil) - try #require(throws: Fibonacci.Error.overflow) { - try Fibonacci(T(fibonacci.index + 1)) + if let index = fibonacci.index.incremented().optional() { + try #require(Fibonacci(index) == nil) } } - + while fibonacci.index > low { - let (i, a, b) = fibonacci.components() - try #require(try fibonacci.decrement()) - try #require(fibonacci.components() == (i - 1, b - a, a)) + let minor = fibonacci.minor + let major = fibonacci.major + let index = fibonacci.index + fibonacci = try #require(fibonacci.decremented()) + + let expectation = Indexacci( + minor: major - minor, + major: minor, + index: index - 1 + ) + + try #require(expectation == fibonacci.components()) + try #require(expectation == Fibonacci(expectation.index)?.components()) } } } - + //=------------------------------------------------------------------------= // MARK: Tests x Jump //=------------------------------------------------------------------------= - + @Test( "Fibonacci/stride: random jump in range", Tag.List.tags(.generic, .random), @@ -91,63 +103,43 @@ import TestKit func whereIs(_ type: T.Type) throws where T: BinaryInteger { let low = T(metadata.low ?? -369) let high = T(metadata.high ?? 369) - + for _ in 0 ..< conditional(debug: 8, release: 32) { let i = next() - + try #require((low...high).contains(i.0)) try #require((low...high).contains(i.1)) try #require((low...high).contains(i.2)) - + let a = try #require(try Fibonacci(i.0)) let b = try #require(try Fibonacci(i.1)) let c = try #require(try Fibonacci(i.2)) - always: do { - var x: Fibonacci = a - try #require(try x.increment(by: b)) - try #require(x.components() == c.components()) - } - - always: do { - var x: Fibonacci = b - try #require(try x.increment(by: a)) - try #require(x.components() == c.components()) - } - - always: do { - var x: Fibonacci = c - try #require(try x.decrement(by: b)) - try #require(x.components() == a.components()) - - } - - always: do { - var x: Fibonacci = c - try #require(try x.decrement(by: a)) - try #require(x.components() == b.components()) - } + try #require(a.incremented(by: b)?.components() == c.components()) + try #require(b.incremented(by: a)?.components() == c.components()) + try #require(c.decremented(by: a)?.components() == b.components()) + try #require(c.decremented(by: b)?.components() == a.components()) } - + func next() -> (T, T, T) { var a = low, b = high - + let i = T.random(in: a...b, using: &randomness) - + (a, b) = ( Swift.max(a, a.minus(i).optional() ?? a), Swift.min(b, b.minus(i).optional() ?? b) ) - + let j = T.random(in: a...b, using: &randomness) return (i, j, i + j) } } } - + @Test( "Fibonacci/stride: random jump out of bounds throws error", - Tag.List.tags(.generic, .todo, .random), + Tag.List.tags(.generic, .random), arguments: FibonacciTests.metadataAsSystemsInteger, fuzzers ) func randomJumpOutOfBoundsThrowsError( metadata: FibonacciTests.Metadata, randomness: consuming FuzzerInt @@ -157,47 +149,33 @@ import TestKit func whereIs(_ type: T.Type) throws where T: BinaryInteger { let low = try T(#require(metadata.low )) let high = try T(#require(metadata.high)) - + for _ in 0 ..< conditional(debug: 8, release: 32) { let i = T.random(in: low...high, using: &randomness) - var a = try Fibonacci(i) - + let a = try #require(Fibonacci(i)) + if let k = (i+1).minus(low).optional(), k <= high { let j = T.random(in: k...high, using: &randomness) - let b = try Fibonacci(j) - - try #require(throws: Fibonacci.Error.indexOutOfBounds) { - try a.decrement(by: b) // i - j < low - } + let b = try #require(Fibonacci(j)) + try #require(a.decremented(by: b) == nil, "i - j < low") } - + if let k = i.minus(high+1).optional(), k >= low { let j = T.random(in: low...k, using: &randomness) - let b = try Fibonacci(j) - - try #require(low.isNegative, "todo: index < 0") - try #require(throws: Fibonacci.Error.overflow) { - try a.decrement(by: b) // i - j > high - } + let b = try #require(Fibonacci(j)) + try #require(a.decremented(by: b) == nil, "i - j > high") } - + if let k = low.minus(i+1).optional(), k >= low { let j = T.random(in: low...k, using: &randomness) - let b = try Fibonacci(j) - - try #require(low.isNegative, "todo: index < 0") - try #require(throws: Fibonacci.Error.indexOutOfBounds) { - try a.decrement(by: b) // i + j < low - } + let b = try #require(Fibonacci(j)) + try #require(a.incremented(by: b) == nil, "i + j < low") } - + if let k = (high+1).minus(i).optional(), k <= high { let j = T.random(in: k...high, using: &randomness) - let b = try Fibonacci(j) - - try #require(throws: Fibonacci.Error.overflow) { - try a.increment(by: b) // i + j > high - } + let b = try #require(Fibonacci(j)) + try #require(a.incremented(by: b) == nil, "i + j > high") } } } diff --git a/Tests/FibonacciKitTests/Fibonacci.swift b/Tests/FibonacciKitTests/Fibonacci.swift index 4a62dce4..a73292a7 100644 --- a/Tests/FibonacciKitTests/Fibonacci.swift +++ b/Tests/FibonacciKitTests/Fibonacci.swift @@ -38,28 +38,32 @@ import TestKit } static let metadata: [Metadata] = [ - Metadata(type: I8 .self, low: 0, high: 10), - Metadata(type: U8 .self, low: 0, high: 12), - Metadata(type: I16 .self, low: 0, high: 22), - Metadata(type: U16 .self, low: 0, high: 23), - Metadata(type: I32 .self, low: 0, high: 45), - Metadata(type: U32 .self, low: 0, high: 46), - Metadata(type: I64 .self, low: 0, high: 91), - Metadata(type: U64 .self, low: 0, high: 92), + Metadata(type: I8 .self, low: -11, high: 10), + Metadata(type: U8 .self, low: 0, high: 12), + Metadata(type: I16 .self, low: -23, high: 22), + Metadata(type: U16 .self, low: 0, high: 23), + Metadata(type: I32 .self, low: -46, high: 45), + Metadata(type: U32 .self, low: 0, high: 46), + Metadata(type: I64 .self, low: -92, high: 91), + Metadata(type: U64 .self, low: 0, high: 92), - Metadata(type: I8x2.self, low: 0, high: 22), - Metadata(type: U8x2.self, low: 0, high: 23), - Metadata(type: I8x4.self, low: 0, high: 45), - Metadata(type: U8x4.self, low: 0, high: 46), - Metadata(type: I8x8.self, low: 0, high: 91), - Metadata(type: U8x8.self, low: 0, high: 92), + Metadata(type: I8x2.self, low: -23, high: 22), + Metadata(type: U8x2.self, low: 0, high: 23), + Metadata(type: I8x4.self, low: -46, high: 45), + Metadata(type: U8x4.self, low: 0, high: 46), + Metadata(type: I8x8.self, low: -92, high: 91), + Metadata(type: U8x8.self, low: 0, high: 92), - Metadata(type: I8L .self, low: 0, high: nil), - Metadata(type: U8L .self, low: 0, high: nil) + Metadata(type: I8L .self, low: nil, high: nil), + Metadata(type: U8L .self, low: 0, high: nil) ] static let metadataAsSystemsInteger = metadata.filter { - !$0.type.isArbitrary + $0.type.isArbitrary == false + } + + static let metadataAsMaximalInteger = metadata.filter { + $0.type.isMaximal } //=------------------------------------------------------------------------= @@ -74,8 +78,9 @@ import TestKit try whereIs(metadata.type) func whereIs(_ type: T.Type) throws where T: BinaryInteger { - try #require(Fibonacci( ).components() == (0, 0, 1)) - try #require(Fibonacci(T.zero).components() == (0, 0, 1)) + let components = Indexacci(minor: 0, major: 1, index: 0) + try #require(try #require(Fibonacci( )).components() == components) + try #require(try #require(Fibonacci(T.zero)).components() == components) } } @@ -87,11 +92,40 @@ import TestKit try whereIs(metadata.type) func whereIs(_ type: T.Type) throws where T: BinaryInteger { - let fibonacci = try Fibonacci( 4) - try #require(fibonacci.index == 4) - try #require(fibonacci.element == 3) - try #require(fibonacci.next == 5) - try #require(fibonacci.components() == (4, 3, 5)) + let instance = try #require(Fibonacci(4)) + let components = Indexacci(minor: 3, major: 5, index: 4) + try #require(instance.index == 4) + try #require(instance.minor == 3) + try #require(instance.major == 5) + try #require(instance.components() == components) + } + } +} + +//*============================================================================* +// MARK: * Fibonacci x Edge Cases +//*============================================================================* + +@Suite struct FibonacciEdgeCase { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "Fibonacci/edge-cases: from infinite index is nil", + Tag.List.tags(.generic, .random), + arguments: FibonacciTests.metadataAsMaximalInteger, fuzzers + ) func fromInfiniteIndexIsNil( + metadata: FibonacciTests.Metadata, randomness: consuming FuzzerInt + ) throws { + + try whereIs(metadata.type) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + let index = T.entropic(size: 256, as: Domain.natural, using: &randomness).toggled() + try #require(index.isInfinite) + try #require(Fibonacci(index) == nil) + try #require(T.fibonacci (index) == nil) } } } diff --git a/Tests/UltimathnumTests/BinaryInteger+Factorial.swift b/Tests/UltimathnumTests/BinaryInteger+Factorial.swift index a612fadc..37ddc1a0 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Factorial.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Factorial.swift @@ -178,18 +178,18 @@ import TestKit } for type in typesAsSystemsIntegerAsUnsigned { - AsUnsignedInteger(type) + whereIsUnsigned(type) } whereIs(IXL.self) whereIs(UXL.self) - AsUnsignedInteger(UXL.self) + whereIsUnsigned(UXL.self) func whereIs(_ type: T.Type) where T: BinaryInteger { #expect(T(clamping: index).factorial() as Optional == T.exactly(element)) } - func AsUnsignedInteger(_ type: T.Type) where T: UnsignedInteger { + func whereIsUnsigned(_ type: T.Type) where T: UnsignedInteger { #expect(T(clamping: index).factorial() as Fallible == T.exactly(element)) } } diff --git a/Tests/UltimathnumTests/BinaryInteger+Fibonacci.swift b/Tests/UltimathnumTests/BinaryInteger+Fibonacci.swift new file mode 100644 index 00000000..7f218329 --- /dev/null +++ b/Tests/UltimathnumTests/BinaryInteger+Fibonacci.swift @@ -0,0 +1,151 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit +import FibonacciKit +import InfiniIntKit +import RandomIntKit +import TestKit + +//*============================================================================* +// MARK: * Binary Integer x Fibonacci +//*============================================================================* + +@Suite struct BinaryIntegerTestsOnFibonacci { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + /// - Seealso: https://www.wolframalpha.com/input?i=fibonacci%281000%29 + /// - Seealso: https://www.wolframalpha.com/input?i=fibonacci%281024%29 + @Test("BinaryInteger/fibonacci: element at large natural index", arguments: [ + + (index: IXL(1000), element: IXL(""" + 0000000000000000000000000000000000000000000000043466557686937456\ + 4356885276750406258025646605173717804024817290895365554179490518\ + 9040387984007925516929592259308032263477520968962323987332247116\ + 1642996440906533187938298969649928516003704476137795166849228875 + """)!), + + (index: IXL(1024), element: IXL(""" + 0000000000000000000000000000000000000000004506699633677819813104\ + 3832357288860493678605962186048308030231496000306457087213962487\ + 9260914103039624487326658034501121953020936742558101987106764609\ + 4200262285202346655868899711089246778413354004103631553925405243 + """)!), + + ] as [(index: IXL, element: IXL)]) + func elementAtLargeNaturalIndex(index: IXL, element: IXL) throws { + for type in typesAsBinaryInteger { + try whereIs(type) + } + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + if let index = T.exactly (index).optional() { + try #require(T.fibonacci(index) == T.exactly(element)) + } + } + } + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "BinaryInteger/fibonacci: lossy vs exact", + Tag.List.tags(.forwarding, .generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func lossyVersusExact( + type: any BinaryInteger.Type, randomness: consuming FuzzerInt + ) throws { + + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 8 { + let index = T.entropic(size: 8, as: Domain.finite, using: &randomness) + let lossy = try #require(T.fibonacci(index)) + let exact = IXL.fibonacci(IXL (index)) + try #require(lossy == T.exactly(exact)) + } + } + } + + @Test( + "BinaryInteger/fibonacci: lossy vs lossy", + Tag.List.tags(.forwarding, .generic, .random), + arguments: typesAsSystemsInteger, fuzzers + ) func lossyVersusLossy( + type: any SystemsInteger.Type, randomness: consuming FuzzerInt + ) throws { + + for other in typesAsSystemsInteger { + try whereIs(small: type, large: other) + } + + func whereIs(small: A.Type, large: B.Type) throws where A: SystemsInteger, B: SystemsInteger { + guard A.mode == B.mode else { return } + guard A.size < B.size else { return } + + for _ in 0 ..< conditional(debug: 8, release: 32) { + let index = A.entropic(using: &randomness) + let small = A.fibonacci(A(index)) + let large = B.fibonacci(B(index)) + try #require(small == large.map(A.exactly)) + } + } + } +} + +//*============================================================================* +// MARK: * Binary Integer x Fibonacci x Conveniences +//*============================================================================* + +@Suite struct BinaryIntegerTestsOnFibonacciConveniences { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "BinaryInteger/fibonacci/conveniences: as FiniteInteger", + Tag.List.tags(.forwarding, .generic, .random), + arguments: typesAsFiniteInteger, fuzzers + ) func asFiniteInteger( + type: any FiniteInteger.Type, randomness: consuming FuzzerInt + ) throws { + + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: FiniteInteger { + for _ in 0 ..< 8 { + let index = T.entropic(size: 8, using: &randomness) + let expectation = T.fibonacci(index) as Optional< Fallible> + try #require(expectation == T.fibonacci(index) as Fallible) + } + } + } + + @Test( + "BinaryInteger/fibonacci/conveniences: as LenientInteger", + Tag.List.tags(.forwarding, .generic, .random), + arguments: typesAsArbitraryIntegerAsSigned, fuzzers + ) func asLenientInteger( + type: any ArbitraryIntegerAsSigned.Type, randomness: consuming FuzzerInt + ) throws { + + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: ArbitraryIntegerAsSigned { + for _ in 0 ..< 32 { + let index = T.entropic(size: 8, using: &randomness) + let expectation = T.fibonacci(index) as Optional> + try #require(expectation?.optional() == T.fibonacci(index) as T) + } + } + } +} From 118efa7d00d4a57649f5e26b6eee7e8458b89d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Wed, 13 Nov 2024 11:46:28 +0100 Subject: [PATCH 2/5] Conform sequences to Hashable & Sendable (#126) --- Sources/FibonacciKit/Fibonacci.swift | 2 +- Sources/FibonacciKit/Indexacci.swift | 2 +- Sources/FibonacciKit/Tupleacci.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/FibonacciKit/Fibonacci.swift b/Sources/FibonacciKit/Fibonacci.swift index 927de3d4..d7eaac0d 100644 --- a/Sources/FibonacciKit/Fibonacci.swift +++ b/Sources/FibonacciKit/Fibonacci.swift @@ -52,7 +52,7 @@ import CoreKit /// /// [info]: https://en.wikipedia.org/wiki/fibonacci_sequence /// -@frozen public struct Fibonacci: CustomStringConvertible, Equatable where Element: BinaryInteger { +@frozen public struct Fibonacci: CustomStringConvertible, Hashable, Sendable where Element: BinaryInteger { //=------------------------------------------------------------------------= // MARK: State diff --git a/Sources/FibonacciKit/Indexacci.swift b/Sources/FibonacciKit/Indexacci.swift index c3f32df4..29efdf19 100644 --- a/Sources/FibonacciKit/Indexacci.swift +++ b/Sources/FibonacciKit/Indexacci.swift @@ -13,7 +13,7 @@ import CoreKit // MARK: * Indexacci //*============================================================================* -@frozen public struct Indexacci: CustomStringConvertible, Equatable, Recoverable where Element: BinaryInteger { +@frozen public struct Indexacci: CustomStringConvertible, Hashable, Recoverable, Sendable where Element: BinaryInteger { //=------------------------------------------------------------------------= // MARK: Metadata diff --git a/Sources/FibonacciKit/Tupleacci.swift b/Sources/FibonacciKit/Tupleacci.swift index 02b86c77..2b394c2e 100644 --- a/Sources/FibonacciKit/Tupleacci.swift +++ b/Sources/FibonacciKit/Tupleacci.swift @@ -13,7 +13,7 @@ import CoreKit // MARK: * Tupleacci //*============================================================================* -@frozen public struct Tupleacci: CustomStringConvertible, Equatable, Recoverable where Element: BinaryInteger { +@frozen public struct Tupleacci: CustomStringConvertible, Hashable, Recoverable, Sendable where Element: BinaryInteger { //=------------------------------------------------------------------------= // MARK: Metadata From 03bbbf839205a588e6f5bc187f588b7e60018837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Thu, 14 Nov 2024 07:41:53 +0100 Subject: [PATCH 3/5] Cleanup and some more tests of (#126). --- Sources/FibonacciKit/Fibonacci+Fast.swift | 2 + Sources/FibonacciKit/Fibonacci+Stride.swift | 22 +----- Sources/FibonacciKit/Fibonacci+Toggle.swift | 4 -- Sources/FibonacciKit/Fibonacci.swift | 22 ++++-- Sources/FibonacciKit/Indexacci+Stride.swift | 10 --- Sources/FibonacciKit/Indexacci.swift | 20 ++++++ Sources/FibonacciKit/Tupleacci+Stride.swift | 10 --- Tests/FibonacciKitTests/Fibonacci.swift | 4 +- Tests/FibonacciKitTests/Indexacci.swift | 77 +++++++++++++++++++++ Tests/FibonacciKitTests/Tupleacci.swift | 72 +++++++++++++++++++ 10 files changed, 191 insertions(+), 52 deletions(-) create mode 100644 Tests/FibonacciKitTests/Indexacci.swift create mode 100644 Tests/FibonacciKitTests/Tupleacci.swift diff --git a/Sources/FibonacciKit/Fibonacci+Fast.swift b/Sources/FibonacciKit/Fibonacci+Fast.swift index d8d2ad54..892b0e3a 100644 --- a/Sources/FibonacciKit/Fibonacci+Fast.swift +++ b/Sources/FibonacciKit/Fibonacci+Fast.swift @@ -25,6 +25,8 @@ extension Fibonacci { /// /// - Note: It produces `nil` of the operation is `lossy`. /// + /// - Note: It produces `nil` if the `index` is `infinite`. + /// @inlinable public init?(_ index: consuming Element) { let base = Self.exactly(index, as: Indexacci.self) guard let base = base?.optional() else { return nil } diff --git a/Sources/FibonacciKit/Fibonacci+Stride.swift b/Sources/FibonacciKit/Fibonacci+Stride.swift index cb27958e..d0744543 100644 --- a/Sources/FibonacciKit/Fibonacci+Stride.swift +++ b/Sources/FibonacciKit/Fibonacci+Stride.swift @@ -25,24 +25,16 @@ extension Fibonacci { /// /// - Note: It produces `nil` of the operation is `lossy`. /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func incremented() -> Optional { self.base.incremented().map(Self.init(unsafe:)).optional() } - /// Returns the sequence pair at `index - 1`, or `nil`.. + /// Returns the sequence pair at `index - 1`, or `nil`. /// /// ### Fibonacci /// /// - Note: It produces `nil` of the operation is `lossy`. /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func decremented() -> Optional { self.base.decremented().map(Self.init(unsafe:)).optional() } @@ -57,10 +49,6 @@ extension Fibonacci { /// /// - Note: It produces `nil` of the operation is `lossy`. /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func doubled() -> Optional { Self.doubled(self.base).map(Self.init(unsafe:)).optional() } @@ -71,10 +59,6 @@ extension Fibonacci { /// /// - Note: It produces `nil` of the operation is `lossy`. /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func incremented(by other: borrowing Self) -> Optional { Self.incremented(self.base, by: other.base).map(Self.init(unsafe:)).optional() } @@ -85,10 +69,6 @@ extension Fibonacci { /// /// - Note: It produces `nil` of the operation is `lossy`. /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func decremented(by other: borrowing Self) -> Optional { Self.decremented(self.base, by: other.base).map(Self.init(unsafe:)).optional() } diff --git a/Sources/FibonacciKit/Fibonacci+Toggle.swift b/Sources/FibonacciKit/Fibonacci+Toggle.swift index 9f83f0db..623caa8a 100644 --- a/Sources/FibonacciKit/Fibonacci+Toggle.swift +++ b/Sources/FibonacciKit/Fibonacci+Toggle.swift @@ -25,10 +25,6 @@ extension Fibonacci { /// /// - Note: It produces `nil` of the operation is `lossy`. /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func toggled() -> Optional { Self.toggled(self.base).map(Self.init(unsafe:)).optional() } diff --git a/Sources/FibonacciKit/Fibonacci.swift b/Sources/FibonacciKit/Fibonacci.swift index d7eaac0d..681e7215 100644 --- a/Sources/FibonacciKit/Fibonacci.swift +++ b/Sources/FibonacciKit/Fibonacci.swift @@ -64,12 +64,17 @@ import CoreKit // MARK: Initializers //=------------------------------------------------------------------------= + /// Returns a new instance at `index` zero. @inlinable public init() { self.base = Indexacci.fibonacci() } - @inlinable public init(unsafe base: Indexacci) { - self.base = base + /// Returns a new instance using the given `components`. + /// + /// - Important: The `components` must belong to this sequence. + /// + @inlinable public init(unsafe components: consuming Indexacci) { + self.base = components } //=------------------------------------------------------------------------= @@ -78,19 +83,26 @@ import CoreKit /// The sequence element at `index`. @inlinable public var minor: Element { - self.base.tuple.minor + _read { + yield self.base.tuple.minor + } } /// The sequence element at `index + 1`. @inlinable public var major: Element { - self.base.tuple.major + _read { + yield self.base.tuple.major + } } /// The sequence `index`. @inlinable public var index: Element { - self.base.index + _read { + yield self.base.index + } } + /// Returns the `components` of this instance. @inlinable public consuming func components() -> Indexacci { self.base } diff --git a/Sources/FibonacciKit/Indexacci+Stride.swift b/Sources/FibonacciKit/Indexacci+Stride.swift index 33b719fc..d25c8ca2 100644 --- a/Sources/FibonacciKit/Indexacci+Stride.swift +++ b/Sources/FibonacciKit/Indexacci+Stride.swift @@ -20,11 +20,6 @@ extension Indexacci { //=------------------------------------------------------------------------= /// Returns the sequence pair at `index + 1`. - /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func incremented() -> Fallible { var error = false self.tuple = self.tuple.incremented().sink(&error) @@ -33,11 +28,6 @@ extension Indexacci { } /// Returns the sequence pair at `index - 1`. - /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func decremented() -> Fallible { var error = false self.tuple = self.tuple.decremented().sink(&error) diff --git a/Sources/FibonacciKit/Indexacci.swift b/Sources/FibonacciKit/Indexacci.swift index 29efdf19..64a6ab54 100644 --- a/Sources/FibonacciKit/Indexacci.swift +++ b/Sources/FibonacciKit/Indexacci.swift @@ -47,6 +47,26 @@ import CoreKit // MARK: Utilities //=------------------------------------------------------------------------= + @inlinable public var minor: Element { + _read { + yield self.tuple.minor + } + + _modify { + yield &self.tuple.minor + } + } + + @inlinable public var major: Element { + _read { + yield self.tuple.major + } + + _modify { + yield &self.tuple.major + } + } + @inlinable public consuming func components() -> (tuple: Tupleacci, index: Element) { (tuple: self.tuple, index: self.index) } diff --git a/Sources/FibonacciKit/Tupleacci+Stride.swift b/Sources/FibonacciKit/Tupleacci+Stride.swift index 52431779..bf4feb39 100644 --- a/Sources/FibonacciKit/Tupleacci+Stride.swift +++ b/Sources/FibonacciKit/Tupleacci+Stride.swift @@ -20,11 +20,6 @@ extension Tupleacci { //=------------------------------------------------------------------------= /// Returns the sequence pair at `index + 1`. - /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func incremented() -> Fallible { var error = false let major = major @@ -34,11 +29,6 @@ extension Tupleacci { } /// Returns the sequence pair at `index - 1`. - /// - /// ### Development - /// - /// - Todo: Measure versus `components()` approach. - /// @inlinable public consuming func decremented() -> Fallible { var error = false let minor = minor diff --git a/Tests/FibonacciKitTests/Fibonacci.swift b/Tests/FibonacciKitTests/Fibonacci.swift index a73292a7..eae509a7 100644 --- a/Tests/FibonacciKitTests/Fibonacci.swift +++ b/Tests/FibonacciKitTests/Fibonacci.swift @@ -58,11 +58,11 @@ import TestKit Metadata(type: U8L .self, low: 0, high: nil) ] - static let metadataAsSystemsInteger = metadata.filter { + static let metadataAsSystemsInteger: [Metadata] = metadata.filter { $0.type.isArbitrary == false } - static let metadataAsMaximalInteger = metadata.filter { + static let metadataAsMaximalInteger: [Metadata] = metadata.filter { $0.type.isMaximal } diff --git a/Tests/FibonacciKitTests/Indexacci.swift b/Tests/FibonacciKitTests/Indexacci.swift new file mode 100644 index 00000000..77054744 --- /dev/null +++ b/Tests/FibonacciKitTests/Indexacci.swift @@ -0,0 +1,77 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit +import FibonacciKit +import RandomIntKit +import TestKit + +//*============================================================================* +// MARK: * Indexacci +//*============================================================================* + +@Suite struct IndexacciTests { + + //=------------------------------------------------------------------------= + // MARK: Metadata + //=------------------------------------------------------------------------= + + static let types: [any BinaryInteger.Type] = FibonacciTests.metadata.map(\.type) + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "Tupleacci: start", + Tag.List.tags(.generic), + arguments: types + ) func start(type: any BinaryInteger.Type) throws { + + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + try #require(Indexacci.fibonacci() == Indexacci(minor: 0, major: 1, index: 0)) + } + } + + @Test( + "Tupleacci: accessors", + Tag.List.tags(.generic, .random), + arguments: types, fuzzers + ) func accessors( + type: any BinaryInteger.Type, randomness: consuming FuzzerInt + ) throws { + + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 8 { + let shift = Shift.max(or: 255) + let minor = T.random(through: shift, using: &randomness) + let major = T.random(through: shift, using: &randomness) + let index = T.random(through: shift, using: &randomness) + let tuple = Tupleacci.init(minor: minor, major: major) + let value = Indexacci.init(tuple: tuple, index: index) + + try #require(value.minor == minor) + try #require(value.major == major) + try #require(value.index == index) + try #require(value.tuple == tuple) + try #require(value.components() == (tuple, index)) + + always: do { + var other = Indexacci(minor: T.zero, major: T.zero, index: T.zero) + other.minor = minor + other.major = major + other.index = index + try #require(value == other) + } + } + } + } +} diff --git a/Tests/FibonacciKitTests/Tupleacci.swift b/Tests/FibonacciKitTests/Tupleacci.swift new file mode 100644 index 00000000..d93cbd2b --- /dev/null +++ b/Tests/FibonacciKitTests/Tupleacci.swift @@ -0,0 +1,72 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum 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 CoreKit +import FibonacciKit +import RandomIntKit +import TestKit + +//*============================================================================* +// MARK: * Tupleacci +//*============================================================================* + +@Suite struct TupleacciTests { + + //=------------------------------------------------------------------------= + // MARK: Metadata + //=------------------------------------------------------------------------= + + static let types: [any BinaryInteger.Type] = FibonacciTests.metadata.map(\.type) + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "Tupleacci: start", + Tag.List.tags(.generic), + arguments: types + ) func start(type: any BinaryInteger.Type) throws { + + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + try #require(Tupleacci.fibonacci() == Tupleacci(minor: 0, major: 1)) + } + } + + @Test( + "Tupleacci: accessors", + Tag.List.tags(.generic, .random), + arguments: types, fuzzers + ) func accessors( + type: any BinaryInteger.Type, randomness: consuming FuzzerInt + ) throws { + + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 8 { + let shift = Shift.max(or: 255) + let minor = T.random(through: shift, using: &randomness) + let major = T.random(through: shift, using: &randomness) + let value = Tupleacci.init(minor: minor, major: major) + + try #require(value.minor == minor) + try #require(value.major == major) + try #require(value.components() == (minor, major)) + + always: do { + var other = Tupleacci(minor: T.zero, major: T.zero) + other.minor = minor + other.major = major + try #require((other) == value) + } + } + } + } +} From e10db09ced9f3bd6e39e1c61ee549da93f8148d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Thu, 14 Nov 2024 09:11:06 +0100 Subject: [PATCH 4/5] Fix some typos. --- Tests/FibonacciKitTests/Indexacci.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/FibonacciKitTests/Indexacci.swift b/Tests/FibonacciKitTests/Indexacci.swift index 77054744..56e701f1 100644 --- a/Tests/FibonacciKitTests/Indexacci.swift +++ b/Tests/FibonacciKitTests/Indexacci.swift @@ -29,7 +29,7 @@ import TestKit //=------------------------------------------------------------------------= @Test( - "Tupleacci: start", + "Indexacci: start", Tag.List.tags(.generic), arguments: types ) func start(type: any BinaryInteger.Type) throws { @@ -41,7 +41,7 @@ import TestKit } @Test( - "Tupleacci: accessors", + "Indexacci: accessors", Tag.List.tags(.generic, .random), arguments: types, fuzzers ) func accessors( From d93e588f3c1047e46613b93705f9673e36e700d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Thu, 14 Nov 2024 09:17:11 +0100 Subject: [PATCH 5/5] Resolve redundant `try` warnings. Fibonacci.init(_:) no longer throws. --- Tests/FibonacciKitTests/Fibonacci+Invariants.swift | 2 +- Tests/FibonacciKitTests/Fibonacci+Stride.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/FibonacciKitTests/Fibonacci+Invariants.swift b/Tests/FibonacciKitTests/Fibonacci+Invariants.swift index 42cd374f..e074f750 100644 --- a/Tests/FibonacciKitTests/Fibonacci+Invariants.swift +++ b/Tests/FibonacciKitTests/Fibonacci+Invariants.swift @@ -38,7 +38,7 @@ import TestKit for _ in 0 ..< conditional(debug: 8, release: 32) { let index = T.random(in: low...high, using: &randomness) - let fibonacci = try #require(try Fibonacci(index)) + let fibonacci = try #require(Fibonacci(index)) try #require(fibonacci.minor.euclidean(fibonacci.major) == 1) } } diff --git a/Tests/FibonacciKitTests/Fibonacci+Stride.swift b/Tests/FibonacciKitTests/Fibonacci+Stride.swift index fdec999f..bed2a19e 100644 --- a/Tests/FibonacciKitTests/Fibonacci+Stride.swift +++ b/Tests/FibonacciKitTests/Fibonacci+Stride.swift @@ -35,7 +35,7 @@ import TestKit let arbitrary: IX = conditional(debug: 144, release: 369) let low: IX = metadata.low ?? -arbitrary let high: IX = metadata.high ?? arbitrary - var fibonacci = try #require(try Fibonacci(T(low))) + var fibonacci = try #require(Fibonacci(T(low))) if let _ = metadata.low { try #require(fibonacci.decremented() == nil) @@ -111,9 +111,9 @@ import TestKit try #require((low...high).contains(i.1)) try #require((low...high).contains(i.2)) - let a = try #require(try Fibonacci(i.0)) - let b = try #require(try Fibonacci(i.1)) - let c = try #require(try Fibonacci(i.2)) + let a = try #require(Fibonacci(i.0)) + let b = try #require(Fibonacci(i.1)) + let c = try #require(Fibonacci(i.2)) try #require(a.incremented(by: b)?.components() == c.components()) try #require(b.incremented(by: a)?.components() == c.components())