From eec2b968627c943b2680bbe9cf579f6234c9201f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Thu, 7 Nov 2024 07:39:45 +0100 Subject: [PATCH] Lower Expect+Logarithm.swift utilities (#110). --- Sources/CoreKit/BinaryInteger+Logarithm.swift | 16 +- Sources/TestKit/Expect+Logarithm.swift | 36 --- .../BinaryInteger+Logarithm.swift | 208 ++++++++++++------ 3 files changed, 144 insertions(+), 116 deletions(-) delete mode 100644 Sources/TestKit/Expect+Logarithm.swift diff --git a/Sources/CoreKit/BinaryInteger+Logarithm.swift b/Sources/CoreKit/BinaryInteger+Logarithm.swift index 777663e7..cd1db830 100644 --- a/Sources/CoreKit/BinaryInteger+Logarithm.swift +++ b/Sources/CoreKit/BinaryInteger+Logarithm.swift @@ -20,20 +20,21 @@ extension BinaryInteger { /// The binary logarithm of `self` rounded towards zero. /// /// ```swift + /// I8( 8).ilog2() // 3 + /// I8( 7).ilog2() // 2 + /// I8( 6).ilog2() // 2 + /// I8( 4).ilog2() // 2 /// I8( 4).ilog2() // 2 /// I8( 3).ilog2() // 1 /// I8( 2).ilog2() // 1 /// I8( 1).ilog2() // 0 /// I8( 0).ilog2() // nil /// I8(-1).ilog2() // nil - /// I8(-2).ilog2() // nil - /// I8(-3).ilog2() // nil - /// I8(-4).ilog2() // nil /// ``` /// /// - Note: `Nonzero` guarantees nonoptional results. /// - @inlinable public /*borrowing*/ func ilog2() -> Optional { + @inlinable public borrowing func ilog2() -> Optional { guard self.isPositive else { return nil } let positive = Nonzero(unchecked: Magnitude(raw: copy self)) return positive.ilog2() as Count @@ -56,15 +57,16 @@ extension Nonzero where Value: UnsignedInteger { /// The binary logarithm of `self` rounded towards zero. /// /// ```swift + /// I8( 8).ilog2() // 3 + /// I8( 7).ilog2() // 2 + /// I8( 6).ilog2() // 2 + /// I8( 4).ilog2() // 2 /// I8( 4).ilog2() // 2 /// I8( 3).ilog2() // 1 /// I8( 2).ilog2() // 1 /// I8( 1).ilog2() // 0 /// I8( 0).ilog2() // nil /// I8(-1).ilog2() // nil - /// I8(-2).ilog2() // nil - /// I8(-3).ilog2() // nil - /// I8(-4).ilog2() // nil /// ``` /// /// - Note: `Nonzero` guarantees nonoptional results. diff --git a/Sources/TestKit/Expect+Logarithm.swift b/Sources/TestKit/Expect+Logarithm.swift deleted file mode 100644 index c5daa38c..00000000 --- a/Sources/TestKit/Expect+Logarithm.swift +++ /dev/null @@ -1,36 +0,0 @@ -//=----------------------------------------------------------------------------= -// 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: * Expect x Logarithm -//*============================================================================* - -@inlinable public func Ɣexpect( - _ value: T, - ilog2 expectation: Count, - at location: SourceLocation = #_sourceLocation -) throws where T: BinaryInteger { - //=------------------------------------------= - let ilog2 = try #require(value.ilog2()) - //=------------------------------------------= - #expect(ilog2 == expectation, sourceLocation: location) - #expect(ilog2 < T.size, sourceLocation: location) - #expect(ilog2.isInfinite == value.isInfinite, sourceLocation: location) - - if !ilog2.isInfinite { - let low = T.lsb.up(ilog2) - #expect(value >= low, sourceLocation: location) - - if let high = low.times(2).optional() { - #expect(value < high, sourceLocation: location) - } - } -} diff --git a/Tests/UltimathnumTests/BinaryInteger+Logarithm.swift b/Tests/UltimathnumTests/BinaryInteger+Logarithm.swift index e0307986..358b8560 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Logarithm.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Logarithm.swift @@ -20,26 +20,87 @@ import TestKit //=------------------------------------------------------------------------= // MARK: Tests //=------------------------------------------------------------------------= + + @Test( + "BinaryInteger/logarithm: ilog2() of examples", + Tag.List.tags(.documentation, .generic), + ParallelizationTrait.serialized, + arguments: Array<(I8, Count?)>([ - @Test("BinaryInteger/ilog2() - [entropic]", arguments: typesAsBinaryInteger, fuzzers) - func binaryIntegerLogarithm(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { - try whereIs(type) + (value: -1 as I8, ilog2: nil), + (value: 0 as I8, ilog2: nil), + (value: 1 as I8, ilog2: Count(0)), + (value: 2 as I8, ilog2: Count(1)), + (value: 3 as I8, ilog2: Count(1)), + (value: 4 as I8, ilog2: Count(2)), + (value: 5 as I8, ilog2: Count(2)), + (value: 6 as I8, ilog2: Count(2)), + (value: 7 as I8, ilog2: Count(2)), + (value: 8 as I8, ilog2: Count(3)), + (value: 9 as I8, ilog2: Count(3)), + (value: 10 as I8, ilog2: Count(3)), + (value: 11 as I8, ilog2: Count(3)), + (value: 12 as I8, ilog2: Count(3)), + (value: 13 as I8, ilog2: Count(3)), + (value: 14 as I8, ilog2: Count(3)), + (value: 15 as I8, ilog2: Count(3)), + (value: 16 as I8, ilog2: Count(4)), + + ])) func ilog2OfExamples(value: I8, ilog2: Count?) throws { + for type in typesAsBinaryInteger { + try whereIs(type) + } + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + if let value = T.exactly(value).optional() { + try #require(value.ilog2() == ilog2) + try Ɣrequire(validating: value, ilog2: ilog2) + } + } + } + + @Test( + "BinaryInteger/logarithm: ilog2() of random", + Tag.List.tags(.random, .generic), + arguments: typesAsBinaryInteger, fuzzers + ) func ilog2OfRandomPositive( + type: any BinaryInteger.Type, randomness: consuming FuzzerInt + ) throws { + try whereIs(type) func whereIs(_ type: T.Type) throws where T: BinaryInteger { for _ in 0 ..< conditional(debug: 32, release: 256) { - let random = T.entropic(through: Shift.max(or: 255), using: &randomness) - if !random.isPositive { - #expect(random.ilog2() == nil) - - } else { - let nonzero = try #require(Nonzero(exactly: random)) - let expectation = try #require(nonzero.value.ilog2()) - let magnitude: Nonzero = nonzero.magnitude() - #expect(expectation == magnitude.ilog2()) - #expect(expectation == magnitude.value.ilog2()) - try Ɣexpect(nonzero.value, ilog2: expectation) + let value = T.entropic(through: Shift.max(or: 255), using: &randomness) + try Ɣrequire(validating: value, ilog2: value.ilog2()) + } + } + } + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + private func Ɣrequire( + validating value: T, + ilog2 expectation: Optional, + at location: SourceLocation = #_sourceLocation + ) throws where T: BinaryInteger { + + if let expectation { + #expect(expectation < T.size, sourceLocation: location) + #expect(expectation.isInfinite == value.isInfinite, sourceLocation: location) + + if !expectation.isInfinite { + let low = T.lsb.up(expectation) + #expect(value >= low, sourceLocation: location) + + if let high = low.times(2).optional() { + #expect(value < high, sourceLocation: location) } } + + } else { + try #require(!value.isPositive, sourceLocation: location) } } } @@ -48,87 +109,88 @@ import TestKit // MARK: * Binary Integer x Logarithm x Edge Cases //*============================================================================* -@Suite(.tags(.documentation)) struct BinaryIntegerTestsOnLogarithmEdgeCases { +@Suite struct BinaryIntegerTestsOnLogarithmEdgeCases { //=------------------------------------------------------------------------= // MARK: Tests //=------------------------------------------------------------------------= - @Test("BinaryInteger/ilog2() - small positive", .serialized, arguments: [ - - Some( 1 as U8, yields: Count(0)), - Some( 2 as U8, yields: Count(1)), - Some( 3 as U8, yields: Count(1)), - Some( 4 as U8, yields: Count(2)), - Some( 5 as U8, yields: Count(2)), - Some( 6 as U8, yields: Count(2)), - Some( 7 as U8, yields: Count(2)), - Some( 8 as U8, yields: Count(3)), - Some( 9 as U8, yields: Count(3)), - Some(10 as U8, yields: Count(3)), - Some(11 as U8, yields: Count(3)), - Some(12 as U8, yields: Count(3)), - Some(13 as U8, yields: Count(3)), - Some(14 as U8, yields: Count(3)), - Some(15 as U8, yields: Count(3)), - Some(16 as U8, yields: Count(4)), - - ] as [Some]) - func binaryIntegerLogarithmOfSmallPositive(expectation: Some) throws { - for type in typesAsBinaryInteger { - try whereIs(type) - } + @Test( + "BinaryInteger/logarithm/edge-cases: ilog2 of zero is nil", + Tag.List.tags(.documentation, .exhaustive, .generic), + arguments: typesAsBinaryInteger + ) func ilog2OfZeroIsNil(type: any BinaryInteger.Type) throws { + try whereIs(type) func whereIs(_ type: T.Type) throws where T: BinaryInteger { - try Ɣexpect(T(expectation.input), ilog2: expectation.output) + #expect(T.zero.ilog2() == nil) } } - @Test("BinaryInteger/ilog2() - zero is nil [uniform]", arguments: typesAsBinaryIntegerAsSigned) - func binaryIntegerLogarithmOfZeroIsNil(type: any BinaryInteger.Type) { - whereIs(type) + @Test( + "BinaryInteger/logarithm/edge-cases: ilog2 of negative is nil", + Tag.List.tags(.documentation, .generic,.random), + arguments: typesAsBinaryIntegerAsSigned, fuzzers + ) func ilog2OfNegativeIsNil( + type: any SignedInteger.Type, randomness: consuming FuzzerInt + ) throws { - func whereIs(_ type: T.Type) where T: BinaryInteger { - #expect(T.zero.ilog2() == nil) + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: SignedInteger { + for _ in 0 ..< 32 { + let value = T.entropic(through: Shift.max(or: 255), as: Domain.natural, using: &randomness).toggled() + try #require(value.isNegative) + try #require(value.ilog2() == nil) + } } } - @Test("BinaryInteger/ilog2() - negative is nil [uniform]", arguments: typesAsBinaryIntegerAsSigned, fuzzers) - func binaryIntegerLogarithmOfNegativeIsNil(type: any SignedInteger.Type, randomness: consuming FuzzerInt) throws { - try whereIs(type) + @Test( + "BinaryInteger/logarithm/edge-cases: log2() of infinite is size - 1", + Tag.List.tags(.documentation, .generic, .random), + arguments: typesAsArbitraryIntegerAsUnsigned, fuzzers + ) func ilog2OfInfiniteIsOneLessThanSize( + type: any ArbitraryIntegerAsUnsigned.Type, randomness: consuming FuzzerInt + ) throws { - func whereIs(_ type: T.Type) throws where T: SignedInteger { - let low = T(repeating: Bit.one).up(Shift.max(or: 255)) - let high = T(repeating: Bit.one) - - #expect(low .ilog2() == nil) - #expect(high.ilog2() == nil) - + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: ArbitraryIntegerAsUnsigned { for _ in 0 ..< 32 { - let random = T.random(in: low...high, using: &randomness) - #expect(random.isNegative) - #expect(random.ilog2() == nil) + let value = T.entropic(through: Shift.max(or: 255), as: Domain.natural, using: &randomness).toggled() + try #require(value.isInfinite) + try #require(value.ilog2() == Count(raw: -2)) } } } +} + +//*============================================================================* +// MARK: * Binary Integer x Logarithm x Conveniences +//*============================================================================* + +@Suite struct BinaryIntegerTestsOnLogarithmConveniences { - @Test("BinaryInteger/ilog2() - infinite is one less than size [uniform]", arguments: typesAsArbitraryIntegerAsUnsigned, fuzzers) - func binaryIntegerLogarithmOfInfiniteIsOneLessThanSize(type: any ArbitraryIntegerAsUnsigned.Type, randomness: consuming FuzzerInt) throws { - try whereIs(type) + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "BinaryInteger/logarithm/conveniences: ilog2() as Nonzero", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryIntegerAsUnsigned, fuzzers + ) func ilog2AsNonzeroMagnitude( + type: any UnsignedInteger.Type, randomness: consuming FuzzerInt + ) throws { - func whereIs(_ type: T.Type) throws where T: ArbitraryIntegerAsUnsigned { - let low = T(repeating: Bit.one).up(Shift.max(or: 255)) - let high = T(repeating: Bit.one) - let expectation = Count(raw: -2) - - #expect(expectation == Count(raw: IX(raw: T.size) - 1)) - #expect(low .ilog2() == expectation) - #expect(high.ilog2() == expectation) - + try whereIs(type) + func whereIs(_ type: T.Type) throws where T: UnsignedInteger { for _ in 0 ..< 32 { - let random = T.random(in: low...high, using: &randomness) - #expect(random.isInfinite) - #expect(random.ilog2() == expectation) + let value = T.entropic(through: Shift.max(or: 255), as: Domain.natural, using: &randomness) + if let nonzero = Nonzero(exactly: value) { + try #require(value.isPositive) + let expectation = value.ilog2() as Count? + try #require(expectation == nonzero.ilog2() as Count) + } } } }